Updated on 2026-08-14

This commit is contained in:
Tangem 2023-07-24 22:35:58 +08:00
parent e3c0b0b311
commit fcdfe622f9
27 changed files with 384 additions and 54 deletions

View file

@ -37,20 +37,21 @@ dependencies {
kapt(deps.hilt.kapt)
/** Core modules */
implementation(project(":core:featuretoggles"))
implementation(project(":core:navigation"))
implementation(project(":core:ui"))
implementation(projects.core.featuretoggles)
implementation(projects.core.navigation)
implementation(projects.core.ui)
implementation(projects.core.utils)
/** Feature Apis */
implementation(project(":features:wallet:api"))
/** Domain modules */
implementation(project(":common"))
implementation(projects.common)
implementation(projects.domain.card)
implementation(project(":domain:legacy"))
implementation(project(":domain:models"))
implementation(project(":domain:wallets"))
implementation(project(":domain:wallets:models"))
implementation(projects.domain.legacy)
implementation(projects.domain.models)
implementation(projects.domain.settings)
implementation(projects.domain.tokens)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
/** Feature Apis */
implementation(projects.features.wallet.api)
}

View file

@ -15,7 +15,6 @@ import androidx.navigation.compose.rememberNavController
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.NavigationStateHolder
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.WalletFragment
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
@ -35,29 +34,27 @@ internal class DefaultWalletRouter(private val navigationStateHolder: Navigation
override fun Initialize(fragmentManager: FragmentManager) {
this.fragmentManager = fragmentManager
TangemTheme {
NavHost(
navController = rememberNavController().apply { navController = this },
startDestination = WalletScreens.WALLET.name,
) {
composable(WalletScreens.WALLET.name) {
val viewModel = hiltViewModel<WalletViewModel>().apply { router = this@DefaultWalletRouter }
LocalLifecycleOwner.current.lifecycle.addObserver(observer = viewModel)
NavHost(
navController = rememberNavController().apply { navController = this },
startDestination = WalletScreens.WALLET.name,
) {
composable(WalletScreens.WALLET.name) {
val viewModel = hiltViewModel<WalletViewModel>().apply { router = this@DefaultWalletRouter }
LocalLifecycleOwner.current.lifecycle.addObserver(observer = viewModel)
WalletScreen(state = viewModel.uiState)
}
WalletScreen(state = viewModel.uiState)
}
composable(WalletScreens.ORGANIZE_TOKENS.name) {
BackHandler(onBack = ::popBackStack)
composable(WalletScreens.ORGANIZE_TOKENS.name) {
BackHandler(onBack = ::popBackStack)
val viewModel: OrganizeTokensViewModel = hiltViewModel<OrganizeTokensViewModel>()
.apply { router = this@DefaultWalletRouter }
val viewModel: OrganizeTokensViewModel = hiltViewModel<OrganizeTokensViewModel>()
.apply { router = this@DefaultWalletRouter }
OrganizeTokensScreen(
modifier = Modifier.systemBarsPadding(),
state = viewModel.uiState,
)
}
OrganizeTokensScreen(
modifier = Modifier.systemBarsPadding(),
state = viewModel.uiState,
)
}
}
}

View file

@ -63,12 +63,29 @@ sealed class WalletNotification(open val state: NotificationState) {
),
)
/**
* "Remaining signatures left" notification
*
* @param count number of remaining signatures
*/
class RemainingSignaturesLeft(count: Int) : WalletNotification(
state = NotificationState.Simple(
title = TextReference.Res(id = R.string.common_warning),
subtitle = TextReference.Res(
id = R.string.warning_low_signatures_format,
formatArgs = WrappedList(data = listOf(count)),
),
iconResId = R.drawable.ic_alert_circle_24,
tint = TangemColorPalette.Amaranth,
),
)
/**
* "Wallet already signed hashes" notification
*
* @property onClick lambda be invoked when notification is clicked
*/
data class WalletAlreadySignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification(
data class AlreadyToppedUpAndSignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification(
state = NotificationState.Clickable(
title = TextReference.Res(id = R.string.common_warning),
subtitle = TextReference.Res(id = R.string.alert_card_signed_transactions),
@ -83,7 +100,7 @@ sealed class WalletNotification(open val state: NotificationState) {
*
* @property onClick lambda be invoked when notification is clicked
*/
data class MultiWalletAlreadySignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification(
data class SignedTransactionsInThePast(override val onClick: () -> Unit) : Clickable, WalletNotification(
state = NotificationState.Clickable(
title = TextReference.Res(
id = R.string.warning_important_security_info,

View file

@ -37,7 +37,7 @@ internal class WalletStateFactory(
walletsListConfig = createWalletsListConfig(wallets),
pullToRefreshConfig = createPullToRefreshConfig(),
contentItems = persistentListOf(),
notifications = persistentListOf(), // TODO: create notifications
notifications = persistentListOf(),
bottomSheet = WalletBottomSheetConfig(
// TODO: check notifications
isShow = false,
@ -58,7 +58,7 @@ internal class WalletStateFactory(
walletsListConfig = createWalletsListConfig(wallets),
pullToRefreshConfig = createPullToRefreshConfig(),
contentItems = persistentListOf(),
notifications = persistentListOf(), // TODO: create notifications
notifications = persistentListOf(),
bottomSheet = WalletBottomSheetConfig(
// TODO: check notifications
isShow = false,

View file

@ -10,6 +10,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.PullRefreshState
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.*
@ -40,6 +41,7 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TransactionsBlockGroupTitle
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TransactionsBlockTitle
import com.tangem.feature.wallet.presentation.wallet.ui.decorations.walletContentItemDecoration
import com.tangem.feature.wallet.presentation.wallet.ui.utils.ScrollOffsetCollector
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
/**
@ -54,13 +56,13 @@ import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimat
@Composable
internal fun WalletScreen(state: WalletStateHolder) {
BackHandler(onBack = state.onBackClick)
val walletsListState = rememberLazyListState()
Scaffold(
topBar = { WalletTopBar(config = state.topBarConfig) },
containerColor = TangemTheme.colors.background.secondary,
) { scaffoldPaddings ->
val walletsListState = rememberLazyListState()
val changeableItemModifier = Modifier.changeWalletAnimator(walletsListState)
val pullRefreshState = rememberPullRefreshState(
refreshing = state.pullToRefreshConfig.isRefreshing,
@ -153,8 +155,8 @@ internal fun WalletScreen(state: WalletStateHolder) {
}
}
PullRefreshIndicator(
refreshing = state.pullToRefreshConfig.isRefreshing,
PullToRefreshIndicator(
isRefreshing = state.pullToRefreshConfig.isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
)
@ -164,6 +166,21 @@ internal fun WalletScreen(state: WalletStateHolder) {
state.bottomSheet?.let { bottomSheetConfig ->
if (bottomSheetConfig.isShow) WalletBottomSheet(config = bottomSheetConfig)
}
LaunchedEffect(key1 = walletsListState, key2 = state.walletsListConfig.onWalletChange) {
snapshotFlow { walletsListState.layoutInfo.visibleItemsInfo }
.collect(collector = ScrollOffsetCollector(callback = state.walletsListConfig.onWalletChange))
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun PullToRefreshIndicator(isRefreshing: Boolean, state: PullRefreshState, modifier: Modifier = Modifier) {
PullRefreshIndicator(
refreshing = isRefreshing,
state = state,
modifier = modifier,
)
}
@Composable

View file

@ -11,8 +11,6 @@ import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Preview
@ -20,7 +18,6 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.WalletsListConfig
import com.tangem.feature.wallet.presentation.wallet.ui.utils.ScrollOffsetCollector
/**
* Wallets list component
@ -47,11 +44,6 @@ internal fun WalletsList(config: WalletsListConfig, lazyListState: LazyListState
WalletCard(state = state, modifier = Modifier.width(itemWidth))
}
}
LaunchedEffect(lazyListState) {
snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo }
.collect(collector = ScrollOffsetCollector(callback = config.onWalletChange))
}
}
@Preview

View file

@ -0,0 +1,104 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import com.tangem.domain.common.CardTypesResolver
import com.tangem.feature.wallet.presentation.wallet.state.WalletNotification
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
/**
[REDACTED_AUTHOR]
*/
@Suppress("LongParameterList")
internal class WalletNotificationsListFactory(
private val wasCardScannedCallback: suspend (String) -> Boolean,
private val isUserAlreadyRateAppCallback: suspend () -> Boolean,
private val onWalletAlreadySignedHashesClick: () -> Unit,
private val onMultiWalletAlreadySignedHashesClick: () -> Unit,
private val onBackupCardClick: () -> Unit,
private val hasUnreachableNetworksCallback: () -> Boolean,
private val onLikeTangemAppClick: () -> Unit,
) {
fun create(cardTypesResolver: CardTypesResolver): Flow<ImmutableList<WalletNotification>> {
// TODO: [REDACTED_JIRA] order
return flow {
emit(
buildList {
if (cardTypesResolver.isTestCard()) {
add(element = WalletNotification.TestCard)
return@buildList
}
addRemainingSignaturesLeftNotifications(cardTypesResolver)
if (!cardTypesResolver.isReleaseFirmwareType()) {
add(element = WalletNotification.DevCard)
} else {
addReleaseSpecialNotifications(cardTypesResolver)
}
// TODO: [REDACTED_JIRA] - DemoModeDatasource
if (hasUnreachableNetworksCallback()) {
add(element = WalletNotification.UnreachableNetworks)
}
if (!cardTypesResolver.isBackupForbidden() && !cardTypesResolver.hasBackup()) {
add(element = WalletNotification.BackupCard(onClick = onBackupCardClick))
}
/* TODO: [REDACTED_JIRA]
* List<WalletStoreModel>
* .filter { store ->
* store.walletsData.any { it.status is WalletDataModel.MissedDerivation }
* }
*/
if (isUserAlreadyRateAppCallback()) {
add(element = WalletNotification.LikeTangemApp(onClick = onLikeTangemAppClick))
}
}.toImmutableList(),
)
}
}
private fun MutableList<WalletNotification>.addRemainingSignaturesLeftNotifications(
cardTypesResolver: CardTypesResolver,
) {
val remainingSignatures = cardTypesResolver.getRemainingSignatures()
if (remainingSignatures != null && remainingSignatures <= MAX_REMAINING_SIGNATURES_COUNT) {
add(element = WalletNotification.RemainingSignaturesLeft(remainingSignatures))
}
}
private suspend fun MutableList<WalletNotification>.addReleaseSpecialNotifications(
cardTypesResolver: CardTypesResolver,
) {
// TODO: [REDACTED_JIRA] !isDemo
if (!wasCardScannedCallback(cardTypesResolver.getCardId()) && cardTypesResolver.isMultiwalletAllowed()) {
if (cardTypesResolver.isBackupForbidden() && cardTypesResolver.hasWalletSignedHashes()) {
add(
element = WalletNotification.SignedTransactionsInThePast(
onClick = onMultiWalletAlreadySignedHashesClick,
),
)
} else if (cardTypesResolver.hasWalletSignedHashes()) {
add(
element = WalletNotification.AlreadyToppedUpAndSignedHashes(
onClick = onWalletAlreadySignedHashesClick,
),
)
}
}
if (cardTypesResolver.isAttestationFailed()) {
add(element = WalletNotification.CardVerificationFailed)
}
}
private companion object {
const val MAX_REMAINING_SIGNATURES_COUNT = 10
}
}