Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-22 22:09:59 +03:00
parent ec23b55dbd
commit a2bc98f8a8
7 changed files with 171 additions and 40 deletions

View file

@ -9,7 +9,10 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
@ -65,7 +68,6 @@ internal fun EnvironmentTogglesScreen(uiModel: EnvironmentTogglesScreenUM) {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun EnvironmentButtons(
uiModel: EnvironmentTogglesScreenUM.ApiInfoUM,

View file

@ -6,14 +6,37 @@ import kotlinx.collections.immutable.ImmutableList
/**
* Content state of feature toggles screen
*
* @property topBarState top bar state
* @property appVersion app version
* @property featureToggles feature toggles list
* @property onBackClick the lambda to be invoked when back button is pressed
* @property onToggleValueChange the lambda to be invoked when switch button is pressed
* @property onApplyChangesClick the lambda to be invoked when apply changes button is pressed
* @property onRestartAppClick the lambda to be invoked when restart app button is pressed
*/
internal data class FeatureTogglesContentState(
val topBarState: TopBarState,
val appVersion: String,
val featureToggles: ImmutableList<TesterFeatureToggle>,
val onToggleValueChange: (String, Boolean) -> Unit,
val onBackClick: () -> Unit,
val onApplyChangesClick: () -> Unit,
)
val onToggleValueChange: (String, Boolean) -> Unit,
val onRestartAppClick: () -> Unit,
) {
/** Top bar state */
sealed interface TopBarState {
/**
* Config setup
*
* @property onBackClick the lambda to be invoked when back button is pressed
*/
data object ConfigSetup : TopBarState
/**
* Custom setup
*
* @property onRecoverClick the lambda to be invoked when recover button is pressed
*/
data class CustomSetup(val onRecoverClick: () -> Unit) : TopBarState
}
}

View file

@ -6,17 +6,21 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Switch
import androidx.compose.material.SwitchDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.components.TangemSwitch
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.tester.impl.R
@ -37,22 +41,35 @@ internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) {
.fillMaxSize()
.background(TangemTheme.colors.background.secondary),
) {
stickyHeader {
AppBarWithBackButton(
onBackClick = state.onBackClick,
text = stringResource(id = R.string.feature_toggles),
)
stickyHeader { TopAppBar(state = state.topBarState, onBackClick = state.onBackClick) }
if (state.topBarState is FeatureTogglesContentState.TopBarState.CustomSetup) {
item(key = "warning_notification", contentType = "warning_notification") {
WarningNotification(
appVersion = state.appVersion,
modifier = Modifier
.animateItem()
.padding(horizontal = 16.dp)
.padding(bottom = 8.dp),
)
}
}
items(state.featureToggles) { featureToggle ->
items(
items = state.featureToggles,
key = TesterFeatureToggle::name,
contentType = { "feature_toggle_item" },
) { featureToggle ->
FeatureToggleItem(
toggle = featureToggle,
onCheckedChange = { isChange -> state.onToggleValueChange(featureToggle.name, isChange) },
)
}
item {
PrimaryButton(
text = stringResource(id = R.string.apply_changes),
onClick = state.onApplyChangesClick,
text = stringResource(id = R.string.restart_app),
onClick = state.onRestartAppClick,
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing16),
@ -61,12 +78,48 @@ internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) {
}
}
@Composable
private fun TopAppBar(state: FeatureTogglesContentState.TopBarState, onBackClick: () -> Unit) {
AppBarWithBackButtonAndIcon(
onBackClick = onBackClick,
text = stringResource(id = R.string.feature_toggles),
iconRes = if (state is FeatureTogglesContentState.TopBarState.CustomSetup) {
R.drawable.ic_refresh_24
} else {
null
},
onIconClick = if (state is FeatureTogglesContentState.TopBarState.CustomSetup) {
state.onRecoverClick
} else {
null
},
)
}
@Composable
private fun WarningNotification(appVersion: String, modifier: Modifier = Modifier) {
Notification(
config = NotificationConfig(
subtitle = resourceReference(
id = R.string.feature_toggles_custom_setup_warning_description,
wrappedList(appVersion),
),
iconResId = R.drawable.ic_alert_triangle_20,
title = resourceReference(id = R.string.feature_toggles_custom_setup_warning_title),
),
modifier = modifier,
)
}
@Composable
private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boolean) -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing18),
.padding(
horizontal = TangemTheme.dimens.spacing18,
vertical = TangemTheme.dimens.spacing8,
),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
@ -78,14 +131,8 @@ private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boo
maxLines = 1,
style = TangemTheme.typography.body2,
)
Switch(
checked = toggle.isEnabled,
onCheckedChange = onCheckedChange,
colors = SwitchDefaults.colors(
checkedThumbColor = TangemTheme.colors.control.checked,
uncheckedThumbColor = TangemTheme.colors.control.unchecked,
),
)
TangemSwitch(onCheckedChange = onCheckedChange, checked = toggle.isEnabled)
}
}
@ -94,15 +141,23 @@ private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boo
@Composable
private fun PreviewFeatureTogglesScreen() {
TangemThemePreview {
var isCustomSetup by remember { mutableStateOf(value = true) }
FeatureTogglesScreen(
state = FeatureTogglesContentState(
topBarState = if (isCustomSetup) {
FeatureTogglesContentState.TopBarState.CustomSetup(onRecoverClick = { isCustomSetup = false })
} else {
FeatureTogglesContentState.TopBarState.ConfigSetup
},
appVersion = "5.15",
featureToggles = persistentListOf(
TesterFeatureToggle(name = "FEATURE_TOGGLE_1", isEnabled = true),
TesterFeatureToggle(name = "FEATURE_TOGGLE_2", isEnabled = false),
),
onToggleValueChange = { _, _ -> },
onBackClick = {},
onApplyChangesClick = {},
onToggleValueChange = { _, _ -> isCustomSetup = true },
onRestartAppClick = {},
),
)
}

View file

@ -11,10 +11,11 @@ import com.tangem.core.navigation.finisher.AppFinisher
import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle
import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.version.AppVersionProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -22,14 +23,14 @@ import javax.inject.Inject
* ViewModel for screen with list of feature toggles
*
* @property featureTogglesManager manager for getting information about the availability of feature toggles
* @property dispatchers coroutine dispatchers provider
* @property appVersionProvider app version provider
*
[REDACTED_AUTHOR]
*/
@HiltViewModel
internal class FeatureTogglesViewModel @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
private val dispatchers: CoroutineDispatcherProvider,
private val appVersionProvider: AppVersionProvider,
) : ViewModel() {
/** Current ui state */
@ -41,28 +42,54 @@ internal class FeatureTogglesViewModel @Inject constructor(
"Feature toggle manager must be mutable (debug build type)"
}
/** Setup navigation state property by router [router] and provides app restart method by [appRestarter] */
/** Setup navigation state property by router [router] and provides app restart method by [appFinisher] */
fun setupInteractions(router: InnerTesterRouter, appFinisher: AppFinisher) {
uiState = uiState.copy(
onBackClick = router::back,
onApplyChangesClick = appFinisher::restart,
onRestartAppClick = appFinisher::restart,
)
}
private fun initState(): FeatureTogglesContentState {
return FeatureTogglesContentState(
topBarState = getConfigSetupState(),
appVersion = appVersionProvider.versionName,
featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles(),
onToggleValueChange = ::onToggleValueChange,
onBackClick = {},
onApplyChangesClick = {},
onRestartAppClick = {},
)
}
private fun onToggleValueChange(name: String, isEnabled: Boolean) {
viewModelScope.launch(dispatchers.main) {
viewModelScope.launch {
mutableFeatureTogglesManager.changeToggle(name = name, isEnabled = isEnabled)
uiState = uiState.copy(featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles())
// delay for smoothly update animations
delay(timeMillis = 300)
uiState = uiState.copy(topBarState = getConfigSetupState())
}
}
private fun getConfigSetupState(): FeatureTogglesContentState.TopBarState {
return if (mutableFeatureTogglesManager.isMatchLocalConfig()) {
FeatureTogglesContentState.TopBarState.ConfigSetup
} else {
FeatureTogglesContentState.TopBarState.CustomSetup(
onRecoverClick = {
viewModelScope.launch {
mutableFeatureTogglesManager.recoverLocalConfig()
uiState = uiState.copy(
topBarState = getConfigSetupState(),
featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles(),
)
}
},
)
}
}

View file

@ -2,7 +2,9 @@
<resources>
<string name="tester_menu" translatable="false">Tester menu</string>
<string name="feature_toggles" translatable="false">Feature toggles</string>
<string name="apply_changes" translatable="false">Apply changes</string>
<string name="feature_toggles_custom_setup_warning_title" translatable="false">Custom setup</string>
<string name="feature_toggles_custom_setup_warning_description" translatable="false">Feature toggles differs from v%s config</string>
<string name="restart_app" translatable="false">Restart app</string>
<string name="environment_toggles" translatable="false">Environment toggles</string>
<string name="tester_actions" translatable="false">Tester actions</string>
<string name="hide_all_currencies" translatable="false">Hide all currencies</string>