From 34ae2abb5a106d47a79510c92c2be7b80b0844d6 Mon Sep 17 00:00:00 2001 From: Rui Date: Fri, 29 Mar 2024 16:13:48 -0700 Subject: [PATCH 1/3] MOB-421 add settings control to turn in-app notifications on/off completely --- v4/app/src/main/assets/settings.json | 11 ++ v4/app/src/main/assets/settings_debug.json | 11 ++ .../main/assets/settings_notifications.json | 45 ++--- .../trading/common/navigation/DydxRoutes.kt | 1 + .../feature/profile/DydxProfileRouter.kt | 9 + .../notifications/DydxNotificationsView.kt | 37 ++++ .../DydxNotificationsViewModel.kt | 54 ++++++ .../trading/feature/shared/PreferenceKeys.kt | 3 + .../feature/shared/views/SettingsView.kt | 175 ++++++++++++------ .../workers/globalworkers/DydxAlertsWorker.kt | 9 + 10 files changed, 270 insertions(+), 85 deletions(-) create mode 100644 v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsView.kt create mode 100644 v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt diff --git a/v4/app/src/main/assets/settings.json b/v4/app/src/main/assets/settings.json index 59a5590c..26300e2f 100644 --- a/v4/app/src/main/assets/settings.json +++ b/v4/app/src/main/assets/settings.json @@ -23,6 +23,17 @@ "field":"v4_theme" } }, + { + "title" : { + "text" : "APP.V4.NOTIFICATIONS" + }, + "link" : { + "text" : "settings/notifications" + }, + "field":{ + "field":"should_display_in_app_notifications" + } + }, { "title" : { "text" : "APP.V4.SYSTEM_STATUS" diff --git a/v4/app/src/main/assets/settings_debug.json b/v4/app/src/main/assets/settings_debug.json index 5a224188..9f12de14 100644 --- a/v4/app/src/main/assets/settings_debug.json +++ b/v4/app/src/main/assets/settings_debug.json @@ -23,6 +23,17 @@ "field":"v4_theme" } }, + { + "title" : { + "text" : "APP.V4.NOTIFICATIONS" + }, + "link" : { + "text" : "settings/notifications" + }, + "field":{ + "field":"should_display_in_app_notifications" + } + }, { "title" : { "text" : "APP.V4.TRADING_NETWORK" diff --git a/v4/app/src/main/assets/settings_notifications.json b/v4/app/src/main/assets/settings_notifications.json index ed6b0787..7b9b916e 100644 --- a/v4/app/src/main/assets/settings_notifications.json +++ b/v4/app/src/main/assets/settings_notifications.json @@ -4,41 +4,22 @@ "fields":[ { "title":{ - "text":"General" - }, - "subtitle":{ - "text":"New markets, newsletters, product updates, and more." - }, - "field":{ - "field":"notifications_general", - "optional":true, - "type" : "bool" - } - }, - { - "title":{ - "text":"Account" - }, - "subtitle":{ - "text":"Deposits, withdrawals, and other important account changes." - }, - "field":{ - "field":"notifications_account", - "optional":true, - "type" : "bool" - } - }, - { - "title":{ - "text":"Trading" - }, - "subtitle":{ - "text":"Order updates, liquidation warnings, funding payments." + "text":"APP.V4.DISPLAY_IN_APP_NOTIFICATIONS" }, "field":{ - "field":"notifications_account", + "field":"should_display_in_app_notifications", + "type":"bool", "optional":true, - "type" : "bool" + "options" : [ + { + "text": "yes", + "value" : "1" + }, + { + "text": "no", + "value" : "0" + } + ] } } ] diff --git a/v4/common/src/main/java/exchange/dydx/trading/common/navigation/DydxRoutes.kt b/v4/common/src/main/java/exchange/dydx/trading/common/navigation/DydxRoutes.kt index b71076e3..8df7f639 100644 --- a/v4/common/src/main/java/exchange/dydx/trading/common/navigation/DydxRoutes.kt +++ b/v4/common/src/main/java/exchange/dydx/trading/common/navigation/DydxRoutes.kt @@ -32,6 +32,7 @@ object ProfileRoutes { const val help = "help" const val rewards = "rewards" const val debug_enable = "action/debug/enable" + const val notifications = "settings/notifications" } object NewsAlertsRoutes { diff --git a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/DydxProfileRouter.kt b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/DydxProfileRouter.kt index e43f3ac2..e6a5077e 100644 --- a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/DydxProfileRouter.kt +++ b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/DydxProfileRouter.kt @@ -15,6 +15,7 @@ import exchange.dydx.trading.feature.profile.help.DydxHelpView import exchange.dydx.trading.feature.profile.history.DydxHistoryView import exchange.dydx.trading.feature.profile.keyexport.DydxKeyExportView import exchange.dydx.trading.feature.profile.language.DydxLanguageView +import exchange.dydx.trading.feature.profile.notifications.DydxNotificationsView import exchange.dydx.trading.feature.profile.rewards.DydxRewardsView import exchange.dydx.trading.feature.profile.settings.DydxSettingsView import exchange.dydx.trading.feature.profile.systemstatus.DydxSystemStatusView @@ -161,4 +162,12 @@ fun NavGraphBuilder.profileGraph( ) { navBackStackEntry -> DydxDebugEnableView.Content(Modifier) } + + dydxComposable( + router = appRouter, + route = ProfileRoutes.notifications, + deepLinks = appRouter.deeplinks(ProfileRoutes.notifications), + ) { navBackStackEntry -> + DydxNotificationsView.Content(Modifier) + } } diff --git a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsView.kt b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsView.kt new file mode 100644 index 00000000..5b3dc0d6 --- /dev/null +++ b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsView.kt @@ -0,0 +1,37 @@ +package exchange.dydx.trading.feature.profile.notifications + +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import exchange.dydx.abacus.protocols.LocalizerProtocol +import exchange.dydx.trading.common.component.DydxComponent +import exchange.dydx.trading.common.compose.collectAsStateWithLifecycle +import exchange.dydx.trading.common.theme.DydxThemedPreviewSurface +import exchange.dydx.trading.common.theme.MockLocalizer +import exchange.dydx.trading.feature.shared.views.SettingsView + +@Preview +@Composable +fun Preview_DydxNotificationsView() { + DydxThemedPreviewSurface { + DydxNotificationsView.Content(Modifier, SettingsView.ViewState.preview) + } +} + +object DydxNotificationsView : DydxComponent { + @Composable + override fun Content(modifier: Modifier) { + val viewModel: DydxNotificationsViewModel = hiltViewModel() + + val state = viewModel.state.collectAsStateWithLifecycle(initialValue = null).value + Content(modifier, state) + } + + @Composable + fun Content(modifier: Modifier, state: SettingsView.ViewState?) { + SettingsView.Content(modifier = modifier, state = state) + } +} + diff --git a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt new file mode 100644 index 00000000..e5c87449 --- /dev/null +++ b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt @@ -0,0 +1,54 @@ +package exchange.dydx.trading.feature.profile.notifications + +import android.content.Context +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import exchange.dydx.abacus.protocols.LocalizerProtocol +import exchange.dydx.platformui.settings.PlatformUISettings +import exchange.dydx.trading.common.DydxViewModel +import exchange.dydx.trading.common.navigation.DydxRouter +import exchange.dydx.trading.feature.shared.PreferenceKeys +import exchange.dydx.trading.feature.shared.views.SettingsView +import exchange.dydx.utilities.utils.SharedPreferencesStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + + +private val settingsFile = "settings_notifications.json" + +@HiltViewModel +class DydxNotificationsViewModel @Inject constructor( + private val localizer: LocalizerProtocol, + @ApplicationContext private val appContext: Context, + private val router: DydxRouter, + private val preferencesStore: SharedPreferencesStore, +) : ViewModel(), DydxViewModel { + + private val mutableState = MutableStateFlow(createViewState()) + + val state: Flow = mutableState + + private fun createViewState(): SettingsView.ViewState { + val settings = PlatformUISettings.loadFromAssets(appContext, settingsFile) + + var viewState = SettingsView.ViewState.createFrom( + settings = settings, + localizer = localizer, + header = localizer.localize("APP.V4.NOTIFICATIONS"), + backAction = { + router.navigateBack() + }, + itemFieldAction = { _, value -> + preferencesStore.save(value, PreferenceKeys.Notifications) + mutableState.value = createViewState() + }, + ) + + val notificationsOn = preferencesStore.read(key = PreferenceKeys.Notifications, defaultValue = "1") + viewState.sections.first().items.first().value = notificationsOn + + return viewState + } +} diff --git a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/PreferenceKeys.kt b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/PreferenceKeys.kt index 21e499fe..0edc71c1 100644 --- a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/PreferenceKeys.kt +++ b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/PreferenceKeys.kt @@ -1,8 +1,11 @@ package exchange.dydx.trading.feature.shared +import android.app.Notification + object PreferenceKeys { const val Language = "language" const val Theme = "v4_theme" const val Env = "env" const val DirectionColor = "direction_color_preference" + const val Notifications = "should_display_in_app_notifications" } diff --git a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt index b730090e..418f4396 100644 --- a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt +++ b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt @@ -1,5 +1,6 @@ package exchange.dydx.trading.feature.shared.views +import android.widget.Switch import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -18,6 +19,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.Icon import androidx.compose.material.Text +import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -81,7 +83,7 @@ object SettingsView { data class Item( val title: String? = null, val subtitle: String? = null, - val value: String? = null, + var value: String? = null, val route: String? = null, var selected: Boolean? = null, val field: ItemField? = null, @@ -111,8 +113,24 @@ object SettingsView { } } + enum class ItemFieldType { + SELECT, + SWITCH; + + companion object { + fun fromString(value: String?): ItemFieldType? { + return when (value) { + "select" -> SELECT + "bool" -> SWITCH + else -> null + } + } + } + } + data class ItemField( val fieldId: String? = null, + val type: ItemFieldType? = null, val options: List? = null, val fieldAction: ((String) -> Unit)? = null, ) { @@ -159,42 +177,30 @@ object SettingsView { emptyList() } else if (section.fields!!.size > 1) { section.fields!!.map { item -> - Item( - title = item.title?.text, - value = if (item.field != null) { - valueOfField?.invoke(item.field!!) - } else { - item.text?.text - }, - route = item.link?.text, - field = if (item.field != null) { - ItemField( - fieldId = item.field?.field, - options = item.field?.options?.map { option -> - ItemFieldOption( - text = option.text, - value = option.value, - selected = option.value == valueOfField?.invoke(item.field!!), - ) - }, - fieldAction = { value -> - itemFieldAction?.invoke(item.field?.field ?: "", value) - }, - ) - } else { - null - }, + createSingleLineFieldItem( + item = item, + itemFieldAction = itemFieldAction, + valueOfField = valueOfField, ) } } else { - section.fields!!.first().field?.options?.map { option -> - Item( - title = option.text, - value = option.value, - route = null, + val sectionField = section.fields!!.first() + if (sectionField.field?.type == "bool") { + val item = createSingleLineFieldItem( + item = sectionField, + itemFieldAction = itemFieldAction, + valueOfField = valueOfField, ) + listOf(item) + } else { + sectionField.field?.options?.map { option -> + Item( + title = option.text, + value = option.value, + route = null, + ) + } ?: emptyList() } - ?: emptyList() }, ) }, @@ -202,6 +208,40 @@ object SettingsView { itemAction = itemAction, ) } + + private fun createSingleLineFieldItem( + item: PlatformUISettings.SectionField, + itemFieldAction: ((String, String) -> Unit)? = null, + valueOfField: ((PlatformUISettings.Field) -> String?)? = null + ): Item { + return Item( + title = item.title?.text, + value = if (item.field != null) { + valueOfField?.invoke(item.field!!) + } else { + item.text?.text + }, + route = item.link?.text, + field = if (item.field != null) { + ItemField( + fieldId = item.field?.field, + type = ItemFieldType.fromString(item.field?.type), + options = item.field?.options?.map { option -> + ItemFieldOption( + text = option.text, + value = option.value, + selected = option.value == valueOfField?.invoke(item.field!!), + ) + }, + fieldAction = { value -> + itemFieldAction?.invoke(item.field?.field ?: "", value) + }, + ) + } else { + null + }, + ) + } } } @@ -292,6 +332,16 @@ object SettingsView { item: ViewState.Item, state: ViewState ) { + @Composable + fun CreateFieldTitle() { + Text( + text = if (item.title != null) state.localizer.localize(item.title!!) else "", + style = TextStyle.dydxDefault + .themeFont(fontSize = ThemeFont.FontSize.base) + .themeColor(ThemeColor.SemanticColor.text_primary), + ) + } + val field = item.field ?: return Column( @@ -303,14 +353,8 @@ object SettingsView { ), verticalArrangement = Arrangement.spacedBy(ThemeShapes.VerticalPadding), ) { - Text( - text = if (item.title != null) state.localizer.localize(item.title!!) else "", - style = TextStyle.dydxDefault - .themeFont(fontSize = ThemeFont.FontSize.base) - .themeColor(ThemeColor.SemanticColor.text_primary), - ) - if (field.options == null) { + CreateFieldTitle() PlatformTextInput( modifier = Modifier .height(40.dp) @@ -321,19 +365,42 @@ object SettingsView { }, ) } else { - Row { - Spacer(modifier = Modifier.weight(1f)) - PlatformTextTabGroup( - items = field.options.map { it.text ?: "" }, - selectedItems = field.options.map { it.text ?: "" }, - currentSelection = field.options.indexOfFirst { it.selected == true }, - onSelectionChanged = { index -> - field.options.forEachIndexed { i, option -> - option.selected = i == index - } - item.field.fieldAction?.invoke(field.options[index].value ?: "") - }, - ) + when (field.type) { + ViewState.ItemFieldType.SELECT, null -> { + CreateFieldTitle() + Row { + Spacer(modifier = Modifier.weight(1f)) + PlatformTextTabGroup( + items = field.options.map { it.text ?: "" }, + selectedItems = field.options.map { it.text ?: "" }, + currentSelection = field.options.indexOfFirst { it.selected == true }, + onSelectionChanged = { index -> + field.options.forEachIndexed { i, option -> + option.selected = i == index + } + item.field.fieldAction?.invoke( + field.options[index].value ?: "" + ) + }, + ) + } + } + + ViewState.ItemFieldType.SWITCH -> { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + CreateFieldTitle() + Spacer(modifier = Modifier.weight(1f)) + Switch(checked = item?.value == "1", + onCheckedChange = { + item.field.fieldAction?.invoke(if (it) "1" else "0") + } + ) + } + } } } } @@ -389,7 +456,9 @@ object SettingsView { Icon( painter = painterResource(id = R.drawable.chevron_right), contentDescription = "", - modifier = Modifier.size(16.dp).padding(start = 8.dp), + modifier = Modifier + .size(16.dp) + .padding(start = 8.dp), tint = ThemeColor.SemanticColor.text_primary.color, ) } diff --git a/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/globalworkers/DydxAlertsWorker.kt b/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/globalworkers/DydxAlertsWorker.kt index 09097987..2f1e1c34 100644 --- a/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/globalworkers/DydxAlertsWorker.kt +++ b/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/globalworkers/DydxAlertsWorker.kt @@ -4,6 +4,8 @@ import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.dydxstatemanager.AbacusStateManagerProtocol import exchange.dydx.platformui.components.PlatformInfo import exchange.dydx.trading.common.navigation.DydxRouter +import exchange.dydx.trading.feature.shared.PreferenceKeys +import exchange.dydx.utilities.utils.SharedPreferencesStore import exchange.dydx.utilities.utils.WorkerProtocol import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn @@ -16,6 +18,7 @@ class DydxAlertsWorker( private val localizer: LocalizerProtocol, private val router: DydxRouter, private val platformInfo: PlatformInfo, + private val preferencesStore: SharedPreferencesStore, ) : WorkerProtocol { private val handledAlertHashes = mutableSetOf() @@ -48,6 +51,12 @@ class DydxAlertsWorker( .sortedBy { it.updateTimeInMilliseconds } .forEach { alert -> val alertText = alert.text ?: return@forEach + + val notificationsOn = preferencesStore.read(key = PreferenceKeys.Notifications, defaultValue = "1") + if (notificationsOn == "0") { + return@forEach + } + val link = alert.link platformInfo.show( title = alert.title, From 13765035c4844406efa33c7aaa4dc4e9ec78e4d5 Mon Sep 17 00:00:00 2001 From: Rui Date: Fri, 29 Mar 2024 16:13:48 -0700 Subject: [PATCH 2/3] MOB-421 add settings control to turn in-app notifications on/off completely --- .../java/exchange/dydx/trading/core/CoreViewModel.kt | 3 +++ .../profile/notifications/DydxNotificationsView.kt | 4 ---- .../notifications/DydxNotificationsViewModel.kt | 11 ++++++++++- .../feature/profile/settings/DydxSettingsViewModel.kt | 11 +++++++++-- .../dydx/trading/feature/shared/views/SettingsView.kt | 11 ++++++----- .../dydx/trading/feature/workers/DydxGlobalWorkers.kt | 4 +++- .../java/exchange/dydx/utilities/utils/SecureStore.kt | 7 +++++++ .../dydx/utilities/utils/SharedPreferencesStore.kt | 9 +++++++++ .../exchange/dydx/utilities/utils/StoreProtocol.kt | 3 +++ 9 files changed, 50 insertions(+), 13 deletions(-) diff --git a/v4/core/src/main/java/exchange/dydx/trading/core/CoreViewModel.kt b/v4/core/src/main/java/exchange/dydx/trading/core/CoreViewModel.kt index b26a4236..448f9f1d 100644 --- a/v4/core/src/main/java/exchange/dydx/trading/core/CoreViewModel.kt +++ b/v4/core/src/main/java/exchange/dydx/trading/core/CoreViewModel.kt @@ -17,6 +17,7 @@ import exchange.dydx.trading.integration.analytics.CompositeTracking import exchange.dydx.trading.integration.analytics.Tracking import exchange.dydx.trading.integration.cosmos.CosmosV4WebviewClientProtocol import exchange.dydx.utilities.utils.CachedFileLoader +import exchange.dydx.utilities.utils.SharedPreferencesStore import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import javax.inject.Inject @@ -37,6 +38,7 @@ class CoreViewModel @Inject constructor( private val parser: ParserProtocol, private val tracker: Tracking, val compositeTracking: CompositeTracking, + private val preferencesStore: SharedPreferencesStore, ) : ViewModel() { private var globalWorkers: DydxGlobalWorkers? = null @@ -53,6 +55,7 @@ class CoreViewModel @Inject constructor( formatter = formatter, parser = parser, tracker = tracker, + preferencesStore = preferencesStore, ) } diff --git a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsView.kt b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsView.kt index 5b3dc0d6..bcad33a3 100644 --- a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsView.kt +++ b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsView.kt @@ -1,15 +1,12 @@ package exchange.dydx.trading.feature.profile.notifications -import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel -import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.trading.common.component.DydxComponent import exchange.dydx.trading.common.compose.collectAsStateWithLifecycle import exchange.dydx.trading.common.theme.DydxThemedPreviewSurface -import exchange.dydx.trading.common.theme.MockLocalizer import exchange.dydx.trading.feature.shared.views.SettingsView @Preview @@ -34,4 +31,3 @@ object DydxNotificationsView : DydxComponent { SettingsView.Content(modifier = modifier, state = state) } } - diff --git a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt index e5c87449..e76fb200 100644 --- a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt +++ b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject - private val settingsFile = "settings_notifications.json" @HiltViewModel @@ -51,4 +50,14 @@ class DydxNotificationsViewModel @Inject constructor( return viewState } + + companion object { + fun currentValueText( + localizer: LocalizerProtocol, + preferencesStore: SharedPreferencesStore, + ): String? { + val notificationsOn = preferencesStore.read(key = PreferenceKeys.Notifications, defaultValue = "1") + return if (notificationsOn == "1") localizer.localize("APP.HEADER.ON") else localizer.localize("APP.HEADER.OFF") + } + } } diff --git a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/settings/DydxSettingsViewModel.kt b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/settings/DydxSettingsViewModel.kt index dd273faf..e48fa4d9 100644 --- a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/settings/DydxSettingsViewModel.kt +++ b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/settings/DydxSettingsViewModel.kt @@ -12,6 +12,7 @@ import exchange.dydx.trading.common.DydxViewModel import exchange.dydx.trading.common.navigation.DydxRouter import exchange.dydx.trading.feature.profile.color.DydxDirectionColorPreferenceViewModel import exchange.dydx.trading.feature.profile.language.DydxLanguageViewModel +import exchange.dydx.trading.feature.profile.notifications.DydxNotificationsViewModel import exchange.dydx.trading.feature.profile.theme.DydxThemeViewModel import exchange.dydx.trading.feature.profile.tradingnetwork.DydxTradingNetworkViewModel import exchange.dydx.trading.feature.shared.PreferenceKeys @@ -19,7 +20,7 @@ import exchange.dydx.trading.feature.shared.views.SettingsView import exchange.dydx.utilities.utils.DebugEnabled import exchange.dydx.utilities.utils.SharedPreferencesStore import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import javax.inject.Inject @HiltViewModel @@ -32,7 +33,7 @@ class DydxSettingsViewModel @Inject constructor( private val abacusStateManager: AbacusStateManagerProtocol, ) : ViewModel(), DydxViewModel { - val state: Flow = flowOf(createViewState()) + val state: Flow = preferencesStore.stateUpdatedCount.map { createViewState() } private fun createViewState(): SettingsView.ViewState { val settingsFile = if (DebugEnabled.enabled(preferencesStore)) { @@ -82,6 +83,12 @@ class DydxSettingsViewModel @Inject constructor( abacusStateManager = abacusStateManager, ) } + PreferenceKeys.Notifications -> { + return@createFrom DydxNotificationsViewModel.currentValueText( + localizer = localizer, + preferencesStore = preferencesStore, + ) + } else -> { return@createFrom "" } diff --git a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt index 418f4396..bc821685 100644 --- a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt +++ b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt @@ -116,7 +116,7 @@ object SettingsView { enum class ItemFieldType { SELECT, SWITCH; - + companion object { fun fromString(value: String?): ItemFieldType? { return when (value) { @@ -186,7 +186,7 @@ object SettingsView { } else { val sectionField = section.fields!!.first() if (sectionField.field?.type == "bool") { - val item = createSingleLineFieldItem( + val item = createSingleLineFieldItem( item = sectionField, itemFieldAction = itemFieldAction, valueOfField = valueOfField, @@ -379,7 +379,7 @@ object SettingsView { option.selected = i == index } item.field.fieldAction?.invoke( - field.options[index].value ?: "" + field.options[index].value ?: "", ) }, ) @@ -394,10 +394,11 @@ object SettingsView { ) { CreateFieldTitle() Spacer(modifier = Modifier.weight(1f)) - Switch(checked = item?.value == "1", + Switch( + checked = item?.value == "1", onCheckedChange = { item.field.fieldAction?.invoke(if (it) "1" else "0") - } + }, ) } } diff --git a/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/DydxGlobalWorkers.kt b/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/DydxGlobalWorkers.kt index 4fbe3969..cb3b8652 100644 --- a/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/DydxGlobalWorkers.kt +++ b/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/DydxGlobalWorkers.kt @@ -17,6 +17,7 @@ import exchange.dydx.trading.feature.workers.globalworkers.DydxUserTrackingWorke import exchange.dydx.trading.integration.analytics.Tracking import exchange.dydx.trading.integration.cosmos.CosmosV4WebviewClientProtocol import exchange.dydx.utilities.utils.CachedFileLoader +import exchange.dydx.utilities.utils.SharedPreferencesStore import exchange.dydx.utilities.utils.WorkerProtocol import kotlinx.coroutines.CoroutineScope @@ -32,11 +33,12 @@ class DydxGlobalWorkers( private val formatter: DydxFormatter, private val parser: ParserProtocol, private val tracker: Tracking, + private val preferencesStore: SharedPreferencesStore, ) : WorkerProtocol { private val workers = listOf( DydxUpdateWorker(scope, abacusStateManager, router, context), - DydxAlertsWorker(scope, abacusStateManager, localizer, router, platformInfo), + DydxAlertsWorker(scope, abacusStateManager, localizer, router, platformInfo, preferencesStore), DydxApiStatusWorker(scope, abacusStateManager, localizer, platformInfo), DydxRestrictionsWorker(scope, abacusStateManager, localizer, platformInfo), DydxCarteraConfigWorker(scope, abacusStateManager, cachedFileLoader, context), diff --git a/v4/utilities/src/main/java/exchange/dydx/utilities/utils/SecureStore.kt b/v4/utilities/src/main/java/exchange/dydx/utilities/utils/SecureStore.kt index a6faee7c..7a4ce6cf 100644 --- a/v4/utilities/src/main/java/exchange/dydx/utilities/utils/SecureStore.kt +++ b/v4/utilities/src/main/java/exchange/dydx/utilities/utils/SecureStore.kt @@ -4,6 +4,8 @@ import android.app.Application import android.content.SharedPreferences import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKeys +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject import javax.inject.Singleton @@ -26,6 +28,11 @@ class SecureStore @Inject constructor( EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, ) + private var _stateUpdatedCount = MutableStateFlow(0) + + override val stateUpdatedCount: StateFlow + get() = _stateUpdatedCount + override fun save(data: String, key: String) { sharedPreferences.edit().putString(key, data).apply() } diff --git a/v4/utilities/src/main/java/exchange/dydx/utilities/utils/SharedPreferencesStore.kt b/v4/utilities/src/main/java/exchange/dydx/utilities/utils/SharedPreferencesStore.kt index ef2151e9..d089c6db 100644 --- a/v4/utilities/src/main/java/exchange/dydx/utilities/utils/SharedPreferencesStore.kt +++ b/v4/utilities/src/main/java/exchange/dydx/utilities/utils/SharedPreferencesStore.kt @@ -3,6 +3,8 @@ package exchange.dydx.utilities.utils import android.app.Application import android.content.Context import android.content.SharedPreferences +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject import javax.inject.Singleton @@ -18,7 +20,13 @@ class SharedPreferencesStore @Inject constructor( private val sharedPreferences: SharedPreferences = application.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + private var _stateUpdatedCount = MutableStateFlow(0) + + override val stateUpdatedCount: StateFlow + get() = _stateUpdatedCount + override fun save(data: String, key: String) { + _stateUpdatedCount.value++ sharedPreferences.edit().putString(key, data).apply() } @@ -31,6 +39,7 @@ class SharedPreferencesStore @Inject constructor( } override fun delete(key: String) { + _stateUpdatedCount.value++ sharedPreferences.edit().remove(key).apply() } } diff --git a/v4/utilities/src/main/java/exchange/dydx/utilities/utils/StoreProtocol.kt b/v4/utilities/src/main/java/exchange/dydx/utilities/utils/StoreProtocol.kt index eca814a6..3f3003f3 100644 --- a/v4/utilities/src/main/java/exchange/dydx/utilities/utils/StoreProtocol.kt +++ b/v4/utilities/src/main/java/exchange/dydx/utilities/utils/StoreProtocol.kt @@ -1,11 +1,14 @@ package exchange.dydx.utilities.utils import android.util.Log +import kotlinx.coroutines.flow.StateFlow import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json interface StoreProtocol { + val stateUpdatedCount: StateFlow + fun save(data: String, key: String) fun read(key: String): String? From d1dca9d9e794cc3171005e503c66221d5bbcca88 Mon Sep 17 00:00:00 2001 From: Rui Date: Mon, 1 Apr 2024 11:09:01 -0700 Subject: [PATCH 3/3] Clean up --- .../DydxNotificationsViewModel.kt | 11 +++++----- .../feature/shared/NotificationEnabled.kt | 16 +++++++++++++++ .../feature/shared/views/SettingsView.kt | 20 +++++++++---------- .../workers/globalworkers/DydxAlertsWorker.kt | 5 ++--- 4 files changed, 32 insertions(+), 20 deletions(-) create mode 100644 v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/NotificationEnabled.kt diff --git a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt index e76fb200..8363de9f 100644 --- a/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt +++ b/v4/feature/profile/src/main/java/exchange/dydx/trading/feature/profile/notifications/DydxNotificationsViewModel.kt @@ -8,7 +8,7 @@ import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.platformui.settings.PlatformUISettings import exchange.dydx.trading.common.DydxViewModel import exchange.dydx.trading.common.navigation.DydxRouter -import exchange.dydx.trading.feature.shared.PreferenceKeys +import exchange.dydx.trading.feature.shared.NotificationEnabled import exchange.dydx.trading.feature.shared.views.SettingsView import exchange.dydx.utilities.utils.SharedPreferencesStore import kotlinx.coroutines.flow.Flow @@ -40,13 +40,12 @@ class DydxNotificationsViewModel @Inject constructor( router.navigateBack() }, itemFieldAction = { _, value -> - preferencesStore.save(value, PreferenceKeys.Notifications) + NotificationEnabled.update(preferencesStore, value) mutableState.value = createViewState() }, ) - val notificationsOn = preferencesStore.read(key = PreferenceKeys.Notifications, defaultValue = "1") - viewState.sections.first().items.first().value = notificationsOn + viewState.sections.first().items.first().value = NotificationEnabled.currentValue(preferencesStore) return viewState } @@ -56,8 +55,8 @@ class DydxNotificationsViewModel @Inject constructor( localizer: LocalizerProtocol, preferencesStore: SharedPreferencesStore, ): String? { - val notificationsOn = preferencesStore.read(key = PreferenceKeys.Notifications, defaultValue = "1") - return if (notificationsOn == "1") localizer.localize("APP.HEADER.ON") else localizer.localize("APP.HEADER.OFF") + val notificationsOn = NotificationEnabled.enabled(preferencesStore) + return if (notificationsOn) localizer.localize("APP.HEADER.ON") else localizer.localize("APP.HEADER.OFF") } } } diff --git a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/NotificationEnabled.kt b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/NotificationEnabled.kt new file mode 100644 index 00000000..1d3b0fea --- /dev/null +++ b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/NotificationEnabled.kt @@ -0,0 +1,16 @@ +package exchange.dydx.trading.feature.shared +import exchange.dydx.utilities.utils.SharedPreferencesStore + +object NotificationEnabled { + fun enabled(preferencesStore: SharedPreferencesStore): Boolean { + return currentValue(preferencesStore) == "1" + } + + fun currentValue(preferencesStore: SharedPreferencesStore): String { + return preferencesStore.read(PreferenceKeys.Notifications, defaultValue = "1") + } + + fun update(preferencesStore: SharedPreferencesStore, value: String) { + preferencesStore.save(value, PreferenceKeys.Notifications) + } +} diff --git a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt index bc821685..0d80436f 100644 --- a/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt +++ b/v4/feature/shared/src/main/java/exchange/dydx/trading/feature/shared/views/SettingsView.kt @@ -216,29 +216,27 @@ object SettingsView { ): Item { return Item( title = item.title?.text, - value = if (item.field != null) { - valueOfField?.invoke(item.field!!) - } else { + value = item.field?.let { + valueOfField?.invoke(it) + } ?: run { item.text?.text }, route = item.link?.text, - field = if (item.field != null) { + field = item.field?.let { ItemField( - fieldId = item.field?.field, - type = ItemFieldType.fromString(item.field?.type), - options = item.field?.options?.map { option -> + fieldId = it.field, + type = ItemFieldType.fromString(it.type), + options = it.options?.map { option -> ItemFieldOption( text = option.text, value = option.value, - selected = option.value == valueOfField?.invoke(item.field!!), + selected = option.value == valueOfField?.invoke(it), ) }, fieldAction = { value -> - itemFieldAction?.invoke(item.field?.field ?: "", value) + itemFieldAction?.invoke(it.field ?: "", value) }, ) - } else { - null }, ) } diff --git a/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/globalworkers/DydxAlertsWorker.kt b/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/globalworkers/DydxAlertsWorker.kt index 2f1e1c34..8266642c 100644 --- a/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/globalworkers/DydxAlertsWorker.kt +++ b/v4/feature/workers/src/main/java/exchange/dydx/trading/feature/workers/globalworkers/DydxAlertsWorker.kt @@ -4,7 +4,7 @@ import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.dydxstatemanager.AbacusStateManagerProtocol import exchange.dydx.platformui.components.PlatformInfo import exchange.dydx.trading.common.navigation.DydxRouter -import exchange.dydx.trading.feature.shared.PreferenceKeys +import exchange.dydx.trading.feature.shared.NotificationEnabled import exchange.dydx.utilities.utils.SharedPreferencesStore import exchange.dydx.utilities.utils.WorkerProtocol import kotlinx.coroutines.CoroutineScope @@ -52,8 +52,7 @@ class DydxAlertsWorker( .forEach { alert -> val alertText = alert.text ?: return@forEach - val notificationsOn = preferencesStore.read(key = PreferenceKeys.Notifications, defaultValue = "1") - if (notificationsOn == "0") { + if (!NotificationEnabled.enabled(preferencesStore)) { return@forEach }