Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-01 20:10:08 +08:00
parent 2782d8a846
commit 8262c72f40
15 changed files with 230 additions and 18 deletions

View file

@ -12,6 +12,7 @@ import com.google.android.material.snackbar.Snackbar
import com.tangem.TangemSdk import com.tangem.TangemSdk
import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.ScanCardUseCase
import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.operations.backup.BackupService import com.tangem.operations.backup.BackupService
import com.tangem.sdk.extensions.init import com.tangem.sdk.extensions.init
import com.tangem.tap.common.ActivityResultCallbackHolder import com.tangem.tap.common.ActivityResultCallbackHolder
@ -86,6 +87,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject @Inject
lateinit var scanCardUseCase: ScanCardUseCase lateinit var scanCardUseCase: ScanCardUseCase
@Inject
lateinit var walletRouter: WalletRouter
private var snackbar: Snackbar? = null private var snackbar: Snackbar? = null
private val dialogManager = DialogManager() private val dialogManager = DialogManager()
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind) private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
@ -113,7 +117,13 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
), ),
) )
store.dispatch(DaggerGraphAction.SetActivityDependencies(testerRouter, scanCardUseCase)) store.dispatch(
DaggerGraphAction.SetActivityDependencies(
testerRouter = testerRouter,
scanCardUseCase = scanCardUseCase,
walletRouter = walletRouter,
),
)
} }
private fun initUserWalletsListManager() { private fun initUserWalletsListManager() {

View file

@ -33,7 +33,6 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.wallet.R import com.tangem.wallet.R
import timber.log.Timber import timber.log.Timber
import com.tangem.feature.wallet.presentation.WalletFragment as RedesignedWalletFragment
import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment as RedesignedAddCustomTokenFragment import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment as RedesignedAddCustomTokenFragment
fun FragmentActivity.openFragment( fun FragmentActivity.openFragment(
@ -140,7 +139,9 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
getDependency = DaggerGraphState::walletFeatureToggles, getDependency = DaggerGraphState::walletFeatureToggles,
) )
if (featureToggles.isRedesignedScreenEnabled) { if (featureToggles.isRedesignedScreenEnabled) {
RedesignedWalletFragment() store.state.daggerGraphState
.get(getDependency = DaggerGraphState::walletRouter)
.getEntryFragment()
} else { } else {
WalletFragment() WalletFragment()
} }

View file

@ -2,6 +2,7 @@ package com.tangem.tap.proxy.redux
import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.ScanCardUseCase
import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.wallet.navigation.WalletRouter
import org.rekotlin.Action import org.rekotlin.Action
sealed interface DaggerGraphAction : Action { sealed interface DaggerGraphAction : Action {
@ -9,5 +10,6 @@ sealed interface DaggerGraphAction : Action {
data class SetActivityDependencies( data class SetActivityDependencies(
val testerRouter: TesterRouter, val testerRouter: TesterRouter,
val scanCardUseCase: ScanCardUseCase, val scanCardUseCase: ScanCardUseCase,
val walletRouter: WalletRouter,
) : DaggerGraphAction ) : DaggerGraphAction
} }

View file

@ -15,6 +15,7 @@ object DaggerGraphReducer {
is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy( is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy(
testerRouter = action.testerRouter, testerRouter = action.testerRouter,
scanCardUseCase = action.scanCardUseCase, scanCardUseCase = action.scanCardUseCase,
walletRouter = action.walletRouter,
) )
} }
} }

View file

@ -5,6 +5,7 @@ import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.ScanCardUseCase
import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import org.rekotlin.StateType import org.rekotlin.StateType
@ -15,6 +16,7 @@ data class DaggerGraphState(
val customTokenFeatureToggles: CustomTokenFeatureToggles? = null, val customTokenFeatureToggles: CustomTokenFeatureToggles? = null,
val scanCardUseCase: ScanCardUseCase? = null, val scanCardUseCase: ScanCardUseCase? = null,
val walletFeatureToggles: WalletFeatureToggles? = null, val walletFeatureToggles: WalletFeatureToggles? = null,
val walletRouter: WalletRouter? = null,
) : StateType { ) : StateType {
inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T { inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T {

View file

@ -2,4 +2,9 @@ plugins {
alias(deps.plugins.android.library) alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.android)
id("configuration") id("configuration")
}
dependencies {
/** AndroidX */
implementation(deps.androidx.fragment.ktx)
} }

View file

@ -0,0 +1,14 @@
package com.tangem.features.wallet.navigation
import androidx.fragment.app.Fragment
/**
* Wallet feature router
*
[REDACTED_AUTHOR]
*/
interface WalletRouter {
/** Get feature entry point [Fragment] */
fun getEntryFragment(): Fragment
}

View file

@ -0,0 +1,18 @@
package com.tangem.feature.wallet.di
import com.tangem.feature.wallet.presentation.router.DefaultWalletRouter
import com.tangem.features.wallet.navigation.WalletRouter
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.scopes.ActivityScoped
@Module
@InstallIn(ActivityComponent::class)
internal object WalletRouterModule {
@Provides
@ActivityScoped
fun provideWalletRouter(): WalletRouter = DefaultWalletRouter()
}

View file

@ -8,9 +8,11 @@ import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.features.wallet.navigation.WalletRouter
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/** /**
* Wallet fragment * Wallet fragment
@ -18,7 +20,16 @@ import dagger.hilt.android.AndroidEntryPoint
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class WalletFragment : Fragment() { internal class WalletFragment : Fragment() {
/** Feature router */
@Inject
internal lateinit var walletRouter: WalletRouter
private val _walletRouter: InnerWalletRouter
get() = requireNotNull(walletRouter as? InnerWalletRouter) {
"_walletRouter should be instance of InnerWalletRouter"
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
@ -31,11 +42,14 @@ class WalletFragment : Fragment() {
return ComposeView(inflater.context).apply { return ComposeView(inflater.context).apply {
setContent { setContent {
isTransitionGroup = true isTransitionGroup = true
_walletRouter.Initialize()
TangemTheme {
// TODO: [REDACTED_TASK_KEY] Call WalletScreen
}
} }
} }
} }
companion object {
/** Create wallet fragment instance */
fun create(): WalletFragment = WalletFragment()
}
} }

View file

@ -0,0 +1,51 @@
package com.tangem.feature.wallet.presentation.router
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.fragment.app.Fragment
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.WalletFragment
import com.tangem.feature.wallet.presentation.ui.WalletScreen
import com.tangem.feature.wallet.presentation.viewmodels.WalletViewModel
import kotlin.properties.Delegates
/** Default implementation of wallet feature router */
internal class DefaultWalletRouter : InnerWalletRouter {
private var navController: NavHostController by Delegates.notNull()
override fun getEntryFragment(): Fragment = WalletFragment.create()
@Composable
override fun Initialize() {
TangemTheme {
NavHost(
navController = rememberNavController().apply { navController = this },
startDestination = WalletScreens.WALLET.name,
) {
composable(WalletScreens.WALLET.name) {
val viewModel = hiltViewModel<WalletViewModel>().apply { router = this@DefaultWalletRouter }
WalletScreen(state = viewModel.uiState)
}
composable(WalletScreens.ORGANIZE_TOKENS.name) {
BackHandler(onBack = ::popBackStack)
// TODO: Add OrganizeTokensScreen
}
}
}
}
override fun popBackStack() {
navController.popBackStack()
}
override fun openOrganizeTokensScreen() {
navController.navigate(WalletScreens.ORGANIZE_TOKENS.name)
}
}

View file

@ -0,0 +1,23 @@
package com.tangem.feature.wallet.presentation.router
import androidx.compose.runtime.Composable
import com.tangem.features.wallet.navigation.WalletRouter
/**
* Interface of inner wallet feature router
*
[REDACTED_AUTHOR]
*/
internal interface InnerWalletRouter : WalletRouter {
/** Initialize router */
@Suppress("TopLevelComposableFunctions")
@Composable
fun Initialize()
/** Pop back stack */
fun popBackStack()
/** Open organize tokens screen */
fun openOrganizeTokensScreen()
}

View file

@ -0,0 +1,10 @@
package com.tangem.feature.wallet.presentation.router
/**
* Wallet feature screens
*
[REDACTED_AUTHOR]
*/
enum class WalletScreens {
WALLET, ORGANIZE_TOKENS
}

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.ui
import com.tangem.core.ui.R import com.tangem.core.ui.R
import com.tangem.feature.wallet.presentation.state.WalletCardState import com.tangem.feature.wallet.presentation.state.WalletCardState
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import java.util.UUID import java.util.UUID
internal object WalletPreviewData { internal object WalletPreviewData {
@ -38,4 +39,13 @@ internal object WalletPreviewData {
imageResId = R.drawable.ill_businessman_3d, imageResId = R.drawable.ill_businessman_3d,
onClick = {}, onClick = {},
) )
val walletScreenState = WalletStateHolder(
onBackClick = {},
headerConfig = WalletStateHolder.HeaderConfig(
wallets = listOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError),
onScanCardClick = {},
onMoreClick = {},
),
)
} }

View file

@ -1,11 +1,17 @@
package com.tangem.feature.wallet.presentation.ui.components package com.tangem.feature.wallet.presentation.ui.components
import androidx.compose.animation.core.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalOverscrollConfiguration
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@ -26,6 +32,7 @@ import com.tangem.feature.wallet.presentation.ui.WalletPreviewData
* *
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
*/ */
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
internal fun WalletHeader(config: WalletStateHolder.HeaderConfig) { internal fun WalletHeader(config: WalletStateHolder.HeaderConfig) {
Column( Column(
@ -34,15 +41,23 @@ internal fun WalletHeader(config: WalletStateHolder.HeaderConfig) {
.padding(bottom = TangemTheme.dimens.spacing14), .padding(bottom = TangemTheme.dimens.spacing14),
) { ) {
TopBar(onScanCardClick = config.onScanCardClick, onMoreClick = config.onMoreClick) TopBar(onScanCardClick = config.onScanCardClick, onMoreClick = config.onMoreClick)
LazyRow(
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), val lazyListState = rememberLazyListState()
) { LazyRow(
items(items = config.wallets, key = WalletCardState::id) { state -> state = lazyListState,
WalletCard( contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
state = state, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
modifier = Modifier.width(LocalConfiguration.current.screenWidthDp.dp - TangemTheme.dimens.size32), flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState),
) ) {
items(items = config.wallets, key = WalletCardState::id) { state ->
WalletCard(
state = state,
modifier = Modifier.width(
LocalConfiguration.current.screenWidthDp.dp - TangemTheme.dimens.size32,
),
)
}
} }
} }
} }

View file

@ -0,0 +1,36 @@
package com.tangem.feature.wallet.presentation.viewmodels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.state.WalletStateHolder
import com.tangem.feature.wallet.presentation.ui.WalletPreviewData
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlin.properties.Delegates
/**
* Wallet screen view model
*
[REDACTED_AUTHOR]
*/
@HiltViewModel
internal class WalletViewModel @Inject constructor() : ViewModel() {
/** Feature router */
var router: InnerWalletRouter by Delegates.notNull()
/** Screen state */
var uiState by mutableStateOf(getInitialState())
private set
// TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData
private fun getInitialState(): WalletStateHolder = WalletPreviewData.walletScreenState.copy(
onBackClick = { router.popBackStack() },
headerConfig = WalletPreviewData.walletScreenState.headerConfig.copy(
onScanCardClick = { router.openOrganizeTokensScreen() },
),
)
}