Updated on 2026-08-14
This commit is contained in:
parent
ec23b55dbd
commit
a2bc98f8a8
7 changed files with 171 additions and 40 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue