Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-07 15:08:26 +03:00
commit 2a5e3fa7eb
220 changed files with 5932 additions and 1815 deletions

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.defaultComponentContext
import com.tangem.core.decompose.context.DefaultAppComponentContext
@ -10,7 +11,7 @@ import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.features.markets.MarketsFeatureToggles
import com.tangem.features.markets.component.MarketsListComponent
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.AndroidEntryPoint
@ -32,7 +33,7 @@ internal class WalletFragment : ComposeFragment() {
internal lateinit var walletRouter: WalletRouter
@Inject
internal lateinit var marketsListComponentFactory: MarketsListComponent.Factory
internal lateinit var marketsEntryComponentFactory: MarketsEntryComponent.Factory
@Inject
internal lateinit var coroutineDispatcherProvider: CoroutineDispatcherProvider
@ -43,7 +44,7 @@ internal class WalletFragment : ComposeFragment() {
@Inject
internal lateinit var marketsFeatureToggles: MarketsFeatureToggles
private var marketsListComponent: MarketsListComponent? = null
private var marketsEntryComponent: MarketsEntryComponent? = null
private val _walletRouter: InnerWalletRouter
get() = requireNotNull(walletRouter as? InnerWalletRouter) {
@ -61,15 +62,19 @@ internal class WalletFragment : ComposeFragment() {
hiltComponentBuilder = componentBuilder,
)
marketsListComponent = marketsListComponentFactory.create(appContext)
marketsEntryComponent = marketsEntryComponentFactory.create(appContext)
}
}
@Composable
override fun ScreenContent(modifier: Modifier) {
_walletRouter.Initialize(
onFinish = requireActivity()::finish,
marketsListComponent = marketsListComponent,
onFinish = remember(requireActivity()) {
{
requireActivity().finish()
}
},
marketsEntryComponent = marketsEntryComponent,
)
}

View file

@ -25,7 +25,7 @@ import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScree
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
import com.tangem.features.markets.component.MarketsListComponent
import com.tangem.features.markets.component.MarketsEntryComponent
import kotlin.properties.Delegates
/** Default implementation of wallet feature router */
@ -41,7 +41,7 @@ internal class DefaultWalletRouter(
override fun getEntryFragment(): Fragment = WalletFragment.create()
@Composable
override fun Initialize(onFinish: () -> Unit, marketsListComponent: MarketsListComponent?) {
override fun Initialize(onFinish: () -> Unit, marketsEntryComponent: MarketsEntryComponent?) {
this.onFinish = onFinish
NavHost(
@ -56,7 +56,7 @@ internal class DefaultWalletRouter(
WalletScreen(
state = viewModel.uiState.collectAsStateWithLifecycle().value,
marketsListComponent = marketsListComponent,
marketsEntryComponent = marketsEntryComponent,
)
}
@ -140,7 +140,7 @@ internal class DefaultWalletRouter(
}
override fun openManageTokensScreen() {
router.push(AppRoute.ManageTokens)
router.push(AppRoute.ManageTokens(readOnlyContent = false))
}
override fun openScanFailedDialog(onTryAgain: () -> Unit) {

View file

@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.markets.component.MarketsListComponent
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.wallet.navigation.WalletRouter
/**
@ -24,7 +24,7 @@ internal interface InnerWalletRouter : WalletRouter {
* @param onFinish finish activity callback
*/
@Composable
fun Initialize(onFinish: () -> Unit, marketsListComponent: MarketsListComponent?)
fun Initialize(onFinish: () -> Unit, marketsEntryComponent: MarketsEntryComponent?)
/** Pop back stack */
fun popBackStack()

View file

@ -1,8 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.event.StateEvent
import kotlinx.collections.immutable.ImmutableList
@Immutable
internal data class WalletScreenState(
val onBackClick: () -> Unit,
val topBarConfig: WalletTopBarConfig,

View file

@ -49,6 +49,8 @@ import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
import com.tangem.core.ui.components.snackbar.TangemSnackbar
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags
@ -70,12 +72,13 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.balances
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.depositButton
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.component.MarketsListComponent
import com.tangem.features.markets.component.BottomSheetState.*
import com.tangem.features.markets.component.MarketsEntryComponent
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
@Composable
internal fun WalletScreen(state: WalletScreenState, marketsListComponent: MarketsListComponent?) {
internal fun WalletScreen(state: WalletScreenState, marketsEntryComponent: MarketsEntryComponent?) {
BackHandler(onBack = state.onBackClick)
// It means that screen is still initializing
@ -98,7 +101,7 @@ internal fun WalletScreen(state: WalletScreenState, marketsListComponent: Market
snackbarHostState = snackbarHostState,
isAutoScroll = isAutoScroll,
onAutoScrollReset = { isAutoScroll.value = false },
marketsListComponent = marketsListComponent,
marketsEntryComponent = marketsEntryComponent,
alertConfig = alertConfig,
)
@ -119,7 +122,7 @@ private fun WalletContent(
walletsListState: LazyListState,
snackbarHostState: SnackbarHostState,
isAutoScroll: State<Boolean>,
marketsListComponent: MarketsListComponent?,
marketsEntryComponent: MarketsEntryComponent?,
alertConfig: WalletAlertState?,
onAutoScrollReset: () -> Unit,
) {
@ -216,7 +219,7 @@ private fun WalletContent(
)
}
if (marketsListComponent != null) {
if (marketsEntryComponent != null) {
val bottomSheetState = remember {
mutableStateOf(BottomSheetState.COLLAPSED)
}
@ -232,7 +235,7 @@ private fun WalletContent(
alertConfig = alertConfig,
onBottomSheetStateChange = { bottomSheetState.value = it },
bottomSheetContent = {
marketsListComponent.BottomSheetContent(
marketsEntryComponent.BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
modifier = Modifier,
@ -316,15 +319,15 @@ private fun BaseScaffold(
@Suppress("LongParameterList", "LongMethod")
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Composable
private fun BaseScaffoldWithMarkets(
private inline fun BaseScaffoldWithMarkets(
state: WalletScreenState,
selectedWallet: WalletState,
snackbarHostState: SnackbarHostState,
bottomSheetHeaderHeightProvider: () -> Dp,
bottomSheetContent: @Composable () -> Unit,
crossinline bottomSheetContent: @Composable () -> Unit,
alertConfig: WalletAlertState?,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
content: @Composable () -> Unit,
noinline onBottomSheetStateChange: (BottomSheetState) -> Unit,
crossinline content: @Composable () -> Unit,
) {
// show the bottom sheet if there is at least one multicurrency wallet
val showManageTokensBottomSheet = remember(state.wallets) {
@ -332,11 +335,13 @@ private fun BaseScaffoldWithMarkets(
}
val bottomSheetState = rememberSheetStateEnhanced(
initialValue = if (showManageTokensBottomSheet) SheetValue.PartiallyExpanded else SheetValue.Hidden,
confirmValueChange = { sheetValue ->
when {
sheetValue == SheetValue.Hidden && showManageTokensBottomSheet -> false
sheetValue != SheetValue.Hidden && !showManageTokensBottomSheet -> false
else -> true
confirmValueChange = remember(showManageTokensBottomSheet) {
{ sheetValue ->
when {
sheetValue == SheetValue.Hidden && showManageTokensBottomSheet -> false
sheetValue != SheetValue.Hidden && !showManageTokensBottomSheet -> false
else -> true
}
}
},
skipHiddenState = showManageTokensBottomSheet,
@ -344,14 +349,6 @@ private fun BaseScaffoldWithMarkets(
val keyboardShown = keyboardAsState()
BottomSheetStateEffects(
bottomSheetState = bottomSheetState,
showManageTokensBottomSheet = showManageTokensBottomSheet,
alertConfig = alertConfig,
keyboardShown = keyboardShown,
onBottomSheetStateChange = onBottomSheetStateChange,
)
val scaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = bottomSheetState,
snackbarHostState = snackbarHostState,
@ -360,77 +357,89 @@ private fun BaseScaffoldWithMarkets(
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
val peekHeight = bottomSheetHeaderHeightProvider() + handComposableComponentHeight + bottomBarHeight
val maxHeight = LocalWindowSize.current.height
val coroutineScope = rememberCoroutineScope()
val backgroundPrimary = TangemTheme.colors.background.primary
BottomSheetScaffold(
snackbarHost = {
WalletSnackbarHost(
snackbarHostState = it,
event = state.event,
modifier = Modifier
.padding(bottom = TangemTheme.dimens.spacing4)
.navigationBarsPadding(),
)
},
containerColor = TangemTheme.colors.background.secondary,
sheetContainerColor = TangemTheme.colors.background.primary,
scaffoldState = scaffoldState,
sheetPeekHeight = peekHeight,
sheetDragHandle = {
Hand(modifier = Modifier.background(color = TangemTheme.colors.background.primary))
},
sheetTonalElevation = 8.dp,
sheetShadowElevation = 8.dp,
sheetContent = {
BoxWithConstraints {
Box(
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember { mutableStateOf(backgroundPrimary) },
) {
val backgroundColor = LocalMainBottomSheetColor.current
BottomSheetStateEffects(
bottomSheetState = bottomSheetState,
showManageTokensBottomSheet = showManageTokensBottomSheet,
alertConfig = alertConfig,
keyboardShown = keyboardShown,
onBottomSheetStateChange = onBottomSheetStateChange,
)
BottomSheetScaffold(
snackbarHost = {
WalletSnackbarHost(
snackbarHostState = it,
event = state.event,
modifier = Modifier
.sizeIn(maxHeight = maxHeight - statusBarHeight)
.align(Alignment.BottomCenter),
.padding(bottom = TangemTheme.dimens.spacing4)
.navigationBarsPadding(),
)
},
containerColor = TangemTheme.colors.background.secondary,
sheetContainerColor = backgroundColor.value,
scaffoldState = scaffoldState,
sheetPeekHeight = peekHeight,
sheetDragHandle = {
Hand(modifier = Modifier.background(color = backgroundColor.value))
},
sheetTonalElevation = 8.dp,
sheetShadowElevation = 8.dp,
sheetContent = {
Box(
modifier = Modifier.sizeIn(maxHeight = maxHeight - statusBarHeight - handComposableComponentHeight),
) {
bottomSheetContent()
}
}
// hide bottom sheet when back pressed
BackHandler(
keyboardShown.value is Keyboard.Closed &&
bottomSheetState.currentValue == SheetValue.Expanded,
) {
coroutineScope.launch { bottomSheetState.partialExpand() }
}
},
content = { _ ->
val pullRefreshState = rememberPullRefreshState(
refreshing = selectedWallet.pullToRefreshConfig.isRefreshing,
onRefresh = {
selectedWallet.pullToRefreshConfig.onRefresh(WalletPullToRefreshConfig.ShowRefreshState(true))
},
)
Column {
WalletTopBar(config = state.topBarConfig)
Box(
modifier = Modifier.pullRefresh(pullRefreshState),
// hide bottom sheet when back pressed
BackHandler(
keyboardShown.value is Keyboard.Closed &&
bottomSheetState.currentValue == SheetValue.Expanded,
) {
content()
WalletPullToRefreshIndicator(
isRefreshing = selectedWallet.pullToRefreshConfig.isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
)
coroutineScope.launch { bottomSheetState.partialExpand() }
}
}
},
content = { _ ->
val pullRefreshState = rememberPullRefreshState(
refreshing = selectedWallet.pullToRefreshConfig.isRefreshing,
onRefresh = {
selectedWallet.pullToRefreshConfig.onRefresh(WalletPullToRefreshConfig.ShowRefreshState(true))
},
)
BottomSheetScrim(
color = BottomSheetDefaults.ScrimColor,
visible = bottomSheetState.targetValue == SheetValue.Expanded,
onDismissRequest = { coroutineScope.launch { bottomSheetState.partialExpand() } },
)
},
)
Column {
WalletTopBar(config = state.topBarConfig)
Box(
modifier = Modifier.pullRefresh(pullRefreshState),
) {
content()
WalletPullToRefreshIndicator(
isRefreshing = selectedWallet.pullToRefreshConfig.isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
)
}
}
BottomSheetScrim(
color = BottomSheetDefaults.ScrimColor,
visible = bottomSheetState.targetValue == SheetValue.Expanded,
onDismissRequest = { coroutineScope.launch { bottomSheetState.partialExpand() } },
)
},
)
}
}
@Composable
@ -545,9 +554,9 @@ private fun BottomSheetStateEffects(
LaunchedEffect(isSheetHidden) {
onBottomSheetStateChange(
if (isSheetHidden) {
BottomSheetState.COLLAPSED
COLLAPSED
} else {
BottomSheetState.EXPANDED
EXPANDED
},
)
}
@ -640,7 +649,7 @@ private fun WalletScreen_Preview(@PreviewParameter(WalletScreenPreviewProvider::
TangemThemePreview {
WalletScreen(
state = data,
marketsListComponent = null,
marketsEntryComponent = null,
)
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import androidx.compose.runtime.Stable
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -43,6 +44,7 @@ import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList", "LargeClass")
@Stable
@HiltViewModel
internal class WalletViewModel @Inject constructor(
private val stateHolder: WalletStateController,

View file

@ -370,6 +370,9 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId))
viewModelScope.launch {
val userWalletId = stateHolder.getSelectedWalletId()
val cryptoCurrency = cryptoCurrencyStatus.currency