diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/AnnouncementPreference.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/AnnouncementPreference.kt index 3aadcce5b71..e9c48bc2083 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/AnnouncementPreference.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/AnnouncementPreference.kt @@ -26,11 +26,14 @@ import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -42,20 +45,28 @@ import app.lawnchair.ui.preferences.components.layout.PreferenceTemplate import app.lawnchair.ui.preferences.data.liveinfo.liveInformationManager import app.lawnchair.ui.preferences.data.liveinfo.model.Announcement import app.lawnchair.ui.util.addIf -import com.android.launcher3.BuildConfig import com.android.launcher3.R +import kotlinx.coroutines.launch @Composable fun AnnouncementPreference() { val liveInformationManager = liveInformationManager() + val coroutineScope = rememberCoroutineScope() val enabled by liveInformationManager.enabled.asState() val showAnnouncements by liveInformationManager.showAnnouncements.asState() + val dismissedAnnouncementIds by liveInformationManager.dismissedAnnouncementIds.asState() val liveInformation by liveInformationManager.liveInformation.asState() + val announcements = remember { liveInformation.announcements.filter { it.id !in dismissedAnnouncementIds } } + if (enabled && showAnnouncements) { AnnouncementPreference( - announcements = liveInformation.announcements, + announcements = announcements, + onDismiss = { announcement -> + val dismissed = dismissedAnnouncementIds.toMutableSet().apply { add(announcement.id) } + coroutineScope.launch { liveInformationManager.dismissedAnnouncementIds.set(dismissed) } + }, ) } } @@ -63,15 +74,21 @@ fun AnnouncementPreference() { @Composable fun AnnouncementPreference( announcements: List, + onDismiss: (Announcement) -> Unit, modifier: Modifier = Modifier, ) { Column( modifier = modifier, ) { announcements.forEachIndexed { index, announcement -> - var show by rememberSaveable { mutableStateOf(true) } - AnnouncementItem(show, announcement) { show = false } - if (index != announcements.lastIndex && show && announcement.active && (!announcement.test || BuildConfig.DEBUG)) { + var dismissed by rememberSaveable { mutableStateOf(false) } + val visible = announcement.shouldBeVisible && !dismissed + + AnnouncementItem(visible, announcement) { + onDismiss(announcement) + dismissed = true + } + if (index != announcements.lastIndex && visible) { Spacer(modifier = Modifier.height(16.dp)) } } @@ -80,20 +97,19 @@ fun AnnouncementPreference( @Composable private fun AnnouncementItem( - show: Boolean, + visible: Boolean, announcement: Announcement, modifier: Modifier = Modifier, onClose: () -> Unit, ) { ExpandAndShrink( modifier = modifier, - visible = show && announcement.active && - announcement.text.isNotBlank() && - (!announcement.test || BuildConfig.DEBUG), + visible = visible, ) { AnnouncementItemContent( text = announcement.text, url = announcement.url, + icon = announcement.getIcon(), onClose = onClose, ) } @@ -103,6 +119,7 @@ private fun AnnouncementItem( private fun AnnouncementItemContent( text: String, url: String?, + icon: ImageVector, modifier: Modifier = Modifier, onClose: () -> Unit, ) { @@ -112,6 +129,7 @@ private fun AnnouncementItemContent( SwipeToDismissBoxValue.StartToEnd -> { onClose() } + SwipeToDismissBoxValue.EndToStart -> return@rememberSwipeToDismissBoxState false SwipeToDismissBoxValue.Settled -> return@rememberSwipeToDismissBoxState false } @@ -151,7 +169,11 @@ private fun AnnouncementItemContent( shape = MaterialTheme.shapes.large, color = MaterialTheme.colorScheme.surfaceVariant, ) { - AnnouncementPreferenceItemContent(text = text, url = url) + AnnouncementPreferenceItemContent( + text = text, + url = url, + icon = icon, + ) } } } @@ -167,6 +189,7 @@ private fun calculateAlpha(progress: Float): Float { private fun AnnouncementPreferenceItemContent( text: String, url: String?, + icon: ImageVector, modifier: Modifier = Modifier, ) { val context = LocalContext.current @@ -195,7 +218,7 @@ private fun AnnouncementPreferenceItemContent( }, startWidget = { Icon( - imageVector = Icons.Rounded.NewReleases, + imageVector = icon, tint = MaterialTheme.colorScheme.primary, contentDescription = null, ) @@ -225,6 +248,7 @@ private fun InfoPreferenceWithoutLinkPreview() { AnnouncementPreferenceItemContent( text = "Very important announcement ", url = "", + icon = Icons.Rounded.NewReleases, ) } @@ -234,5 +258,6 @@ private fun InfoPreferenceWithLinkPreview() { AnnouncementPreferenceItemContent( text = "Very important announcement with a very important link", url = "https://lawnchair.app/", + icon = Icons.Rounded.NewReleases, ) } diff --git a/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/LiveInformationManager.kt b/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/LiveInformationManager.kt index 2913db7bfa7..55d3104afad 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/LiveInformationManager.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/LiveInformationManager.kt @@ -6,10 +6,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import app.lawnchair.ui.preferences.data.liveinfo.model.AnnouncementId import app.lawnchair.ui.preferences.data.liveinfo.model.LiveInformation import com.android.launcher3.R import com.android.launcher3.util.MainThreadInitializedObject import com.patrykmichalik.opto.core.PreferenceManager +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json class LiveInformationManager private constructor(context: Context) : PreferenceManager { @@ -41,8 +43,28 @@ class LiveInformationManager private constructor(context: Context) : PreferenceM val liveInformation = preference( key = stringPreferencesKey(name = "live_information"), defaultValue = LiveInformation.default, - parse = { Json.decodeFromString(it) }, - save = { Json.encodeToString(LiveInformation.serializer(), it) }, + parse = { string -> + val withUnknownKeys = Json { ignoreUnknownKeys = true } + withUnknownKeys.decodeFromString(string) + }, + save = { liveInformation -> + Json.encodeToString( + LiveInformation.serializer(), + liveInformation, + ) + }, + ) + + val dismissedAnnouncementIds = preference( + key = stringPreferencesKey(name = "dismissed_announcement_ids"), + defaultValue = emptySet(), + parse = { + val withUnknownKeys = Json { ignoreUnknownKeys = true } + withUnknownKeys.decodeFromString>(it) + }, + save = { + Json.encodeToString(it) + }, ) } diff --git a/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/Announcement.kt b/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/Announcement.kt index 93abe437546..8b911400694 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/Announcement.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/Announcement.kt @@ -1,11 +1,60 @@ package app.lawnchair.ui.preferences.data.liveinfo.model +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.BugReport +import androidx.compose.material.icons.rounded.CheckCircle +import androidx.compose.material.icons.rounded.Error +import androidx.compose.material.icons.rounded.Favorite +import androidx.compose.material.icons.rounded.Feedback +import androidx.compose.material.icons.rounded.Forum +import androidx.compose.material.icons.rounded.Hub +import androidx.compose.material.icons.rounded.Loyalty +import androidx.compose.material.icons.rounded.NewReleases +import androidx.compose.material.icons.rounded.PriorityHigh +import androidx.compose.material.icons.rounded.PrivacyTip +import androidx.compose.material.icons.rounded.Sos +import androidx.compose.material.icons.rounded.Star +import androidx.compose.material.icons.rounded.Support +import androidx.compose.material.icons.rounded.Warning +import com.android.launcher3.BuildConfig import kotlinx.serialization.Serializable @Serializable data class Announcement( val text: String, val url: String? = null, - val active: Boolean = true, - val test: Boolean = false, -) + private val active: Boolean = true, + private val test: Boolean = false, + private val channel: String? = null, + private val icon: String? = null, +) { + + val id: AnnouncementId get() = text to url + + fun getIcon() = when (icon) { + "bug-report" -> Icons.Rounded.BugReport + "check-circle" -> Icons.Rounded.CheckCircle + "error" -> Icons.Rounded.Error + "favorite" -> Icons.Rounded.Favorite + "feedback" -> Icons.Rounded.Feedback + "forum" -> Icons.Rounded.Forum + "hub" -> Icons.Rounded.Hub + "loyalty" -> Icons.Rounded.Loyalty + "priority-high" -> Icons.Rounded.PriorityHigh + "privacy-tip" -> Icons.Rounded.PrivacyTip + "sos" -> Icons.Rounded.Sos + "star" -> Icons.Rounded.Star + "support" -> Icons.Rounded.Support + "warning" -> Icons.Rounded.Warning + else -> Icons.Rounded.NewReleases + } + + val shouldBeVisible + get(): Boolean { + if (active.not()) return false + if (text.isBlank()) return false + if (test && BuildConfig.DEBUG.not()) return false + if (channel != null && channel != BuildConfig.FLAVOR_channel) return false + return true + } +} diff --git a/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/AnnouncementId.kt b/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/AnnouncementId.kt new file mode 100644 index 00000000000..527b4fdcbe3 --- /dev/null +++ b/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/AnnouncementId.kt @@ -0,0 +1,3 @@ +package app.lawnchair.ui.preferences.data.liveinfo.model + +typealias AnnouncementId = Pair diff --git a/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/LiveInformation.kt b/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/LiveInformation.kt index cd328857e4a..ee4f0e8d7af 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/LiveInformation.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/data/liveinfo/model/LiveInformation.kt @@ -4,12 +4,13 @@ import kotlinx.serialization.Serializable @Serializable data class LiveInformation( - private val version: Int = 1, + private val version: Int = 2, val announcements: List, ) { + companion object { val default = LiveInformation( - version = 1, + version = 2, announcements = emptyList(), ) } diff --git a/lawnchair/src/app/lawnchair/ui/preferences/destinations/DebugMenuPreferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/destinations/DebugMenuPreferences.kt index 8e8d96afc22..99ae4a046a9 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/destinations/DebugMenuPreferences.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/destinations/DebugMenuPreferences.kt @@ -18,9 +18,12 @@ import app.lawnchair.ui.preferences.components.controls.SwitchPreference import app.lawnchair.ui.preferences.components.controls.TextPreference import app.lawnchair.ui.preferences.components.layout.PreferenceGroup import app.lawnchair.ui.preferences.components.layout.PreferenceLayout +import app.lawnchair.ui.preferences.data.liveinfo.liveInformationManager +import app.lawnchair.ui.preferences.data.liveinfo.model.LiveInformation import com.android.launcher3.settings.SettingsActivity import com.android.launcher3.uioverrides.flags.DeveloperOptionsFragment import com.patrykmichalik.opto.domain.Preference +import kotlinx.coroutines.runBlocking /** * A screen to house unfinished preferences and debug flags @@ -31,6 +34,7 @@ fun DebugMenuPreferences( ) { val prefs = preferenceManager() val prefs2 = preferenceManager2() + val liveInfoManager = liveInformationManager() val flags = remember { prefs.debugFlags } val flags2 = remember { prefs2.debugFlags } val textFlags = remember { prefs2.textFlags } @@ -60,6 +64,15 @@ fun DebugMenuPreferences( label = "Crash launcher", onClick = { throw RuntimeException("User triggered crash") }, ) + ClickablePreference( + label = "Reset live information", + onClick = { + runBlocking { + liveInfoManager.liveInformation.set(LiveInformation.default) + liveInfoManager.dismissedAnnouncementIds.set(emptySet()) + } + }, + ) } PreferenceGroup(heading = "Debug flags") {