diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index 5faa769457..cf7de3bfe1 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -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())) + } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt index 58c0bb0dde..80e03d9347 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt @@ -10,4 +10,6 @@ interface ReduxStateHolder { suspend fun dispatchWithMain(action: Action) suspend fun onUserWalletSelected(userWallet: UserWallet) + + fun sendFeedbackEmail() } \ No newline at end of file diff --git a/features/details/api/src/main/kotlin/com/tangem/features/details/DetailsEntryPoint.kt b/features/details/api/src/main/kotlin/com/tangem/features/details/DetailsEntryPoint.kt index f2cf124e1c..d3ac9fe6f7 100644 --- a/features/details/api/src/main/kotlin/com/tangem/features/details/DetailsEntryPoint.kt +++ b/features/details/api/src/main/kotlin/com/tangem/features/details/DetailsEntryPoint.kt @@ -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" + } } \ No newline at end of file diff --git a/features/details/impl/build.gradle.kts b/features/details/impl/build.gradle.kts index 4f9498a097..5060249621 100644 --- a/features/details/impl/build.gradle.kts +++ b/features/details/impl/build.gradle.kts @@ -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) diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/DetailsFragment.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/DetailsFragment.kt index 6e6b812683..28db59fc3d 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/DetailsFragment.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/DetailsFragment.kt @@ -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 { diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/DetailsRouter.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/DetailsRouter.kt new file mode 100644 index 0000000000..fbd703a49a --- /dev/null +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/DetailsRouter.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/DetailsComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/DetailsComponent.kt index fbe165d89e..9731597b9b 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/DetailsComponent.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/DetailsComponent.kt @@ -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) diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt index 59a69914fd..9c6aff4521 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt @@ -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, + ) } } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewRouter.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewRouter.kt new file mode 100644 index 0000000000..49366db923 --- /dev/null +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewRouter.kt @@ -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 */ + } +} \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/routing/DetailsRoute.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/routing/DetailsRoute.kt new file mode 100644 index 0000000000..878987aca5 --- /dev/null +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/routing/DetailsRoute.kt @@ -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() +} \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt index ff67ebbd9e..0b03305816 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt @@ -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( diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt index 9ef2760ab6..9bd974e94c 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt @@ -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 = 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/" + } } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/SocialsBuilder.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/SocialsBuilder.kt index d2666a03e7..6896f1e7d0 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/SocialsBuilder.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/SocialsBuilder.kt @@ -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 = 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( diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 301b8984f8..dad4976a7d 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -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) } \ No newline at end of file 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 c0f4910bfe..6276cdb2e9 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 @@ -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() { 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 a8b311b067..bf8aba542f 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 @@ -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() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt index cae3373a31..5c192d9a44 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt @@ -82,7 +82,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( ), ) } else { - router.openDetailsScreen() + router.openDetailsScreen(stateHolder.getSelectedWalletId()) } } }