Updated on 2026-08-14
This commit is contained in:
commit
3220a47a88
17 changed files with 215 additions and 19 deletions
|
|
@ -10,7 +10,9 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -54,4 +56,8 @@ class AppStateHolder @Inject constructor() : ReduxNavController, ReduxStateHolde
|
|||
override suspend fun onUserWalletSelected(userWallet: UserWallet) {
|
||||
mainStore?.onUserWalletSelected(userWallet)
|
||||
}
|
||||
|
||||
override fun sendFeedbackEmail() {
|
||||
mainStore?.dispatch(GlobalAction.SendEmail(FeedbackEmail()))
|
||||
}
|
||||
}
|
||||
|
|
@ -10,4 +10,6 @@ interface ReduxStateHolder {
|
|||
suspend fun dispatchWithMain(action: Action)
|
||||
|
||||
suspend fun onUserWalletSelected(userWallet: UserWallet)
|
||||
|
||||
fun sendFeedbackEmail()
|
||||
}
|
||||
|
|
@ -5,4 +5,9 @@ import androidx.fragment.app.Fragment
|
|||
interface DetailsEntryPoint {
|
||||
|
||||
fun entryFragment(): Fragment
|
||||
|
||||
companion object {
|
||||
|
||||
const val USER_WALLET_ID_KEY = "user_wallet_id"
|
||||
}
|
||||
}
|
||||
|
|
@ -15,14 +15,18 @@ dependencies {
|
|||
|
||||
/* Project - API */
|
||||
implementation(projects.features.details.api)
|
||||
implementation(projects.features.tester.api)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.analytics.models)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.legacy)
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,13 @@ package com.tangem.features.details
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
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.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.component.preview.PreviewDetailsComponent
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
|
@ -16,10 +21,52 @@ 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) {
|
||||
// TODO: Update to use the actual component in [REDACTED_TASK_KEY]
|
||||
PreviewDetailsComponent().View(modifier = modifier)
|
||||
component.View(modifier = modifier)
|
||||
|
||||
EventMessageEffect(
|
||||
messageHandler = messageHandler,
|
||||
snackbarHostState = component.snackbarHostState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun initComponent(): DetailsComponent {
|
||||
// TODO: Uncomment in [REDACTED_JIRA]
|
||||
// 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,
|
||||
// ),
|
||||
// )
|
||||
|
||||
return PreviewDetailsComponent()
|
||||
}
|
||||
|
||||
companion object : DetailsEntryPoint {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
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.domain.redux.ReduxStateHolder
|
||||
import com.tangem.features.details.routing.DetailsRoute
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Remove after [REDACTED_JIRA]
|
||||
internal class DetailsRouter @Inject constructor(
|
||||
private val reduxNavController: ReduxNavController,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val testerRouter: TesterRouter,
|
||||
) : 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 -> {
|
||||
reduxStateHolder.sendFeedbackEmail()
|
||||
}
|
||||
is DetailsRoute.TesterMenu -> {
|
||||
testerRouter.startTesterScreen()
|
||||
}
|
||||
is DetailsRoute.Url -> {
|
||||
reduxNavController.navigate(NavigationAction.OpenUrl(route.url))
|
||||
}
|
||||
}
|
||||
onComplete(true)
|
||||
} else {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
reduxNavController.popBackStack()
|
||||
onComplete(true)
|
||||
}
|
||||
|
||||
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
if (route is DetailsRoute.Screen) {
|
||||
reduxNavController.popBackStack(route.screen)
|
||||
onComplete(true)
|
||||
} else {
|
||||
reduxNavController.getBackStack()
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.details.component
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
|
|
@ -7,6 +8,8 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
|
||||
interface DetailsComponent {
|
||||
|
||||
val snackbarHostState: SnackbarHostState
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
fun View(modifier: Modifier)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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.features.details.component.DetailsComponent
|
||||
|
|
@ -12,15 +13,18 @@ import kotlinx.coroutines.runBlocking
|
|||
|
||||
internal class PreviewDetailsComponent : DetailsComponent {
|
||||
|
||||
override val snackbarHostState: SnackbarHostState = SnackbarHostState()
|
||||
|
||||
private val previewBlocks = runBlocking {
|
||||
ItemsBuilder(
|
||||
walletConnectComponent = PreviewWalletConnectComponent(),
|
||||
userWalletListComponent = PreviewUserWalletListComponent(),
|
||||
router = PreviewRouter(),
|
||||
).buldAll()
|
||||
}
|
||||
|
||||
private val previewFooter = DetailsFooterUM(
|
||||
socials = SocialsBuilder(urlOpener = { /* no-op */ }).buildAll(),
|
||||
socials = SocialsBuilder(PreviewRouter()).buildAll(),
|
||||
appVersion = "1.0.0-preview",
|
||||
)
|
||||
|
||||
|
|
@ -33,6 +37,10 @@ internal class PreviewDetailsComponent : DetailsComponent {
|
|||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
override fun View(modifier: Modifier) {
|
||||
DetailsScreen(state = previewState, modifier = modifier)
|
||||
DetailsScreen(
|
||||
modifier = modifier,
|
||||
state = previewState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.details.component.preview
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
|
||||
internal class PreviewRouter : Router {
|
||||
override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
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()
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.details.component.preview.PreviewDetailsComponent
|
||||
|
|
@ -31,7 +32,7 @@ private const val COLLAPSED_APP_BAR_THRESHOLD = 0.4f
|
|||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun DetailsScreen(state: DetailsUM, modifier: Modifier = Modifier) {
|
||||
internal fun DetailsScreen(state: DetailsUM, snackbarHostState: SnackbarHostState, modifier: Modifier = Modifier) {
|
||||
val backgroundColor = TangemTheme.colors.background.secondary
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
|
||||
|
|
@ -44,6 +45,12 @@ internal fun DetailsScreen(state: DetailsUM, modifier: Modifier = Modifier) {
|
|||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
containerColor = backgroundColor,
|
||||
snackbarHost = {
|
||||
TangemSnackbarHost(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||
hostState = snackbarHostState,
|
||||
)
|
||||
},
|
||||
topBar = { TopBar(state, scrollBehavior) },
|
||||
) { paddingValues ->
|
||||
Content(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
|
|
@ -7,6 +9,7 @@ import com.tangem.features.details.component.WalletConnectComponent
|
|||
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
|
||||
|
|
@ -14,6 +17,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
internal class ItemsBuilder(
|
||||
private val walletConnectComponent: WalletConnectComponent,
|
||||
private val userWalletListComponent: UserWalletListComponent,
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
suspend fun buldAll(): ImmutableList<DetailsItemUM> = buildList {
|
||||
|
|
@ -37,7 +41,7 @@ internal class ItemsBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
fun buildUserWalletListBlock(): DetailsItemUM = DetailsItemUM.Component(
|
||||
private fun buildUserWalletListBlock(): DetailsItemUM = DetailsItemUM.Component(
|
||||
id = "user_wallet_list",
|
||||
content = {
|
||||
userWalletListComponent.View(modifier = it)
|
||||
|
|
@ -51,7 +55,7 @@ internal class ItemsBuilder(
|
|||
id = "buy_tangem_wallet",
|
||||
title = stringReference("Buy Tangem Wallet"), // TODO: Move to resources in [REDACTED_TASK_KEY]
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onClick = { router.push(DetailsRoute.Url(BUY_TANGEM_URL)) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -63,7 +67,7 @@ internal class ItemsBuilder(
|
|||
id = "app_settings",
|
||||
title = resourceReference(R.string.app_settings_title),
|
||||
iconRes = R.drawable.ic_settings_24,
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onClick = { router.push(DetailsRoute.Screen(AppScreen.AppSettings)) },
|
||||
).let(::add)
|
||||
|
||||
if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
|
|
@ -71,7 +75,7 @@ internal class ItemsBuilder(
|
|||
id = "tester_menu",
|
||||
title = stringReference(value = "Tester menu"),
|
||||
iconRes = R.drawable.ic_alert_24,
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onClick = { router.push(DetailsRoute.TesterMenu) },
|
||||
).let(::add)
|
||||
}
|
||||
}.toImmutableList(),
|
||||
|
|
@ -84,14 +88,18 @@ internal class ItemsBuilder(
|
|||
id = "send_feedback",
|
||||
title = stringReference("Send feedback"), // TODO: Move to resources in [REDACTED_TASK_KEY]
|
||||
iconRes = R.drawable.ic_comment_24,
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onClick = { router.push(DetailsRoute.Feedback) },
|
||||
),
|
||||
DetailsItemUM.Basic.Item(
|
||||
id = "disclaimer",
|
||||
title = resourceReference(R.string.disclaimer_title),
|
||||
iconRes = R.drawable.ic_text_24,
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onClick = { router.push(DetailsRoute.Screen(AppScreen.Disclaimer)) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val BUY_TANGEM_URL = "https://buy.tangem.com/"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
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
|
||||
|
||||
internal class SocialsBuilder(
|
||||
private val urlOpener: (url: String) -> Unit,
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
fun buildAll(): ImmutableList<DetailsFooterUM.Social> = Social.all.map { social ->
|
||||
|
|
@ -21,11 +23,13 @@ internal class SocialsBuilder(
|
|||
private fun openUrl(social: Social) {
|
||||
val locale = Locale.current.region
|
||||
|
||||
if (locale == RUSSIA_LOCALE && social.urlRu != null) {
|
||||
urlOpener(social.urlRu)
|
||||
val url = if (locale == RUSSIA_LOCALE && social.urlRu != null) {
|
||||
social.urlRu
|
||||
} else {
|
||||
urlOpener(social.url)
|
||||
social.url
|
||||
}
|
||||
|
||||
router.push(DetailsRoute.Url(url))
|
||||
}
|
||||
|
||||
private enum class Social(
|
||||
|
|
|
|||
|
|
@ -87,4 +87,5 @@ dependencies {
|
|||
implementation(projects.features.send.api)
|
||||
implementation(projects.features.tester.api)
|
||||
implementation(projects.features.manageTokens.api)
|
||||
implementation(projects.features.details.api)
|
||||
}
|
||||
|
|
@ -28,6 +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.details.DetailsEntryPoint
|
||||
import com.tangem.features.managetokens.navigation.ExpandableState
|
||||
import com.tangem.features.managetokens.navigation.ManageTokensUi
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
|
||||
|
|
@ -116,8 +117,15 @@ internal class DefaultWalletRouter(
|
|||
navController.navigate(WalletRoute.OrganizeTokens.createRoute(userWalletId))
|
||||
}
|
||||
|
||||
override fun openDetailsScreen() {
|
||||
reduxNavController.navigate(action = NavigationAction.NavigateTo(AppScreen.Details))
|
||||
override fun openDetailsScreen(selectedWalletId: UserWalletId) {
|
||||
reduxNavController.navigate(
|
||||
action = NavigationAction.NavigateTo(
|
||||
screen = AppScreen.Details,
|
||||
bundle = bundleOf(
|
||||
DetailsEntryPoint.USER_WALLET_ID_KEY to selectedWalletId.stringValue,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun openOnboardingScreen() {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ internal interface InnerWalletRouter : WalletRouter {
|
|||
fun openOrganizeTokensScreen(userWalletId: UserWalletId)
|
||||
|
||||
/** Open details screen */
|
||||
fun openDetailsScreen()
|
||||
fun openDetailsScreen(selectedWalletId: UserWalletId)
|
||||
|
||||
/** Open onboarding screen */
|
||||
fun openOnboardingScreen()
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
),
|
||||
)
|
||||
} else {
|
||||
router.openDetailsScreen()
|
||||
router.openDetailsScreen(stateHolder.getSelectedWalletId())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue