Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-13 15:22:15 +04:00
parent 0225de915a
commit 1526da80eb
20 changed files with 41 additions and 226 deletions

View file

@ -29,7 +29,6 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
@ -112,8 +111,6 @@ interface ApplicationEntryPoint {
fun getDetailsFeatureToggles(): DetailsFeatureToggles
fun getDetailsEntryPoint(): DetailsEntryPoint
fun getUrlOpener(): UrlOpener
fun getShareManager(): ShareManager

View file

@ -44,7 +44,6 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
@ -186,9 +185,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val detailsFeatureToggles: DetailsFeatureToggles
get() = entryPoint.getDetailsFeatureToggles()
private val detailsEntryPoint: DetailsEntryPoint
get() = entryPoint.getDetailsEntryPoint()
private val urlOpener
get() = entryPoint.getUrlOpener()
@ -285,7 +281,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
blockchainSDKFactory = blockchainSDKFactory,
saveBlockchainErrorUseCase = saveBlockchainErrorUseCase,
detailsFeatureToggles = detailsFeatureToggles,
detailsEntryPoint = detailsEntryPoint,
urlOpener = urlOpener,
shareManager = shareManager,
appRouter = appRouter,

View file

@ -182,13 +182,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
}
}
AppScreen.Details -> {
val featureToggles = store.inject(getDependency = DaggerGraphState::detailsFeatureToggles)
if (featureToggles.isRedesignEnabled) {
store.inject(DaggerGraphState::detailsEntryPoint).entryFragment()
} else {
DetailsFragment()
}
DetailsFragment()
}
AppScreen.DetailsSecurity -> SecurityModeFragment()
AppScreen.CardSettings -> CardSettingsFragment()

View file

@ -27,7 +27,6 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.managetokens.navigation.ManageTokensUi
@ -82,7 +81,6 @@ data class DaggerGraphState(
val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase? = null,
val assetLoader: AssetLoader? = null,
val detailsFeatureToggles: DetailsFeatureToggles? = null,
val detailsEntryPoint: DetailsEntryPoint? = null,
val stakingRouter: StakingRouter? = null,
val urlOpener: UrlOpener? = null,
val shareManager: ShareManager? = null,

View file

@ -5,6 +5,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
@ -16,6 +17,7 @@ import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorFragmen
import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryFragment
import com.tangem.tap.features.details.ui.details.DetailsFragment
import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment
import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment
@ -45,6 +47,7 @@ internal class ChildFactory @Inject constructor(
private val qrScanningRouter: QrScanningRouter,
private val stakingRouter: StakingRouter,
private val testerRouter: TesterRouter,
private val detailsFeatureToggles: DetailsFeatureToggles,
) {
@Suppress("LongMethod", "CyclomaticComplexMethod")
@ -80,11 +83,15 @@ internal class ChildFactory @Inject constructor(
route.asFragmentChild(Provider { CardSettingsFragment() })
}
is AppRoute.Details -> {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = DetailsComponent.Params(route.userWalletId),
componentFactory = detailsComponentFactory,
)
if (detailsFeatureToggles.isRedesignEnabled) {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = DetailsComponent.Params(route.userWalletId),
componentFactory = detailsComponentFactory,
)
} else {
route.asFragmentChild(Provider { DetailsFragment() })
}
}
is AppRoute.DetailsSecurity -> {
route.asFragmentChild(Provider { SecurityModeFragment() })

View file

@ -15,6 +15,7 @@ dependencies {
/* Project - Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
/* AndroidX */
implementation(deps.androidx.fragment.ktx)

View file

@ -1,13 +0,0 @@
package com.tangem.features.details
import androidx.fragment.app.Fragment
interface DetailsEntryPoint {
fun entryFragment(): Fragment
companion object {
const val USER_WALLET_ID_KEY = "user_wallet_id"
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.features.details.component
import androidx.compose.material3.SnackbarHostState
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.ComposableContentComponent
@ -8,8 +7,6 @@ import com.tangem.domain.wallets.models.UserWalletId
interface DetailsComponent : ComposableContentComponent {
val snackbarHostState: SnackbarHostState
interface Factory : ComponentFactory<DetailsComponent, Params> {
override fun create(context: AppComponentContext, params: Params): DetailsComponent

View file

@ -23,6 +23,7 @@ dependencies {
implementation(projects.core.featuretoggles)
implementation(projects.core.navigation)
implementation(projects.core.analytics.models)
implementation(projects.common.routing)
/* Project - Domain */
implementation(projects.domain.models)

View file

@ -1,74 +0,0 @@
package com.tangem.features.details
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.fragment.app.Fragment
import com.arkivanov.decompose.defaultComponentContext
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.di.RootAppComponentContext
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.message.EventMessageEffect
import com.tangem.core.ui.message.EventMessageHandler
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.details.component.DetailsComponent
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
// TODO: Remove after [REDACTED_JIRA]
@AndroidEntryPoint
internal class DetailsFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
@Inject
internal lateinit var componentFactory: DetailsComponent.Factory
@Inject
internal lateinit var detailsRouter: DetailsRouter
@Inject
@RootAppComponentContext
internal lateinit var rootContext: AppComponentContext
private val component: DetailsComponent by lazy { initComponent() }
private val messageHandler = EventMessageHandler()
@Composable
override fun ScreenContent(modifier: Modifier) {
component.Content(modifier = modifier)
EventMessageEffect(
messageHandler = messageHandler,
snackbarHostState = component.snackbarHostState,
)
}
private fun initComponent(): DetailsComponent {
val selectedUserWalletId = arguments?.getString(DetailsEntryPoint.USER_WALLET_ID_KEY)
?.let(::UserWalletId)
requireNotNull(selectedUserWalletId) { "UserWalletId must be provided" }
val context = rootContext.childByContext(
componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher),
messageHandler = messageHandler,
router = detailsRouter,
)
return componentFactory.create(
context = context,
params = DetailsComponent.Params(
selectedUserWalletId = selectedUserWalletId,
),
)
}
companion object : DetailsEntryPoint {
override fun entryFragment(): Fragment = DetailsFragment()
}
}

View file

@ -1,61 +0,0 @@
package com.tangem.features.details
import com.tangem.core.decompose.navigation.Route
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.ReduxNavController
import com.tangem.core.navigation.feedback.FeedbackManager
import com.tangem.core.navigation.feedback.FeedbackType
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.features.details.routing.DetailsRoute
import com.tangem.features.tester.api.TesterRouter
import javax.inject.Inject
import kotlin.reflect.KClass
// TODO: Remove after [REDACTED_JIRA]
internal class DetailsRouter @Inject constructor(
private val reduxNavController: ReduxNavController,
private val feedbackManager: FeedbackManager,
private val testerRouter: TesterRouter,
private val urlOpener: UrlOpener,
) : Router {
override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
if (route is DetailsRoute) {
when (route) {
is DetailsRoute.Screen -> {
reduxNavController.navigate(NavigationAction.NavigateTo(route.screen, bundle = route.params))
}
is DetailsRoute.Feedback -> {
feedbackManager.sendEmail(FeedbackType.Feedback)
}
is DetailsRoute.TesterMenu -> {
// testerRouter.startTesterScreen()
}
is DetailsRoute.Url -> {
urlOpener.openUrl(route.url)
}
}
onComplete(true)
} else {
onComplete(false)
}
}
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
reduxNavController.popBackStack()
onComplete(true)
}
override fun popTo(routeClass: KClass<out Route>, onComplete: (isSuccess: Boolean) -> Unit) {
TODO("This class will be removed in future")
}
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
TODO("This class will be removed in future")
}
override fun replaceAll(vararg routes: Route, onComplete: (isSuccess: Boolean) -> Unit) {
TODO("This class will be removed in future")
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.features.details.component.impl
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
@ -28,8 +27,6 @@ internal class DefaultDetailsComponent @AssistedInject constructor(
context = child(key = "user_wallet_list"),
)
override val snackbarHostState: SnackbarHostState = SnackbarHostState()
init {
model.provideUserWalletId(params.selectedUserWalletId)
}
@ -41,7 +38,6 @@ internal class DefaultDetailsComponent @AssistedInject constructor(
DetailsScreen(
modifier = modifier,
state = state,
snackbarHostState = snackbarHostState,
userWalletListBlockContent = userWalletListComponent,
)
}

View file

@ -1,9 +1,10 @@
package com.tangem.features.details.component.preview
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.navigation.DummyRouter
import com.tangem.core.navigation.feedback.DummyFeedbackManager
import com.tangem.core.navigation.url.DummyUrlOpener
import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.details.entity.DetailsFooterUM
import com.tangem.features.details.entity.DetailsUM
@ -14,16 +15,16 @@ import kotlinx.coroutines.runBlocking
internal class PreviewDetailsComponent : DetailsComponent {
override val snackbarHostState: SnackbarHostState = SnackbarHostState()
private val previewBlocks = runBlocking {
ItemsBuilder(
router = DummyRouter(),
urlOpener = DummyUrlOpener(),
feedbackManager = DummyFeedbackManager(),
).buldAll(isWalletConnectAvailable = true)
}
private val previewFooter = DetailsFooterUM(
socials = SocialsBuilder(DummyRouter()).buildAll(),
socials = SocialsBuilder(DummyUrlOpener()).buildAll(),
appVersion = "1.0.0-preview",
)
@ -39,7 +40,6 @@ internal class PreviewDetailsComponent : DetailsComponent {
DetailsScreen(
modifier = modifier,
state = previewState,
snackbarHostState = snackbarHostState,
userWalletListBlockContent = PreviewUserWalletListComponent(),
)
}

View file

@ -2,9 +2,7 @@ package com.tangem.features.details.di
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.features.details.DefaultDetailsFeatureToggles
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.details.DetailsFragment
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -20,10 +18,4 @@ internal object FeatureModule {
fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): DetailsFeatureToggles {
return DefaultDetailsFeatureToggles(featureTogglesManager)
}
@Provides
@Singleton
fun provideEntryPoint(): DetailsEntryPoint {
return DetailsFragment.Companion
}
}

View file

@ -1,20 +0,0 @@
package com.tangem.features.details.routing
import android.os.Bundle
import com.tangem.core.decompose.navigation.Route
import com.tangem.core.navigation.AppScreen
// TODO: Remove after [REDACTED_JIRA]
internal sealed class DetailsRoute : Route {
data class Screen(
val screen: AppScreen,
val params: Bundle? = null,
) : DetailsRoute()
data class Url(val url: String) : DetailsRoute()
data object Feedback : DetailsRoute()
data object TesterMenu : DetailsRoute()
}

View file

@ -21,6 +21,7 @@ import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.ComposableContentComponent
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
import com.tangem.core.ui.res.LocalSnackbarHostState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.details.component.preview.PreviewDetailsComponent
@ -35,7 +36,6 @@ private const val COLLAPSED_APP_BAR_THRESHOLD = 0.4f
@Composable
internal fun DetailsScreen(
state: DetailsUM,
snackbarHostState: SnackbarHostState,
userWalletListBlockContent: ComposableContentComponent,
modifier: Modifier = Modifier,
) {
@ -54,7 +54,7 @@ internal fun DetailsScreen(
snackbarHost = {
TangemSnackbarHost(
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
hostState = snackbarHostState,
hostState = LocalSnackbarHostState.current,
)
},
topBar = { TopBar(state, scrollBehavior) },

View file

@ -1,14 +1,16 @@
package com.tangem.features.details.utils
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.feedback.FeedbackManager
import com.tangem.core.navigation.feedback.FeedbackType
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.features.details.entity.DetailsItemUM
import com.tangem.features.details.impl.BuildConfig
import com.tangem.features.details.impl.R
import com.tangem.features.details.routing.DetailsRoute
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -17,6 +19,8 @@ import javax.inject.Inject
@ComponentScoped
internal class ItemsBuilder @Inject constructor(
private val router: Router,
private val urlOpener: UrlOpener,
private val feedbackManager: FeedbackManager,
) {
suspend fun buldAll(isWalletConnectAvailable: Boolean): ImmutableList<DetailsItemUM> = buildList {
@ -30,7 +34,7 @@ internal class ItemsBuilder @Inject constructor(
private suspend fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean): DetailsItemUM? {
return if (isWalletConnectAvailable) {
DetailsItemUM.WalletConnect(
onClick = { router.push(DetailsRoute.Screen(AppScreen.WalletConnectSessions)) },
onClick = { router.push(AppRoute.WalletConnectSessions) },
)
} else {
null
@ -46,7 +50,7 @@ internal class ItemsBuilder @Inject constructor(
id = "buy_tangem_wallet",
title = resourceReference(R.string.details_buy_wallet),
iconRes = R.drawable.ic_tangem_24,
onClick = { router.push(DetailsRoute.Url(BUY_TANGEM_URL)) },
onClick = { urlOpener.openUrl(BUY_TANGEM_URL) },
),
),
)
@ -58,7 +62,7 @@ internal class ItemsBuilder @Inject constructor(
id = "app_settings",
title = resourceReference(R.string.app_settings_title),
iconRes = R.drawable.ic_settings_24,
onClick = { router.push(DetailsRoute.Screen(AppScreen.AppSettings)) },
onClick = { router.push(AppRoute.AppSettings) },
).let(::add)
if (BuildConfig.TESTER_MENU_ENABLED) {
@ -66,7 +70,7 @@ internal class ItemsBuilder @Inject constructor(
id = "tester_menu",
title = stringReference(value = "Tester menu"),
iconRes = R.drawable.ic_alert_24,
onClick = { router.push(DetailsRoute.TesterMenu) },
onClick = { router.push(AppRoute.TesterMenu) },
).let(::add)
}
}.toImmutableList(),
@ -79,13 +83,13 @@ internal class ItemsBuilder @Inject constructor(
id = "send_feedback",
title = resourceReference(R.string.details_send_feedback),
iconRes = R.drawable.ic_comment_24,
onClick = { router.push(DetailsRoute.Feedback) },
onClick = { feedbackManager.sendEmail(FeedbackType.Feedback) },
),
DetailsItemUM.Basic.Item(
id = "disclaimer",
title = resourceReference(R.string.disclaimer_title),
iconRes = R.drawable.ic_text_24,
onClick = { router.push(DetailsRoute.Screen(AppScreen.Disclaimer)) },
onClick = { router.push(AppRoute.Disclaimer) },
),
),
)

View file

@ -2,17 +2,16 @@ package com.tangem.features.details.utils
import androidx.compose.ui.text.intl.Locale
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.features.details.entity.DetailsFooterUM
import com.tangem.features.details.impl.R
import com.tangem.features.details.routing.DetailsRoute
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject
@ComponentScoped
internal class SocialsBuilder @Inject constructor(
private val router: Router,
private val urlOpener: UrlOpener,
) {
fun buildAll(): ImmutableList<DetailsFooterUM.Social> = Social.all.map { social ->
@ -32,7 +31,7 @@ internal class SocialsBuilder @Inject constructor(
social.url
}
router.push(DetailsRoute.Url(url))
urlOpener.openUrl(url)
}
private enum class Social(

View file

@ -2,9 +2,11 @@ package com.tangem.features.details.utils
import arrow.core.raise.*
import arrow.core.recover
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.navigation.popTo
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.isNullOrEmpty
@ -42,7 +44,7 @@ internal class UserWalletSaver @Inject constructor(
saveWallet(userWallet)
// router.popTo(DetailsRoute.Screen(AppScreen.Wallet))
router.popTo<AppRoute.Wallet>()
},
recover = { error ->
val message = error.message
@ -75,7 +77,7 @@ internal class UserWalletSaver @Inject constructor(
selectWalletUseCase(userWallet.walletId).bind()
}
// router.popTo(DetailsRoute.Screen(AppScreen.Wallet))
router.popTo<AppRoute.Wallet>()
}
private suspend fun Raise<Error>.createUserWallet(response: ScanResponse): UserWallet {