Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-25 12:15:27 +04:00
parent f6a56e1657
commit 36ce94fb22
12 changed files with 318 additions and 3 deletions

View file

@ -46,7 +46,7 @@ inline fun RowContentContainer(
} }
@Composable @Composable
internal fun RowText( fun RowText(
mainText: String, mainText: String,
secondText: String, secondText: String,
accentMainText: Boolean, accentMainText: Boolean,

View file

@ -47,6 +47,9 @@ dependencies {
/** Feature Apis */ /** Feature Apis */
implementation(projects.features.tester.api) implementation(projects.features.tester.api)
/* SDK */
implementation(deps.tangem.blockchain)
/** Other modules */ /** Other modules */
implementation(projects.common.routing) implementation(projects.common.routing)
implementation(projects.libs.crypto) implementation(projects.libs.crypto)

View file

@ -2,8 +2,10 @@ package com.tangem.feature.tester.presentation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
@ -17,6 +19,8 @@ import com.tangem.feature.tester.presentation.actions.TesterActionsScreen
import com.tangem.feature.tester.presentation.actions.TesterActionsViewModel import com.tangem.feature.tester.presentation.actions.TesterActionsViewModel
import com.tangem.feature.tester.presentation.environments.ui.EnvironmentTogglesScreen import com.tangem.feature.tester.presentation.environments.ui.EnvironmentTogglesScreen
import com.tangem.feature.tester.presentation.environments.viewmodels.EnvironmentsTogglesViewModel import com.tangem.feature.tester.presentation.environments.viewmodels.EnvironmentsTogglesViewModel
import com.tangem.feature.tester.presentation.excludedblockchains.ExcludedBlockchainsScreen
import com.tangem.feature.tester.presentation.excludedblockchains.ExcludedBlockchainsViewModel
import com.tangem.feature.tester.presentation.featuretoggles.ui.FeatureTogglesScreen import com.tangem.feature.tester.presentation.featuretoggles.ui.FeatureTogglesScreen
import com.tangem.feature.tester.presentation.featuretoggles.viewmodels.FeatureTogglesViewModel import com.tangem.feature.tester.presentation.featuretoggles.viewmodels.FeatureTogglesViewModel
import com.tangem.feature.tester.presentation.menu.state.TesterMenuContentState import com.tangem.feature.tester.presentation.menu.state.TesterMenuContentState
@ -70,6 +74,9 @@ internal class TesterActivity : ComposeActivity() {
onBackClick = { appRouter.pop { finish() } }, onBackClick = { appRouter.pop { finish() } },
onFeatureTogglesClick = { innerTesterRouter.open(TesterScreen.FEATURE_TOGGLES) }, onFeatureTogglesClick = { innerTesterRouter.open(TesterScreen.FEATURE_TOGGLES) },
onEnvironmentTogglesClick = { innerTesterRouter.open(TesterScreen.ENVIRONMENTS_TOGGLES) }, onEnvironmentTogglesClick = { innerTesterRouter.open(TesterScreen.ENVIRONMENTS_TOGGLES) },
onExcludedBlockchainsClick = {
innerTesterRouter.open(TesterScreen.EXCLUDED_BLOCKCHAINS)
},
onTesterActionsClick = { innerTesterRouter.open(TesterScreen.TESTER_ACTIONS) }, onTesterActionsClick = { innerTesterRouter.open(TesterScreen.TESTER_ACTIONS) },
), ),
) )
@ -100,6 +107,15 @@ internal class TesterActivity : ComposeActivity() {
TesterActionsScreen(state = viewModel.uiState) TesterActionsScreen(state = viewModel.uiState)
} }
composable(route = TesterScreen.EXCLUDED_BLOCKCHAINS.name) {
val viewModel = hiltViewModel<ExcludedBlockchainsViewModel>().apply {
setupNavigation(innerTesterRouter, appFinisher)
}
val state by viewModel.state.collectAsStateWithLifecycle()
ExcludedBlockchainsScreen(state = state)
}
} }
} }
} }

View file

@ -0,0 +1,190 @@
package com.tangem.feature.tester.presentation.excludedblockchains
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.FabPosition
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.TangemSwitch
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.bottomFade
import com.tangem.core.ui.components.fields.SearchBar
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.components.rows.RowContentContainer
import com.tangem.core.ui.components.rows.RowText
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
import com.tangem.feature.tester.presentation.excludedblockchains.state.BlockchainUM
import com.tangem.feature.tester.presentation.excludedblockchains.state.ExcludedBlockchainsScreenUM
import com.tangem.feature.tester.presentation.excludedblockchains.state.mapper.toUiModels
import kotlinx.collections.immutable.toPersistentList
@Composable
internal fun ExcludedBlockchainsScreen(state: ExcludedBlockchainsScreenUM, modifier: Modifier = Modifier) {
Scaffold(
modifier = modifier,
containerColor = TangemTheme.colors.background.primary,
topBar = {
TangemTopAppBar(
title = resourceReference(R.string.excluded_blockchains),
startButton = TopAppBarButtonUM.Back(onBackClicked = state.popBack),
endButton = TopAppBarButtonUM(
iconRes = R.drawable.ic_refresh_24,
onIconClicked = state.onRecoverClick,
),
)
},
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
PrimaryButton(
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
text = "Restart App",
onClick = state.onRestartClick,
)
},
content = { paddingValues ->
LazyColumn(
modifier = Modifier
.bottomFade()
.padding(paddingValues)
.fillMaxSize(),
contentPadding = PaddingValues(
top = 8.dp,
bottom = 96.dp,
start = 16.dp,
end = 16.dp,
),
) {
item {
SearchBar(
modifier = Modifier.padding(bottom = 12.dp),
state = state.search,
)
}
item {
TogglesNotification(
modifier = Modifier.padding(vertical = 12.dp),
appVersion = state.appVersion,
isVisible = state.showRecoverWarning,
)
}
items(
items = state.blockchains,
key = BlockchainUM::id,
) { item ->
BlockchainItem(
model = item,
)
}
}
},
)
}
@Composable
private fun TogglesNotification(appVersion: String, isVisible: Boolean, modifier: Modifier = Modifier) {
if (isVisible) {
Notification(
modifier = modifier,
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),
),
)
}
}
@Composable
private fun BlockchainItem(model: BlockchainUM, modifier: Modifier = Modifier) {
RowContentContainer(
modifier = modifier
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size52),
icon = {
Image(
modifier = Modifier
.align(Alignment.Center)
.size(TangemTheme.dimens.size22),
painter = painterResource(id = model.iconResId),
contentDescription = null,
)
},
text = {
RowText(
mainText = model.name,
secondText = model.symbol,
accentMainText = false,
accentSecondText = false,
)
},
action = {
TangemSwitch(
checked = model.isExcluded,
onCheckedChange = model.onExcludedStateChange,
)
},
)
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_ExcludedBlockchainsScreen(
@PreviewParameter(ExcludedBlockchainsScreenPreviewProvider::class) params: ExcludedBlockchainsScreenUM,
) {
TangemThemePreview {
ExcludedBlockchainsScreen(params)
}
}
private class ExcludedBlockchainsScreenPreviewProvider : PreviewParameterProvider<ExcludedBlockchainsScreenUM> {
override val values: Sequence<ExcludedBlockchainsScreenUM>
get() = sequenceOf(
ExcludedBlockchainsScreenUM(
blockchains = Blockchain.entries.toUiModels(
excludedBlockchainsIds = setOf(Blockchain.Ethereum.id, Blockchain.Solana.id),
onExcludedStateChange = { _, _ -> },
).toPersistentList(),
showRecoverWarning = true,
appVersion = "1.0.0",
search = SearchBarUM(
placeholderText = resourceReference(R.string.excluded_blockchains_search_placeholder),
query = "",
isActive = false,
onQueryChange = {},
onActiveChange = {},
),
popBack = {},
onRecoverClick = {},
onRestartClick = {},
),
)
}
// endregion Preview

View file

@ -0,0 +1,37 @@
package com.tangem.feature.tester.presentation.excludedblockchains
import androidx.lifecycle.ViewModel
import com.tangem.core.toggle.blockchain.MutableExcludedBlockchainsManager
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.feature.tester.presentation.excludedblockchains.state.ExcludedBlockchainsScreenUM
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
@HiltViewModel
internal class ExcludedBlockchainsViewModel @Inject constructor(
private val excludedBlockchainsManager: MutableExcludedBlockchainsManager,
) : ViewModel() {
val uiState: MutableStateFlow<ExcludedBlockchainsScreenUM> = MutableStateFlow(
value = ExcludedBlockchainsScreenUM(
popBack = {},
search = SearchBarUM(
placeholderText = stringReference(value = "Filter by name or symbol"),
query = "",
isActive = false,
onQueryChange = {},
onActiveChange = {},
),
blockchains = persistentListOf(),
onRestartClick = {},
onRestoreClick = {},
),
)
fun setupNavigation(router: InnerTesterRouter) {
}
}

View file

@ -0,0 +1,10 @@
package com.tangem.feature.tester.presentation.excludedblockchains.state
internal data class BlockchainUM(
val id: String,
val name: String,
val symbol: String,
val iconResId: Int,
val isExcluded: Boolean,
val onExcludedStateChange: (isExcluded: Boolean) -> Unit,
)

View file

@ -0,0 +1,14 @@
package com.tangem.feature.tester.presentation.excludedblockchains.state
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import kotlinx.collections.immutable.PersistentList
internal data class ExcludedBlockchainsScreenUM(
val popBack: () -> Unit,
val search: SearchBarUM,
val blockchains: PersistentList<BlockchainUM>,
val showRecoverWarning: Boolean,
val appVersion: String,
val onRecoverClick: () -> Unit,
val onRestartClick: () -> Unit,
)

View file

@ -0,0 +1,31 @@
package com.tangem.feature.tester.presentation.excludedblockchains.state.mapper
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.feature.tester.presentation.excludedblockchains.state.BlockchainUM
internal fun List<Blockchain>.toUiModels(
excludedBlockchainsIds: Set<String>,
onExcludedStateChange: (Blockchain, Boolean) -> Unit,
): List<BlockchainUM> = mapNotNull { blockchain ->
blockchain
.takeUnless { it == Blockchain.Unknown || it.isTestnet() }
?.toUiModel(
isExcluded = blockchain.id in excludedBlockchainsIds,
onExcludedStateChange = { isExcluded ->
onExcludedStateChange(blockchain, isExcluded)
},
)
}
.sortedBy(BlockchainUM::name)
.sortedByDescending(BlockchainUM::isExcluded)
private fun Blockchain.toUiModel(isExcluded: Boolean, onExcludedStateChange: (Boolean) -> Unit): BlockchainUM =
BlockchainUM(
id = id,
name = name,
symbol = currency,
iconResId = getActiveIconRes(id),
isExcluded = isExcluded,
onExcludedStateChange = onExcludedStateChange,
)

View file

@ -12,5 +12,6 @@ data class TesterMenuContentState(
val onBackClick: () -> Unit, val onBackClick: () -> Unit,
val onFeatureTogglesClick: () -> Unit, val onFeatureTogglesClick: () -> Unit,
val onEnvironmentTogglesClick: () -> Unit, val onEnvironmentTogglesClick: () -> Unit,
val onExcludedBlockchainsClick: () -> Unit,
val onTesterActionsClick: () -> Unit, val onTesterActionsClick: () -> Unit,
) )

View file

@ -51,6 +51,12 @@ internal fun TesterMenuScreen(state: TesterMenuContentState) {
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.excluded_blockchains),
onClick = state.onExcludedBlockchainsClick,
)
PrimaryButton( PrimaryButton(
text = stringResource(R.string.environment_toggles), text = stringResource(R.string.environment_toggles),
onClick = state.onEnvironmentTogglesClick, onClick = state.onEnvironmentTogglesClick,
@ -76,6 +82,7 @@ private fun PreviewTesterMenuScreen() {
onBackClick = {}, onBackClick = {},
onFeatureTogglesClick = {}, onFeatureTogglesClick = {},
onEnvironmentTogglesClick = {}, onEnvironmentTogglesClick = {},
onExcludedBlockchainsClick = {},
onTesterActionsClick = {}, onTesterActionsClick = {},
), ),
) )

View file

@ -6,5 +6,9 @@ package com.tangem.feature.tester.presentation.navigation
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
internal enum class TesterScreen { internal enum class TesterScreen {
MENU, FEATURE_TOGGLES, ENVIRONMENTS_TOGGLES, TESTER_ACTIONS MENU,
FEATURE_TOGGLES,
ENVIRONMENTS_TOGGLES,
TESTER_ACTIONS,
EXCLUDED_BLOCKCHAINS,
} }

View file

@ -3,10 +3,12 @@
<string name="tester_menu" translatable="false">Tester menu</string> <string name="tester_menu" translatable="false">Tester menu</string>
<string name="feature_toggles" translatable="false">Feature toggles</string> <string name="feature_toggles" translatable="false">Feature toggles</string>
<string name="feature_toggles_custom_setup_warning_title" translatable="false">Custom setup</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="feature_toggles_custom_setup_warning_description" translatable="false">Toggles differs from v%s config</string>
<string name="restart_app" translatable="false">Restart app</string> <string name="restart_app" translatable="false">Restart app</string>
<string name="environment_toggles" translatable="false">Environment toggles</string> <string name="environment_toggles" translatable="false">Environment toggles</string>
<string name="tester_actions" translatable="false">Tester actions</string> <string name="tester_actions" translatable="false">Tester actions</string>
<string name="hide_all_currencies" translatable="false">Hide all currencies</string> <string name="hide_all_currencies" translatable="false">Hide all currencies</string>
<string name="toggle_app_theme" translatable="false">Toggle app theme - %s</string> <string name="toggle_app_theme" translatable="false">Toggle app theme - %s</string>
<string name="excluded_blockchains" translatable="false">Excluded blockchains</string>
<string name="excluded_blockchains_search_placeholder" translatable="false">Filter by name or symbol</string>
</resources> </resources>