Updated on 2026-08-14
This commit is contained in:
parent
d677abc44d
commit
dadcea496b
9 changed files with 205 additions and 27 deletions
12
app/src/main/java/com/tangem/tap/GlobalSettingsState.kt
Normal file
12
app/src/main/java/com/tangem/tap/GlobalSettingsState.kt
Normal 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()
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
35
app/src/main/java/com/tangem/tap/MainViewModel.kt
Normal file
35
app/src/main/java/com/tangem/tap/MainViewModel.kt
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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) {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue