Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-15 10:13:44 +03:00
parent 3a4ce4d24b
commit 76c64ef543
27 changed files with 270 additions and 238 deletions

View file

@ -37,7 +37,7 @@ import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.features.managetokens.navigation.ManageTokensRouter
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
@ -126,7 +126,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
lateinit var tokenDetailsRouter: TokenDetailsRouter
@Inject
lateinit var manageTokensRouter: ManageTokensRouter
lateinit var manageTokensUi: ManageTokensUi
@Inject
lateinit var walletConnectInteractor: WalletConnectInteractor
@ -213,7 +213,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
walletRouter = walletRouter,
walletConnectInteractor = walletConnectInteractor,
tokenDetailsRouter = tokenDetailsRouter,
manageTokensRouter = manageTokensRouter,
manageTokensUi = manageTokensUi,
cardSdkConfigRepository = cardSdkConfigRepository,
sendRouter = sendRouter,
qrScanningRouter = qrScanningRouter,

View file

@ -3,7 +3,7 @@ package com.tangem.tap.proxy.redux
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.features.managetokens.navigation.ManageTokensRouter
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
@ -19,7 +19,7 @@ sealed interface DaggerGraphAction : Action {
val walletRouter: WalletRouter,
val walletConnectInteractor: WalletConnectInteractor,
val tokenDetailsRouter: TokenDetailsRouter,
val manageTokensRouter: ManageTokensRouter,
val manageTokensUi: ManageTokensUi,
val cardSdkConfigRepository: CardSdkConfigRepository,
val sendRouter: SendRouter,
val qrScanningRouter: QrScanningRouter,

View file

@ -18,7 +18,7 @@ object DaggerGraphReducer {
walletRouter = action.walletRouter,
walletConnectInteractor = action.walletConnectInteractor,
tokenDetailsRouter = action.tokenDetailsRouter,
manageTokensRouter = action.manageTokensRouter,
manageTokensUi = action.manageTokensUi,
cardSdkConfigRepository = action.cardSdkConfigRepository,
sendRouter = action.sendRouter,
qrScanningRouter = action.qrScanningRouter,

View file

@ -15,7 +15,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.managetokens.navigation.ManageTokensRouter
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.tester.api.TesterRouter
@ -39,7 +39,7 @@ data class DaggerGraphState(
val walletConnectInteractor: WalletConnectInteractor? = null,
val tokenDetailsRouter: TokenDetailsRouter? = null,
val manageTokensFeatureToggles: ManageTokensFeatureToggles? = null,
val manageTokensRouter: ManageTokensRouter? = null,
val manageTokensUi: ManageTokensUi? = null,
val scanCardProcessor: ScanCardProcessor? = null,
val cardSdkConfigRepository: CardSdkConfigRepository? = null,
val appCurrencyRepository: AppCurrencyRepository? = null,

View file

@ -6,4 +6,6 @@ import androidx.compose.runtime.Immutable
* General interface for bottom sheet config model
*/
@Immutable
interface TangemBottomSheetConfigContent
interface TangemBottomSheetConfigContent {
object Empty : TangemBottomSheetConfigContent
}

View file

@ -9,9 +9,5 @@ android {
}
dependencies {
/** AndroidX */
implementation(deps.androidx.fragment.ktx)
implementation(deps.compose.foundation)
implementation(deps.compose.navigation)
}

View file

@ -1,12 +0,0 @@
package com.tangem.features.managetokens.navigation
import androidx.compose.ui.unit.Dp
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
interface ManageTokensRouter {
val startDestination: String
fun NavGraphBuilder.initialize(navController: NavHostController, onHeaderSizeChange: (Dp) -> Unit)
}

View file

@ -0,0 +1,10 @@
package com.tangem.features.managetokens.navigation
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
interface ManageTokensUi {
@Suppress("TopLevelComposableFunctions")
@Composable
fun Content(onHeaderSizeChange: (Dp) -> Unit)
}

View file

@ -1,21 +1,18 @@
package com.tangem.managetokens.di
import com.tangem.core.navigation.ReduxNavController
import com.tangem.features.managetokens.navigation.ManageTokensRouter
import com.tangem.managetokens.presentation.router.DefaultManageTokensRouter
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.managetokens.presentation.router.ManageTokensUiImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.scopes.ActivityScoped
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(ActivityComponent::class)
internal object ManageTokensRouterModule {
@InstallIn(SingletonComponent::class)
internal interface ManageTokensRouterModule {
@Provides
@ActivityScoped
fun provideManageTokensRouter(reduxNavController: ReduxNavController): ManageTokensRouter {
return DefaultManageTokensRouter(reduxNavController)
}
@Binds
@Singleton
fun provideManageTokensRouter(manageTokensUiImpl: ManageTokensUiImpl): ManageTokensUi
}

View file

@ -0,0 +1,14 @@
package com.tangem.managetokens.presentation.addcustomtoken.router
/**
* Add Custom Tokens screens
* @property route route string representation
*/
internal sealed class AddCustomTokenRoute(val route: String) {
object Main : AddCustomTokenRoute("$BASE_ROUTE/main")
object ChooseNetwork : AddCustomTokenRoute("$BASE_ROUTE/choose_network")
object ChooseWallet : AddCustomTokenRoute("$BASE_ROUTE/choose_wallet")
object ChooseDerivation : AddCustomTokenRoute("$BASE_ROUTE/choose_derivation")
}
private const val BASE_ROUTE = "manage_tokens/add_custom_token"

View file

@ -0,0 +1,29 @@
package com.tangem.managetokens.presentation.addcustomtoken.router
import androidx.compose.runtime.Stable
import androidx.navigation.NavController
@Stable
internal class AddCustomTokenRouter(
private val navController: NavController,
) {
/** Pop back stack */
fun popBackStack() {
navController.popBackStack()
}
/** Open custom token choose network screen */
fun openCustomTokenChooseNetwork() {
navController.navigate(AddCustomTokenRoute.ChooseNetwork.route)
}
/** Open custom token choose derivation screen */
fun openCustomTokenChooseDerivation() {
navController.navigate(AddCustomTokenRoute.ChooseDerivation.route)
}
/** Open custom token choose wallet screen */
fun openCustomTokenChooseWallet() {
navController.navigate(AddCustomTokenRoute.ChooseWallet.route)
}
}

View file

@ -0,0 +1,115 @@
package com.tangem.managetokens.presentation.addcustomtoken.ui
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.systemBars
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetDraggableHeader
import com.tangem.core.ui.components.bottomsheets.collapse
import com.tangem.core.ui.res.TangemTheme
import com.tangem.managetokens.presentation.addcustomtoken.router.AddCustomTokenRoute
import com.tangem.managetokens.presentation.addcustomtoken.router.AddCustomTokenRouter
import com.tangem.managetokens.presentation.addcustomtoken.viewmodels.AddCustomTokenViewModel
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddCustomTokenBottomSheet(config: TangemBottomSheetConfig) {
val viewModel = hiltViewModel<AddCustomTokenViewModel>()
var isVisible by remember { mutableStateOf(value = config.isShow) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
if (isVisible) {
// ViewModel cannot be scoped to ModalBottomSheet's lifecycle,
// so we have to manually initialize and dispose its state when bottom sheet enters and leaves a composition
DisposableEffect(viewModel) {
viewModel.onInitialize()
onDispose { viewModel.onDispose() }
}
// FIXME: handle back presses after updating material3 to 1.2.0
ModalBottomSheet(
onDismissRequest = config.onDismissRequest,
sheetState = sheetState,
containerColor = TangemTheme.colors.background.tertiary,
shape = TangemTheme.shapes.bottomSheetLarge,
windowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top),
dragHandle = { TangemBottomSheetDraggableHeader(color = TangemTheme.colors.background.tertiary) },
) {
Content(onDismissRequest = config.onDismissRequest, viewModel = viewModel)
}
}
LaunchedEffect(key1 = config.isShow) {
if (config.isShow) {
isVisible = true
} else {
sheetState.collapse { isVisible = false }
}
}
}
@SuppressLint("RestrictedApi")
@Composable
private fun Content(viewModel: AddCustomTokenViewModel, onDismissRequest: () -> Unit) {
val navController = rememberNavController()
LaunchedEffect(navController) {
navController.currentBackStack
.collect {
if (it.isEmpty()) {
onDismissRequest()
}
}
}
val router = remember(navController) { AddCustomTokenRouter(navController) }
viewModel.router = router
NavHost(
modifier = Modifier.fillMaxSize(),
navController = navController,
startDestination = AddCustomTokenRoute.Main.route,
) {
composable(
route = AddCustomTokenRoute.Main.route,
) {
AddCustomTokenScreen(state = viewModel.uiState)
}
composable(
route = AddCustomTokenRoute.ChooseNetwork.route,
) {
ChooseNetworkCustomScreen(state = viewModel.uiState.chooseNetworkState)
}
composable(
route = AddCustomTokenRoute.ChooseDerivation.route,
) {
ChooseDerivationScreen(state = requireNotNull(viewModel.uiState.chooseDerivationState))
}
composable(
route = AddCustomTokenRoute.ChooseWallet.route,
) {
CustomTokensChooseWalletScreen(state = viewModel.uiState.chooseWalletState as ChooseWalletState.Choose)
}
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.managetokens.presentation.addcustomtoken.viewmodels
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@ -20,6 +21,7 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
import com.tangem.managetokens.presentation.addcustomtoken.router.AddCustomTokenRouter
import com.tangem.managetokens.presentation.common.analytics.ManageTokens
import com.tangem.managetokens.presentation.common.state.AlertState
import com.tangem.managetokens.presentation.common.state.Event
@ -29,22 +31,20 @@ import com.tangem.managetokens.presentation.addcustomtoken.state.factory.AddCust
import com.tangem.managetokens.presentation.addcustomtoken.state.factory.ContractAddressToCustomTokenDataConverter
import com.tangem.managetokens.presentation.addcustomtoken.state.factory.AddCustomTokenStateFactory
import com.tangem.managetokens.presentation.addcustomtoken.state.factory.FoundTokenToCustomTokenDataConverter
import com.tangem.managetokens.presentation.router.InnerManageTokensRouter
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.Debouncer
import com.tangem.utils.coroutines.Debouncer.Companion.DEFAULT_WAIT_TIME_MS
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toPersistentSet
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import javax.inject.Inject
import kotlin.properties.Delegates
@Suppress("LongParameterList", "TooManyFunctions", "LargeClass")
@Stable
@HiltViewModel
internal class AddCustomTokenViewModel @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
@ -67,12 +67,15 @@ internal class AddCustomTokenViewModel @Inject constructor(
clickIntents = this,
)
var router: InnerManageTokensRouter by Delegates.notNull()
var router: AddCustomTokenRouter by Delegates.notNull()
var uiState: AddCustomTokenState by mutableStateOf(stateFactory.getInitialState())
private set
override fun onCreate(owner: LifecycleOwner) {
/**
* Called when the bottom sheet was opened
*/
fun onInitialize() {
viewModelScope.launch(dispatchers.io) {
getWalletsUseCase()
.distinctUntilChanged()
@ -92,6 +95,16 @@ internal class AddCustomTokenViewModel @Inject constructor(
}
}
/**
* Called after the bottom sheet is closed
*/
fun onDispose() {
// We have to manually cancel viewModelScope's child jobs when bottom sheet is closed
viewModelScope.coroutineContext.cancelChildren()
// and reset state
uiState = stateFactory.getInitialState()
}
override fun onDestroy(owner: LifecycleOwner) {
uiState = stateFactory.getInitialState()
}

View file

@ -1,6 +1,7 @@
package com.tangem.managetokens.presentation.managetokens.state
import androidx.paging.PagingData
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.event.StateEvent
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
import com.tangem.managetokens.presentation.common.state.Event
@ -15,6 +16,7 @@ internal data class ManageTokensState(
val derivationNotification: DerivationNotificationState? = null,
val selectedToken: TokenItemState.Loaded? = null,
val showChooseWalletScreen: Boolean = false,
val customTokenBottomSheetConfig: TangemBottomSheetConfig,
val event: StateEvent<Event>,
val onEmptySearchResult: (String) -> Unit,
)

View file

@ -1,6 +1,8 @@
package com.tangem.managetokens.presentation.managetokens.state.factory
import androidx.paging.PagingData
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.domain.tokens.CurrencyCompatibilityError
@ -39,6 +41,11 @@ internal class ManageTokensStateFactory(
event = consumedEvent(),
chooseWalletState = ChooseWalletState.NoSelection,
onEmptySearchResult = uiIntents::onEmptySearchResult,
customTokenBottomSheetConfig = TangemBottomSheetConfig(
isShow = false,
onDismissRequest = uiIntents::onAddCustomTokenSheetDismissed,
content = TangemBottomSheetConfigContent.Empty,
),
)
}

View file

@ -1,6 +1,8 @@
package com.tangem.managetokens.presentation.managetokens.state.previewdata
import androidx.paging.PagingData
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.event.consumedEvent
import com.tangem.managetokens.presentation.common.state.previewdata.ChooseWalletStatePreviewData
import com.tangem.managetokens.presentation.managetokens.state.AddCustomTokenButton
@ -20,6 +22,7 @@ internal object ManageTokensStatePreviewData {
event = consumedEvent(),
chooseWalletState = ChooseWalletStatePreviewData.state,
onEmptySearchResult = {},
customTokenBottomSheetConfig = TangemBottomSheetConfig(false, {}, TangemBottomSheetConfigContent.Empty),
)
val loadingState: ManageTokensState

View file

@ -23,6 +23,7 @@ import com.tangem.core.ui.components.SpacerH18
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.managetokens.presentation.addcustomtoken.ui.AddCustomTokenBottomSheet
import com.tangem.managetokens.presentation.common.state.AlertState
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
import com.tangem.managetokens.presentation.common.ui.ChooseWalletBottomSheet
@ -49,6 +50,8 @@ internal fun ManageTokensScreen(state: ManageTokensState, onHeaderSizeChange: (D
}
Content(state = state, onHeaderSizeChange = onHeaderSizeChange)
AddCustomTokenBottomSheet(state.customTokenBottomSheetConfig)
}
@Composable
@ -122,6 +125,9 @@ private fun Content(state: ManageTokensState, onHeaderSizeChange: (Dp) -> Unit)
with(density) { tokenListAlertBottomPadding = it.size.height.toDp() }
},
)
DisposableEffect(Unit) {
onDispose { tokenListAlertBottomPadding = 0.dp }
}
}
}
}

View file

@ -3,4 +3,6 @@ package com.tangem.managetokens.presentation.managetokens.viewmodels
internal interface ManageTokensUiEvents {
fun onEmptySearchResult(query: String)
fun onAddCustomTokenSheetDismissed()
}

View file

@ -3,7 +3,6 @@ package com.tangem.managetokens.presentation.managetokens.viewmodels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
@ -30,7 +29,6 @@ import com.tangem.managetokens.presentation.managetokens.state.ManageTokensState
import com.tangem.managetokens.presentation.managetokens.state.TokenButtonType
import com.tangem.managetokens.presentation.managetokens.state.TokenItemState
import com.tangem.managetokens.presentation.managetokens.state.factory.*
import com.tangem.managetokens.presentation.router.InnerManageTokensRouter
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.Debouncer
@ -62,7 +60,7 @@ internal class ManageTokensViewModel @Inject constructor(
private val checkCurrencyCompatibilityUseCase: CheckCurrencyCompatibilityUseCase,
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel(), ManageTokensClickIntents, ManageTokensUiEvents, DefaultLifecycleObserver {
) : ViewModel(), ManageTokensClickIntents, ManageTokensUiEvents {
private val debouncer = Debouncer()
@ -72,8 +70,6 @@ internal class ManageTokensViewModel @Inject constructor(
uiIntents = this,
)
var router: InnerManageTokensRouter by Delegates.notNull()
var uiState: ManageTokensState by mutableStateOf(stateFactory.getInitialState(flowOf(PagingData.from(emptyList()))))
private set
@ -190,7 +186,7 @@ internal class ManageTokensViewModel @Inject constructor(
override fun onAddCustomTokensButtonClick() {
analyticsEventHandler.send(ManageTokens.ButtonCustomToken)
router.openAddCustomTokenScreen()
uiState = uiState.copy(customTokenBottomSheetConfig = uiState.customTokenBottomSheetConfig.copy(isShow = true))
}
override fun onSearchQueryChange(query: String) {
@ -407,4 +403,8 @@ internal class ManageTokensViewModel @Inject constructor(
override fun onEmptySearchResult(query: String) {
analyticsEventHandler.send(ManageTokens.TokenIsNotFound(query))
}
override fun onAddCustomTokenSheetDismissed() {
uiState = uiState.copy(customTokenBottomSheetConfig = uiState.customTokenBottomSheetConfig.copy(isShow = false))
}
}

View file

@ -1,124 +0,0 @@
package com.tangem.managetokens.presentation.router
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.Dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import androidx.navigation.navigation
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.ReduxNavController
import com.tangem.core.ui.extensions.parentHiltViewModel
import com.tangem.managetokens.presentation.addcustomtoken.ui.AddCustomTokenScreen
import com.tangem.managetokens.presentation.addcustomtoken.ui.ChooseDerivationScreen
import com.tangem.managetokens.presentation.addcustomtoken.ui.ChooseNetworkCustomScreen
import com.tangem.managetokens.presentation.addcustomtoken.ui.CustomTokensChooseWalletScreen
import com.tangem.managetokens.presentation.addcustomtoken.viewmodels.AddCustomTokenViewModel
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
import com.tangem.managetokens.presentation.managetokens.ui.ManageTokensScreen
import com.tangem.managetokens.presentation.managetokens.viewmodels.ManageTokensViewModel
import kotlin.properties.Delegates
internal class DefaultManageTokensRouter(
private val reduxNavController: ReduxNavController,
) : InnerManageTokensRouter {
private var navController: NavHostController by Delegates.notNull()
override val startDestination: String = ManageTokensRoute.ManageTokens.route
override fun NavGraphBuilder.initialize(navController: NavHostController, onHeaderSizeChange: (Dp) -> Unit) {
this@DefaultManageTokensRouter.navController = navController
composable(ManageTokensRoute.ManageTokens.route) {
val viewModel = hiltViewModel<ManageTokensViewModel>().apply {
router = this@DefaultManageTokensRouter
}
LocalLifecycleOwner.current.lifecycle.addObserver(viewModel)
ManageTokensScreen(
state = viewModel.uiState,
onHeaderSizeChange = onHeaderSizeChange,
)
}
navigation(
route = ManageTokensRoute.AddCustomToken.route,
startDestination = ManageTokensRoute.AddCustomToken.Main.route,
) {
composable(
ManageTokensRoute.AddCustomToken.Main.route,
) { backStackEntry ->
val viewModel =
backStackEntry.parentHiltViewModel<AddCustomTokenViewModel>(navController = navController).apply {
router = this@DefaultManageTokensRouter
}
LocalLifecycleOwner.current.lifecycle.addObserver(viewModel)
AddCustomTokenScreen(state = viewModel.uiState)
}
composable(
ManageTokensRoute.AddCustomToken.ChooseNetwork.route,
) { backStackEntry ->
val viewModel =
backStackEntry.parentHiltViewModel<AddCustomTokenViewModel>(navController = navController).apply {
router = this@DefaultManageTokensRouter
}
ChooseNetworkCustomScreen(
state = viewModel.uiState.chooseNetworkState,
)
}
composable(
ManageTokensRoute.AddCustomToken.ChooseDerivation.route,
) { backStackEntry ->
val viewModel =
backStackEntry.parentHiltViewModel<AddCustomTokenViewModel>(navController = navController).apply {
router = this@DefaultManageTokensRouter
}
ChooseDerivationScreen(
state = requireNotNull(viewModel.uiState.chooseDerivationState),
)
}
composable(
ManageTokensRoute.AddCustomToken.ChooseWallet.route,
) { backStackEntry ->
val viewModel =
backStackEntry.parentHiltViewModel<AddCustomTokenViewModel>(navController = navController).apply {
router = this@DefaultManageTokensRouter
}
CustomTokensChooseWalletScreen(
state = viewModel.uiState.chooseWalletState as ChooseWalletState.Choose,
)
}
}
}
override fun popBackStack(screen: AppScreen?) {
if (screen != null) {
reduxNavController.navigate(action = NavigationAction.PopBackTo(screen))
} else {
navController.popBackStack()
}
}
override fun openManageTokensScreen() {
navController.navigate(ManageTokensRoute.ManageTokens.route)
}
override fun openAddCustomTokenScreen() {
navController.navigate(ManageTokensRoute.AddCustomToken.route)
}
override fun openCustomTokenChooseNetwork() {
navController.navigate(ManageTokensRoute.AddCustomToken.ChooseNetwork.route)
}
override fun openCustomTokenChooseDerivation() {
navController.navigate(ManageTokensRoute.AddCustomToken.ChooseDerivation.route)
}
override fun openCustomTokenChooseWallet() {
navController.navigate(ManageTokensRoute.AddCustomToken.ChooseWallet.route)
}
}

View file

@ -1,25 +0,0 @@
package com.tangem.managetokens.presentation.router
import com.tangem.core.navigation.AppScreen
import com.tangem.features.managetokens.navigation.ManageTokensRouter
internal interface InnerManageTokensRouter : ManageTokensRouter {
/** Pop back stack */
fun popBackStack(screen: AppScreen? = null)
/** Open manage tokens screen */
fun openManageTokensScreen()
/** Open add custom token screen */
fun openAddCustomTokenScreen()
/** Open custom token choose network screen */
fun openCustomTokenChooseNetwork()
/** Open custom token choose derivation screen */
fun openCustomTokenChooseDerivation()
/** Open custom token choose wallet screen */
fun openCustomTokenChooseWallet()
}

View file

@ -1,19 +0,0 @@
package com.tangem.managetokens.presentation.router
/**
* Manage Tokens screens
*
* @property route route string representation
*
*/
internal sealed class ManageTokensRoute(val route: String) {
object ManageTokens : ManageTokensRoute(route = "manage_tokens")
object AddCustomToken : ManageTokensRoute(route = "manage_tokens/add_custom_token") {
object Main : ManageTokensRoute(AddCustomToken.route + "/main")
object ChooseNetwork : ManageTokensRoute(AddCustomToken.route + "/choose_network")
object ChooseWallet : ManageTokensRoute(AddCustomToken.route + "/choose_wallet")
object ChooseDerivation : ManageTokensRoute(AddCustomToken.route + "/choose_derivation")
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.managetokens.presentation.router
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.managetokens.presentation.managetokens.ui.ManageTokensScreen
import com.tangem.managetokens.presentation.managetokens.viewmodels.ManageTokensViewModel
import javax.inject.Inject
internal class ManageTokensUiImpl @Inject constructor() : ManageTokensUi {
@Composable
override fun Content(onHeaderSizeChange: (Dp) -> Unit) {
val viewModel = hiltViewModel<ManageTokensViewModel>()
ManageTokensScreen(
state = viewModel.uiState,
onHeaderSizeChange = onHeaderSizeChange,
)
}
}

View file

@ -7,7 +7,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.features.managetokens.navigation.ManageTokensRouter
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.features.wallet.navigation.WalletRouter
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -24,7 +24,7 @@ internal class WalletFragment : ComposeFragment() {
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Inject
internal lateinit var manageTokensRouter: ManageTokensRouter
internal lateinit var manageTokensUi: ManageTokensUi
/** Feature router */
@Inject
@ -44,7 +44,7 @@ internal class WalletFragment : ComposeFragment() {
_walletRouter.Initialize(
onFinish = requireActivity()::finish,
manageTokensRouter = manageTokensRouter,
manageTokensUi = manageTokensUi,
)
}

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.router
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
@ -27,7 +28,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.managetokens.navigation.ManageTokensRouter
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import kotlin.properties.Delegates
@ -42,7 +43,7 @@ internal class DefaultWalletRouter(
override fun getEntryFragment(): Fragment = WalletFragment.create()
@Composable
override fun Initialize(onFinish: () -> Unit, manageTokensRouter: ManageTokensRouter) {
override fun Initialize(onFinish: () -> Unit, manageTokensUi: ManageTokensUi) {
this.onFinish = onFinish
NavHost(
@ -56,24 +57,15 @@ internal class DefaultWalletRouter(
}
var bottomSheetHeaderHeight by remember { mutableStateOf(0.dp) }
val innerController = rememberNavController()
WalletScreen(
state = viewModel.uiState.collectAsStateWithLifecycle().value,
bottomSheetHeaderHeightProvider = { bottomSheetHeaderHeight },
bottomSheetContent = {
NavHost(
navController = innerController,
startDestination = manageTokensRouter.startDestination,
) {
// Manage Tokens
with(manageTokensRouter) {
initialize(
navController = innerController,
onHeaderSizeChange = { bottomSheetHeaderHeight = it },
)
}
}
// Manage Tokens
manageTokensUi.Content(
onHeaderSizeChange = { bottomSheetHeaderHeight = it },
)
},
)
}
@ -97,6 +89,7 @@ internal class DefaultWalletRouter(
}
}
@SuppressLint("RestrictedApi")
override fun popBackStack(screen: AppScreen?) {
/*
* It's hack that avoid issue with closing the wallet screen.

View file

@ -5,7 +5,7 @@ import androidx.compose.runtime.Stable
import com.tangem.core.navigation.AppScreen
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.managetokens.navigation.ManageTokensRouter
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.features.wallet.navigation.WalletRouter
/**
@ -26,7 +26,7 @@ internal interface InnerWalletRouter : WalletRouter {
*/
@Suppress("TopLevelComposableFunctions")
@Composable
fun Initialize(onFinish: () -> Unit, manageTokensRouter: ManageTokensRouter)
fun Initialize(onFinish: () -> Unit, manageTokensUi: ManageTokensUi)
/** Pop back stack */
fun popBackStack(screen: AppScreen? = null)

View file

@ -23,6 +23,7 @@ internal fun BaseExtension.configureCompose(project: Project) {
contains(Regex(pattern = ":onboarding\$")) || // TODO: divide on api/impl after migrating all onboarding to module
contains(Regex(pattern = ":presentation\$")) ||
contains(Regex(pattern = ":app\$")) || // TODO: [REDACTED_JIRA]
contains(Regex(pattern = ":features:manage-tokens:api\$")) || // provides Composable function
contains(Regex(pattern = ":impl\$"))
}