diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 4ef519edbd..04b3f17541 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index 9dab1b838d..995658a42c 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt index 5f2030f390..7074523df0 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index febaf4d633..4a543195d6 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -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, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt index 409e1430d0..093d9cfce7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt @@ -6,4 +6,6 @@ import androidx.compose.runtime.Immutable * General interface for bottom sheet config model */ @Immutable -interface TangemBottomSheetConfigContent \ No newline at end of file +interface TangemBottomSheetConfigContent { + object Empty : TangemBottomSheetConfigContent +} \ No newline at end of file diff --git a/features/manage-tokens/api/build.gradle.kts b/features/manage-tokens/api/build.gradle.kts index 1195231904..cbfb3f7b1f 100644 --- a/features/manage-tokens/api/build.gradle.kts +++ b/features/manage-tokens/api/build.gradle.kts @@ -9,9 +9,5 @@ android { } dependencies { - /** AndroidX */ - implementation(deps.androidx.fragment.ktx) - implementation(deps.compose.foundation) - implementation(deps.compose.navigation) } \ No newline at end of file diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensRouter.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensRouter.kt deleted file mode 100644 index b6310d4e11..0000000000 --- a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensRouter.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensUi.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensUi.kt new file mode 100644 index 0000000000..7e5c95da12 --- /dev/null +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/navigation/ManageTokensUi.kt @@ -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) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/di/ManageTokensRouterModule.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/di/ManageTokensRouterModule.kt index 12c7db4c53..e0ca63c3b2 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/di/ManageTokensRouterModule.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/di/ManageTokensRouterModule.kt @@ -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 } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRoute.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRoute.kt new file mode 100644 index 0000000000..fad644416a --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRoute.kt @@ -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" \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRouter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRouter.kt new file mode 100644 index 0000000000..4185ccddd4 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/router/AddCustomTokenRouter.kt @@ -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) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenBottomSheet.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenBottomSheet.kt new file mode 100644 index 0000000000..499c2e6cb4 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/ui/AddCustomTokenBottomSheet.kt @@ -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() + + 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) + } + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/viewmodels/AddCustomTokenViewModel.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/viewmodels/AddCustomTokenViewModel.kt index 9211b1aaf1..1efdf2911b 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/viewmodels/AddCustomTokenViewModel.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/addcustomtoken/viewmodels/AddCustomTokenViewModel.kt @@ -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() } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt index 8b4c05217c..6738d56d3f 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt @@ -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, val onEmptySearchResult: (String) -> Unit, ) diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/factory/ManageTokensStateFactory.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/factory/ManageTokensStateFactory.kt index a32b887e26..d7531b2e85 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/factory/ManageTokensStateFactory.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/factory/ManageTokensStateFactory.kt @@ -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, + ), ) } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt index 5d141ddbaa..40d4334eb3 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt @@ -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 diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt index 28dd482aeb..12f7a0a191 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt @@ -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 } + } } } } diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensUiEvents.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensUiEvents.kt index a5ce66e8d5..663871cf2a 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensUiEvents.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensUiEvents.kt @@ -3,4 +3,6 @@ package com.tangem.managetokens.presentation.managetokens.viewmodels internal interface ManageTokensUiEvents { fun onEmptySearchResult(query: String) + + fun onAddCustomTokenSheetDismissed() } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensViewModel.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensViewModel.kt index a2bb0805e8..ad42c3159e 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensViewModel.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/viewmodels/ManageTokensViewModel.kt @@ -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)) + } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/DefaultManageTokensRouter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/DefaultManageTokensRouter.kt deleted file mode 100644 index 5ecebe79fe..0000000000 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/DefaultManageTokensRouter.kt +++ /dev/null @@ -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().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(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(navController = navController).apply { - router = this@DefaultManageTokensRouter - } - ChooseNetworkCustomScreen( - state = viewModel.uiState.chooseNetworkState, - ) - } - composable( - ManageTokensRoute.AddCustomToken.ChooseDerivation.route, - ) { backStackEntry -> - val viewModel = - backStackEntry.parentHiltViewModel(navController = navController).apply { - router = this@DefaultManageTokensRouter - } - ChooseDerivationScreen( - state = requireNotNull(viewModel.uiState.chooseDerivationState), - ) - } - composable( - ManageTokensRoute.AddCustomToken.ChooseWallet.route, - ) { backStackEntry -> - val viewModel = - backStackEntry.parentHiltViewModel(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) - } -} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/InnerManageTokensRouter.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/InnerManageTokensRouter.kt deleted file mode 100644 index 999ec29252..0000000000 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/InnerManageTokensRouter.kt +++ /dev/null @@ -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() -} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensRoute.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensRoute.kt deleted file mode 100644 index 705769d5b0..0000000000 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensRoute.kt +++ /dev/null @@ -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") - } -} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensUiImpl.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensUiImpl.kt new file mode 100644 index 0000000000..3dd460d06f --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/router/ManageTokensUiImpl.kt @@ -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() + + ManageTokensScreen( + state = viewModel.uiState, + onHeaderSizeChange = onHeaderSizeChange, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt index c13cfa5e90..74df363767 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -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, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 24c2270f96..54ca84fe4d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -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. diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index 41abc741f6..9cfd2fe858 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -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) diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt index 3504fee112..142649ae2a 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt @@ -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\$")) }