Updated on 2026-08-14
This commit is contained in:
parent
332927dc9b
commit
16fb5487ee
4 changed files with 153 additions and 54 deletions
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.tester.presentation.featuretoggles.state
|
||||
|
||||
import com.tangem.core.ui.ds2.search.TangemSearch
|
||||
import com.tangem.feature.tester.presentation.common.components.appbar.TopBarWithRefreshUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
@ -8,6 +9,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
*
|
||||
* @property topBar top bar state
|
||||
* @property appVersion app version
|
||||
* @property searchBar search bar state used to filter [featureToggleGroups] by toggle name
|
||||
* @property featureToggleGroups feature toggles grouped by release status
|
||||
* @property onToggleValueChange the lambda to be invoked when switch button is pressed
|
||||
* @property onRestartAppClick the lambda to be invoked when restart app button is pressed
|
||||
|
|
@ -15,6 +17,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
internal data class FeatureTogglesScreenUM(
|
||||
val topBar: TopBarWithRefreshUM,
|
||||
val appVersion: String,
|
||||
val searchBar: TangemSearch.State,
|
||||
val featureToggleGroups: ImmutableList<FeatureToggleGroupUM>,
|
||||
val onToggleValueChange: (String, Boolean) -> Unit,
|
||||
val onRestartAppClick: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
|
|
@ -17,7 +18,9 @@ import androidx.compose.ui.util.fastForEachIndexed
|
|||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.components.divider.DividerWithPadding
|
||||
import com.tangem.core.ui.ds2.search.TangemSearch
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -34,6 +37,8 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
private val RESTART_BUTTON_OVERLAY_HEIGHT = 80.dp
|
||||
|
||||
/**
|
||||
* Screen with feature toggles list
|
||||
*
|
||||
|
|
@ -42,69 +47,117 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun FeatureTogglesScreen(state: FeatureTogglesScreenUM) {
|
||||
LazyColumn(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
) {
|
||||
stickyHeader { TopBarWithRefresh(state = state.topBar) }
|
||||
|
||||
if (state.topBar.refreshButton.isVisible) {
|
||||
item(key = "warning_notification", contentType = "warning_notification") {
|
||||
CustomSetupNotification(
|
||||
subtitle = resourceReference(
|
||||
id = R.string.feature_toggles_custom_setup_warning_description,
|
||||
wrappedList(state.appVersion),
|
||||
),
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
item(key = "warning_notification", contentType = "warning_notification") {
|
||||
InitialSetupNotification(
|
||||
subtitle = resourceReference(
|
||||
id = R.string.feature_toggles_initial_setup_warning_description,
|
||||
wrappedList(state.appVersion),
|
||||
),
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
state.featureToggleGroups.fastForEachIndexed { index, group ->
|
||||
if (index != 0) {
|
||||
item(key = "divider_${group.status.name}", contentType = "group_divider") {
|
||||
DividerWithPadding(horizontal = 16.dp, vertical = 8.dp)
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = RESTART_BUTTON_OVERLAY_HEIGHT),
|
||||
) {
|
||||
stickyHeader {
|
||||
Column(modifier = Modifier.background(TangemTheme.colors.background.secondary)) {
|
||||
TopBarWithRefresh(state = state.topBar)
|
||||
TangemSearch(
|
||||
state = state.searchBar,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item(key = "header_${group.status.name}", contentType = "group_header") {
|
||||
GroupHeader(title = "${group.status.emoji} ${group.status.title}")
|
||||
}
|
||||
customSetup(state.topBar, state.appVersion)
|
||||
|
||||
items(
|
||||
items = group.toggles,
|
||||
key = TesterFeatureToggleUM::name,
|
||||
contentType = { "feature_toggle_item" },
|
||||
) { featureToggle ->
|
||||
FeatureToggleItem(
|
||||
toggle = featureToggle,
|
||||
onCheckedChange = { isChange -> state.onToggleValueChange(featureToggle.name, isChange) },
|
||||
)
|
||||
}
|
||||
emptySearch(state.featureToggleGroups, state.searchBar)
|
||||
|
||||
featureTogglesList(state.featureToggleGroups, state.onToggleValueChange)
|
||||
}
|
||||
|
||||
item {
|
||||
PrimaryButton(
|
||||
text = stringResourceSafe(id = R.string.restart_app),
|
||||
onClick = state.onRestartAppClick,
|
||||
RestartButton(
|
||||
onRestartAppClick = state.onRestartAppClick,
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.customSetup(topBar: TopBarWithRefreshUM, appVersion: String) {
|
||||
if (topBar.refreshButton.isVisible) {
|
||||
item(key = "warning_notification", contentType = "warning_notification") {
|
||||
CustomSetupNotification(
|
||||
subtitle = resourceReference(
|
||||
id = R.string.feature_toggles_custom_setup_warning_description,
|
||||
wrappedList(appVersion),
|
||||
),
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
item(key = "warning_notification", contentType = "warning_notification") {
|
||||
InitialSetupNotification(
|
||||
subtitle = resourceReference(
|
||||
id = R.string.feature_toggles_initial_setup_warning_description,
|
||||
wrappedList(appVersion),
|
||||
),
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.emptySearch(featureToggleGroups: List<FeatureToggleGroupUM>, searchBar: TangemSearch.State) {
|
||||
if (featureToggleGroups.isEmpty() && searchBar.query.isNotBlank()) {
|
||||
item(key = "nothing_found", contentType = "nothing_found") {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.feature_toggles_no_feature_toggles_found),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RestartButton(onRestartAppClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
PrimaryButton(
|
||||
text = stringResourceSafe(id = R.string.restart_app),
|
||||
onClick = onRestartAppClick,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
|
||||
private fun LazyListScope.featureTogglesList(
|
||||
featureToggleGroups: List<FeatureToggleGroupUM>,
|
||||
onToggleValueChange: (String, Boolean) -> Unit,
|
||||
) {
|
||||
featureToggleGroups.fastForEachIndexed { index, group ->
|
||||
if (index != 0) {
|
||||
item(key = "divider_${group.status.name}", contentType = "group_divider") {
|
||||
DividerWithPadding(horizontal = 16.dp, vertical = 8.dp)
|
||||
}
|
||||
}
|
||||
|
||||
item(key = "header_${group.status.name}", contentType = "group_header") {
|
||||
GroupHeader(title = "${group.status.emoji} ${group.status.title}")
|
||||
}
|
||||
|
||||
items(
|
||||
items = group.toggles,
|
||||
key = TesterFeatureToggleUM::name,
|
||||
contentType = { "feature_toggle_item" },
|
||||
) { featureToggle ->
|
||||
FeatureToggleItem(
|
||||
toggle = featureToggle,
|
||||
onCheckedChange = { isChange -> onToggleValueChange(featureToggle.name, isChange) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -189,6 +242,13 @@ private fun PreviewFeatureTogglesScreen() {
|
|||
),
|
||||
),
|
||||
appVersion = "5.38",
|
||||
searchBar = TangemSearch.State(
|
||||
placeholderText = stringReference(value = "Search"),
|
||||
query = "",
|
||||
onQueryChange = {},
|
||||
isActive = false,
|
||||
onActiveChange = {},
|
||||
),
|
||||
featureToggleGroups = previewFeatureToggleGroups(),
|
||||
onToggleValueChange = { _, _ -> isCustomSetup = true },
|
||||
onRestartAppClick = {},
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
|||
import com.tangem.core.configtoggle.feature.MutableFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.version.Version
|
||||
import com.tangem.core.navigation.finisher.AppFinisher
|
||||
import com.tangem.core.ui.ds2.search.TangemSearch
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.feature.tester.impl.R
|
||||
import com.tangem.feature.tester.presentation.common.components.appbar.TopBarWithRefreshUM
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureToggleGroupUM
|
||||
|
|
@ -65,12 +67,42 @@ internal class FeatureTogglesViewModel @Inject constructor(
|
|||
return FeatureTogglesScreenUM(
|
||||
topBar = getConfigSetupState(isPrimarySetup = true),
|
||||
appVersion = appInfoProvider.appVersion,
|
||||
featureToggleGroups = mutableFeatureTogglesManager.getTesterFeatureToggleGroups(),
|
||||
searchBar = initSearchBar(),
|
||||
featureToggleGroups = mutableFeatureTogglesManager.getTesterFeatureToggleGroups(query = ""),
|
||||
onToggleValueChange = ::onToggleValueChange,
|
||||
onRestartAppClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
private fun initSearchBar(): TangemSearch.State {
|
||||
return TangemSearch.State(
|
||||
placeholderText = resourceReference(com.tangem.core.ui.R.string.common_search),
|
||||
query = "",
|
||||
onQueryChange = ::onQueryChange,
|
||||
isActive = false,
|
||||
onActiveChange = ::onActiveChange,
|
||||
onClearClick = ::onClearQuery,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onQueryChange(query: String) {
|
||||
val groups = mutableFeatureTogglesManager.getTesterFeatureToggleGroups(query = query)
|
||||
state.update { screenUM ->
|
||||
screenUM.copy(
|
||||
searchBar = screenUM.searchBar.copy(query = query),
|
||||
featureToggleGroups = groups,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onActiveChange(isActive: Boolean) {
|
||||
state.update { it.copy(searchBar = it.searchBar.copy(isActive = isActive)) }
|
||||
}
|
||||
|
||||
private fun onClearQuery() {
|
||||
onQueryChange(query = "")
|
||||
}
|
||||
|
||||
private fun getConfigSetupState(isPrimarySetup: Boolean): TopBarWithRefreshUM {
|
||||
val isMatchLocalConfig = mutableFeatureTogglesManager.isMatchLocalConfig()
|
||||
|
||||
|
|
@ -95,7 +127,7 @@ internal class FeatureTogglesViewModel @Inject constructor(
|
|||
viewModelScope.launch {
|
||||
mutableFeatureTogglesManager.changeToggle(name = name, isEnabled = isEnabled)
|
||||
|
||||
val groups = mutableFeatureTogglesManager.getTesterFeatureToggleGroups()
|
||||
val groups = mutableFeatureTogglesManager.getTesterFeatureToggleGroups(query = state.value.searchBar.query)
|
||||
state.update { it.copy(featureToggleGroups = groups) }
|
||||
|
||||
// delay for smoothly update animations
|
||||
|
|
@ -111,13 +143,16 @@ internal class FeatureTogglesViewModel @Inject constructor(
|
|||
mutableFeatureTogglesManager.recoverLocalConfig()
|
||||
|
||||
val topBar = getConfigSetupState(isPrimarySetup = false)
|
||||
val groups = mutableFeatureTogglesManager.getTesterFeatureToggleGroups()
|
||||
val groups = mutableFeatureTogglesManager.getTesterFeatureToggleGroups(query = state.value.searchBar.query)
|
||||
state.update { it.copy(topBar = topBar, featureToggleGroups = groups) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableFeatureTogglesManager.getTesterFeatureToggleGroups(): ImmutableList<FeatureToggleGroupUM> {
|
||||
private fun MutableFeatureTogglesManager.getTesterFeatureToggleGroups(
|
||||
query: String,
|
||||
): ImmutableList<FeatureToggleGroupUM> {
|
||||
val togglesByStatus = getFeatureToggles()
|
||||
.filter { it.name.contains(query, ignoreCase = true) }
|
||||
.sortedWith(featureToggleComparator)
|
||||
.map { info ->
|
||||
TesterFeatureToggleUM(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<string name="initial_setup_warning_title" translatable="false">Initial setup</string>
|
||||
<string name="feature_toggles_custom_setup_warning_description" translatable="false">Toggles differs from v%s config</string>
|
||||
<string name="feature_toggles_initial_setup_warning_description" translatable="false">Toggles matches v%s config</string>
|
||||
<string name="feature_toggles_no_feature_toggles_found" translatable="false">Cannot find any feature toggle by this query</string>
|
||||
<string name="blockchain_providers_custom_setup_warning_description" translatable="false">Providers differs from release config</string>
|
||||
<string name="restart_app" translatable="false">Restart app</string>
|
||||
<string name="environment_toggles" translatable="false">Environment toggles</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue