Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-31 13:11:49 +03:00
parent d677abc44d
commit dadcea496b
9 changed files with 205 additions and 27 deletions

View file

@ -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()
}

View file

@ -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<OnActivityResultCallback>()
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,

View file

@ -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<GlobalSettingsState> = createMainStateFlow()
private fun createMainStateFlow(): StateFlow<GlobalSettingsState> {
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,
)
}
}

View file

@ -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)
}

View file

@ -5,6 +5,7 @@
<application>
<activity
android:name="com.tangem.feature.tester.presentation.TesterActivity"
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"
android:exported="false" />
</application>
</manifest>
</manifest>

View file

@ -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
}
object Progress : HideAllCurrenciesConfig()
}
internal data class ToggleAppThemeConfig(
val currentAppTheme: AppThemeMode,
val onClick: () -> Unit,
)

View file

@ -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) {},
),
)
}
}

View file

@ -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)
}
}

View file

@ -5,4 +5,5 @@
<string name="stand_toggles" translatable="false">Stand toggles</string>
<string name="tester_actions" translatable="false">Tester actions</string>
<string name="hide_all_currencies" translatable="false">Hide all currencies</string>
<string name="toggle_app_theme" translatable="false">Toggle app theme - %s</string>
</resources>