From dadcea496b98d1bc0e2d37125f649ecb77667206 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Aug 2023 13:11:49 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/tap/GlobalSettingsState.kt | 12 +++ .../main/java/com/tangem/tap/MainActivity.kt | 36 +++++++-- .../main/java/com/tangem/tap/MainViewModel.kt | 35 ++++++++ features/tester/impl/build.gradle.kts | 8 ++ .../tester/impl/src/main/AndroidManifest.xml | 3 +- .../actions/TesterActionsContentState.kt | 18 +++-- .../actions/TesterActionsScreen.kt | 39 ++++++--- .../actions/TesterActionsViewModel.kt | 80 ++++++++++++++++++- .../impl/src/main/res/values/strings.xml | 1 + 9 files changed, 205 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/GlobalSettingsState.kt create mode 100644 app/src/main/java/com/tangem/tap/MainViewModel.kt diff --git a/app/src/main/java/com/tangem/tap/GlobalSettingsState.kt b/app/src/main/java/com/tangem/tap/GlobalSettingsState.kt new file mode 100644 index 0000000000..19cd307271 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/GlobalSettingsState.kt @@ -0,0 +1,12 @@ +package com.tangem.tap + +import com.tangem.domain.apptheme.model.AppThemeMode + +internal sealed class GlobalSettingsState { + + object Loading : GlobalSettingsState() + + data class Content( + val appThemeMode: AppThemeMode, + ) : GlobalSettingsState() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 429cbe3e57..06ca8efa85 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -4,10 +4,11 @@ import android.content.Intent import android.content.pm.ActivityInfo import android.os.Bundle import android.view.View +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat -import androidx.core.view.WindowInsetsControllerCompat +import androidx.lifecycle.lifecycleScope import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.snackbar.Snackbar import com.tangem.core.navigation.AppScreen @@ -25,6 +26,7 @@ import com.tangem.tap.common.ActivityResultCallbackHolder import com.tangem.tap.common.DialogManager import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.SnackbarHandler +import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.NotificationsHandler import com.tangem.tap.common.redux.global.GlobalAction @@ -52,6 +54,8 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import java.lang.ref.WeakReference import javax.inject.Inject @@ -108,6 +112,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var walletConnectInteractor: WalletConnectInteractor + private val viewModel: MainViewModel by viewModels() + private var isInitializing: Boolean = true + // TODO: fixme: inject through DI private val intentProcessor: IntentProcessor = IntentProcessor() @@ -118,11 +125,17 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac private val onActivityResultCallbacks = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { - installSplashScreen() + val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) + + bootstrapMainStateUpdates() + + splashScreen.setKeepOnScreenCondition { isInitializing } + setContentView(R.layout.activity_main) systemActions() + store.dispatch(NavigationAction.ActivityCreated(WeakReference(this))) cardSdkLifecycleObserver.onCreate(context = this) @@ -198,13 +211,24 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac store.dispatch(GlobalAction.UpdateUserWalletsListManager(manager)) } + private fun bootstrapMainStateUpdates() { + viewModel.state + .onEach { state -> + isInitializing = state is GlobalSettingsState.Loading + + when (state) { + is GlobalSettingsState.Content -> { + MutableAppThemeModeHolder.value = state.appThemeMode + } + is GlobalSettingsState.Loading -> Unit + } + } + .launchIn(lifecycleScope) + } + private fun systemActions() { WindowCompat.setDecorFitsSystemWindows(window, false) - val windowInsetsController = WindowInsetsControllerCompat(window, binding.root) - windowInsetsController.isAppearanceLightStatusBars = true - windowInsetsController.isAppearanceLightNavigationBars = true - supportFragmentManager.registerFragmentLifecycleCallbacks( NavBarInsetsFragmentLifecycleCallback(), true, diff --git a/app/src/main/java/com/tangem/tap/MainViewModel.kt b/app/src/main/java/com/tangem/tap/MainViewModel.kt new file mode 100644 index 0000000000..2cb276000c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/MainViewModel.kt @@ -0,0 +1,35 @@ +package com.tangem.tap + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import arrow.core.getOrElse +import com.tangem.domain.apptheme.GetAppThemeModeUseCase +import com.tangem.domain.apptheme.model.AppThemeMode +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +@HiltViewModel +internal class MainViewModel @Inject constructor( + private val getAppThemeModeUseCase: GetAppThemeModeUseCase, +) : ViewModel() { + + val state: StateFlow = createMainStateFlow() + + private fun createMainStateFlow(): StateFlow { + return getAppThemeModeUseCase() + .map { maybeMode -> + val mode = maybeMode.getOrElse { AppThemeMode.DEFAULT } + + GlobalSettingsState.Content(appThemeMode = mode) + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5_000), + initialValue = GlobalSettingsState.Loading, + ) + } +} \ No newline at end of file diff --git a/features/tester/impl/build.gradle.kts b/features/tester/impl/build.gradle.kts index b94d17db0e..73011f1a76 100644 --- a/features/tester/impl/build.gradle.kts +++ b/features/tester/impl/build.gradle.kts @@ -23,6 +23,10 @@ dependencies { implementation(deps.hilt.android) kapt(deps.hilt.kapt) + /** Domain modules */ + implementation(projects.domain.appTheme) + implementation(projects.domain.appTheme.models) + /** Core modules */ implementation(project(":core:featuretoggles")) implementation(project(":core:ui")) @@ -32,4 +36,8 @@ dependencies { /** Other modules */ implementation(project(":libs:crypto")) + + /** Other libraries */ + implementation(deps.arrow.core) + implementation(deps.timber) } \ No newline at end of file diff --git a/features/tester/impl/src/main/AndroidManifest.xml b/features/tester/impl/src/main/AndroidManifest.xml index 57f9021fbf..b4e4fc0cfd 100644 --- a/features/tester/impl/src/main/AndroidManifest.xml +++ b/features/tester/impl/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ - \ No newline at end of file + diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt index ea8771920d..fa65d935e4 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt @@ -1,12 +1,20 @@ package com.tangem.feature.tester.presentation.actions +import com.tangem.domain.apptheme.model.AppThemeMode + internal data class TesterActionsContentState( val onBackClick: () -> Unit, - val hideAllCurrencies: HideAllCurrenciesState, + val hideAllCurrenciesConfig: HideAllCurrenciesConfig, + val toggleAppThemeConfig: ToggleAppThemeConfig, ) -internal sealed interface HideAllCurrenciesState { - data class Clickable(val onClick: () -> Unit) : HideAllCurrenciesState +internal sealed class HideAllCurrenciesConfig { + data class Clickable(val onClick: () -> Unit) : HideAllCurrenciesConfig() - object Progress : HideAllCurrenciesState -} \ No newline at end of file + object Progress : HideAllCurrenciesConfig() +} + +internal data class ToggleAppThemeConfig( + val currentAppTheme: AppThemeMode, + val onClick: () -> Unit, +) \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt index 341b2879e7..f8d9df0a78 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt @@ -2,11 +2,7 @@ package com.tangem.feature.tester.presentation.actions import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -16,6 +12,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme +import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.feature.tester.impl.R @OptIn(ExperimentalFoundationApi::class) @@ -33,23 +30,37 @@ internal fun TesterActionsScreen(state: TesterActionsContentState, modifier: Mod ) } item { - val onClick = remember(state.hideAllCurrencies) { - { (state.hideAllCurrencies as? HideAllCurrenciesState.Clickable)?.onClick?.invoke() ?: Unit } + val onClick = remember(state.hideAllCurrenciesConfig) { + { (state.hideAllCurrenciesConfig as? HideAllCurrenciesConfig.Clickable)?.onClick?.invoke() ?: Unit } } TesterActionItem( - progress = state.hideAllCurrencies is HideAllCurrenciesState.Progress, + name = stringResource(R.string.hide_all_currencies), + progress = state.hideAllCurrenciesConfig is HideAllCurrenciesConfig.Progress, onClick = onClick, ) } + item { + val config = state.toggleAppThemeConfig + + TesterActionItem( + name = stringResource(id = R.string.toggle_app_theme, config.currentAppTheme.name), + onClick = config.onClick, + ) + } } } @Composable -private fun TesterActionItem(progress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { +private fun TesterActionItem( + name: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + progress: Boolean = false, +) { Box(modifier = modifier.padding(all = TangemTheme.dimens.spacing16)) { PrimaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.hide_all_currencies), + text = name, onClick = onClick, showProgress = progress, ) @@ -63,7 +74,13 @@ private fun TesterActionsScreenSample(modifier: Modifier = Modifier) { modifier = modifier .background(TangemTheme.colors.background.primary), ) { - TesterActionsScreen(state = TesterActionsContentState({}, HideAllCurrenciesState.Clickable({}))) + TesterActionsScreen( + state = TesterActionsContentState( + onBackClick = {}, + hideAllCurrenciesConfig = HideAllCurrenciesConfig.Clickable {}, + toggleAppThemeConfig = ToggleAppThemeConfig(AppThemeMode.DEFAULT) {}, + ), + ) } } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt index f2024d34cd..b919f37132 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt @@ -5,15 +5,25 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import arrow.core.getOrElse +import com.tangem.domain.apptheme.ChangeAppThemeModeUseCase +import com.tangem.domain.apptheme.GetAppThemeModeUseCase +import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter import com.tangem.lib.crypto.UserWalletManager import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel internal class TesterActionsViewModel @Inject constructor( private val userWalletManager: UserWalletManager, + private val changeAppThemeModeUseCase: ChangeAppThemeModeUseCase, + private val getAppThemeModeUseCase: GetAppThemeModeUseCase, ) : ViewModel() { var uiState: TesterActionsContentState by mutableStateOf(initialState) @@ -21,22 +31,84 @@ internal class TesterActionsViewModel @Inject constructor( private val initialState: TesterActionsContentState get() = TesterActionsContentState( - onBackClick = { /* [REDACTED_TODO_COMMENT] */ }, - hideAllCurrencies = HideAllCurrenciesState.Clickable(this::hideAllCurrencies), + onBackClick = { /* no-op */ }, + hideAllCurrenciesConfig = HideAllCurrenciesConfig.Clickable(this::hideAllCurrencies), + toggleAppThemeConfig = ToggleAppThemeConfig(AppThemeMode.DEFAULT, this::toggleAppTheme), ) + init { + bootstrapAppThemeModeUpdates() + } + fun setupNavigation(router: InnerTesterRouter) { uiState = uiState.copy(onBackClick = router::back) } private fun hideAllCurrencies() = viewModelScope.launch { uiState = uiState.copy( - hideAllCurrencies = HideAllCurrenciesState.Progress, + hideAllCurrenciesConfig = HideAllCurrenciesConfig.Progress, ) userWalletManager.hideAllTokens() uiState = uiState.copy( - hideAllCurrencies = HideAllCurrenciesState.Clickable(this@TesterActionsViewModel::hideAllCurrencies), + hideAllCurrenciesConfig = HideAllCurrenciesConfig.Clickable(this@TesterActionsViewModel::hideAllCurrencies), ) } + + private fun toggleAppTheme() = viewModelScope.launch { + val currentAppThemeMode = uiState.toggleAppThemeConfig.currentAppTheme + val newAppThemeMode = when (currentAppThemeMode) { + AppThemeMode.FORCE_DARK -> AppThemeMode.FORCE_LIGHT + AppThemeMode.FORCE_LIGHT -> AppThemeMode.FOLLOW_SYSTEM + AppThemeMode.FOLLOW_SYSTEM -> AppThemeMode.FORCE_DARK + } + + Timber.d( + """ + Change app theme mode + |- Current theme mode: $currentAppThemeMode + |- New theme mode: $newAppThemeMode + """.trimIndent(), + ) + + changeAppThemeModeUseCase(newAppThemeMode).onLeft { error -> + Timber.e( + """ + Unable to change app theme mode + |- Error: $error + """.trimIndent(), + ) + } + } + + private fun bootstrapAppThemeModeUpdates() { + getAppThemeModeUseCase() + .distinctUntilChanged() + .onEach { maybeAppThemeMode -> + Timber.d( + """ + Current app theme mode updated + |- Previous app theme mode: ${uiState.toggleAppThemeConfig.currentAppTheme} + |- New app theme mode: $maybeAppThemeMode + """.trimIndent(), + ) + + uiState = uiState.copy( + toggleAppThemeConfig = uiState.toggleAppThemeConfig.copy( + currentAppTheme = maybeAppThemeMode.getOrElse { error -> + Timber.e( + """ + Unable to get current app theme mode, using default + |- Default theme mode: ${AppThemeMode.DEFAULT} + |- Error: $error + """.trimIndent(), + ) + + AppThemeMode.DEFAULT + }, + ), + ) + } + .launchIn(viewModelScope) + } } \ No newline at end of file diff --git a/features/tester/impl/src/main/res/values/strings.xml b/features/tester/impl/src/main/res/values/strings.xml index 22d5e89633..9a5a5846b6 100644 --- a/features/tester/impl/src/main/res/values/strings.xml +++ b/features/tester/impl/src/main/res/values/strings.xml @@ -5,4 +5,5 @@ Stand toggles Tester actions Hide all currencies + Toggle app theme - %s