Updated on 2026-08-14
This commit is contained in:
parent
0e29df4ce9
commit
199b7d3743
27 changed files with 774 additions and 135 deletions
|
|
@ -25,9 +25,21 @@ dependencies {
|
|||
implementation(projects.core.analytics.models)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.walletConnect)
|
||||
implementation(projects.domain.legacy)
|
||||
|
||||
/* SDK */
|
||||
// TODO: For TangemError model, should be removed after card domain scanning refactoring
|
||||
implementation(deps.tangem.card.core)
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.androidx.activity.compose)
|
||||
|
|
@ -47,4 +59,5 @@ dependencies {
|
|||
|
||||
/* Other */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.timber)
|
||||
}
|
||||
|
|
@ -3,14 +3,16 @@ 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 com.tangem.features.details.component.preview.PreviewDetailsComponent
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -21,8 +23,8 @@ internal class DetailsFragment : ComposeFragment() {
|
|||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
// @Inject
|
||||
// internal lateinit var componentFactory: DetailsComponent.Factory
|
||||
@Inject
|
||||
internal lateinit var componentFactory: DetailsComponent.Factory
|
||||
|
||||
@Inject
|
||||
internal lateinit var detailsRouter: DetailsRouter
|
||||
|
|
@ -37,7 +39,7 @@ internal class DetailsFragment : ComposeFragment() {
|
|||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
component.View(modifier = modifier)
|
||||
component.Content(modifier = modifier)
|
||||
|
||||
EventMessageEffect(
|
||||
messageHandler = messageHandler,
|
||||
|
|
@ -46,27 +48,23 @@ internal class DetailsFragment : ComposeFragment() {
|
|||
}
|
||||
|
||||
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,
|
||||
// ),
|
||||
// )
|
||||
val selectedUserWalletId = arguments?.getString(DetailsEntryPoint.USER_WALLET_ID_KEY)
|
||||
?.let(::UserWalletId)
|
||||
|
||||
return PreviewDetailsComponent()
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
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
|
||||
import com.tangem.core.ui.ComposableContentProvider
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface DetailsComponent {
|
||||
interface DetailsComponent : ComposableContentProvider {
|
||||
|
||||
val snackbarHostState: SnackbarHostState
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
fun View(modifier: Modifier)
|
||||
|
||||
interface Factory {
|
||||
|
||||
fun create(context: AppComponentContext, params: Params): DetailsComponent
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
package com.tangem.features.details.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.ComposableContentProvider
|
||||
|
||||
interface UserWalletListComponent {
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
fun View(modifier: Modifier)
|
||||
interface UserWalletListComponent : ComposableContentProvider {
|
||||
|
||||
interface Factory {
|
||||
fun create(context: AppComponentContext): UserWalletListComponent
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.features.details.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface WalletConnectComponent {
|
||||
|
||||
suspend fun checkIsAvailable(): Boolean
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
fun View(modifier: Modifier)
|
||||
|
||||
interface Factory {
|
||||
fun create(context: AppComponentContext, params: Params): WalletConnectComponent
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
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
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
import com.tangem.features.details.model.DetailsModel
|
||||
import com.tangem.features.details.ui.DetailsScreen
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultDetailsComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: DetailsComponent.Params,
|
||||
userWalletListComponentFactory: UserWalletListComponent.Factory,
|
||||
) : DetailsComponent, AppComponentContext by context {
|
||||
|
||||
private val model: DetailsModel = getOrCreateModel()
|
||||
|
||||
private val userWalletListComponent = userWalletListComponentFactory.create(
|
||||
context = child(key = "user_wallet_list"),
|
||||
)
|
||||
|
||||
override val snackbarHostState: SnackbarHostState = SnackbarHostState()
|
||||
|
||||
init {
|
||||
model.provideUserWalletId(params.selectedUserWalletId)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
DetailsScreen(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
snackbarHostState = snackbarHostState,
|
||||
userWalletListBlockContent = userWalletListComponent,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : DetailsComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: DetailsComponent.Params): DefaultDetailsComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.features.details.component.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
import com.tangem.features.details.model.UserWalletListModel
|
||||
import com.tangem.features.details.ui.UserWalletListBlock
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultUserWalletListComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
) : UserWalletListComponent, AppComponentContext by context {
|
||||
|
||||
private val model: UserWalletListModel = getOrCreateModel()
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
UserWalletListBlock(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : UserWalletListComponent.Factory {
|
||||
|
||||
override fun create(context: AppComponentContext): DefaultUserWalletListComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -17,10 +17,8 @@ internal class PreviewDetailsComponent : DetailsComponent {
|
|||
|
||||
private val previewBlocks = runBlocking {
|
||||
ItemsBuilder(
|
||||
walletConnectComponent = PreviewWalletConnectComponent(),
|
||||
userWalletListComponent = PreviewUserWalletListComponent(),
|
||||
router = PreviewRouter(),
|
||||
).buldAll()
|
||||
).buldAll(isWalletConnectAvailable = true)
|
||||
}
|
||||
|
||||
private val previewFooter = DetailsFooterUM(
|
||||
|
|
@ -36,11 +34,12 @@ internal class PreviewDetailsComponent : DetailsComponent {
|
|||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
override fun View(modifier: Modifier) {
|
||||
override fun Content(modifier: Modifier) {
|
||||
DetailsScreen(
|
||||
modifier = modifier,
|
||||
state = previewState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
userWalletListBlockContent = PreviewUserWalletListComponent(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
|
|||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
override fun View(modifier: Modifier) {
|
||||
override fun Content(modifier: Modifier) {
|
||||
UserWalletListBlock(state = previewState, modifier = modifier)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.features.details.component.preview
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.features.details.component.WalletConnectComponent
|
||||
import com.tangem.features.details.ui.WalletConnectBlock
|
||||
|
||||
internal class PreviewWalletConnectComponent : WalletConnectComponent {
|
||||
|
||||
override suspend fun checkIsAvailable(): Boolean = true
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
override fun View(modifier: Modifier) {
|
||||
WalletConnectBlock(onClick = { /* no-op */ }, modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.details.di
|
||||
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
import com.tangem.features.details.component.impl.DefaultDetailsComponent
|
||||
import com.tangem.features.details.component.impl.DefaultUserWalletListComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface ComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindDetailsComponentFactory(factory: DefaultDetailsComponent.Factory): DetailsComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindUserWalletListComponentFactory(
|
||||
factory: DefaultUserWalletListComponent.Factory,
|
||||
): UserWalletListComponent.Factory
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.details.di
|
|||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.details.model.DetailsModel
|
||||
import com.tangem.features.details.model.UserWalletListModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -17,4 +18,9 @@ internal interface ModelModule {
|
|||
@IntoMap
|
||||
@ClassKey(DetailsModel::class)
|
||||
fun provideDetailsModel(model: DetailsModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(UserWalletListModel::class)
|
||||
fun provideUserWalletListModel(model: UserWalletListModel): Model
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.features.details.entity
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal data class DetailsFooterUM(
|
||||
val appVersion: String,
|
||||
val socials: ImmutableList<Social>,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.features.details.entity
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
@ -26,16 +24,11 @@ internal sealed class DetailsItemUM {
|
|||
)
|
||||
}
|
||||
|
||||
data class Component(
|
||||
override val id: String,
|
||||
val content: Content,
|
||||
) : DetailsItemUM() {
|
||||
data class WalletConnect(val onClick: () -> Unit) : DetailsItemUM() {
|
||||
override val id: String = "wallet_connect"
|
||||
}
|
||||
|
||||
fun interface Content {
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions", "ComposableFunctionName")
|
||||
operator fun invoke(modifier: Modifier)
|
||||
}
|
||||
data object UserWalletList : DetailsItemUM() {
|
||||
override val id: String = "user_wallet_list"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,74 @@
|
|||
package com.tangem.features.details.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
import com.tangem.features.details.entity.DetailsItemUM
|
||||
import com.tangem.features.details.entity.DetailsUM
|
||||
import com.tangem.features.details.utils.ItemsBuilder
|
||||
import com.tangem.features.details.utils.SocialsBuilder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Will be implemented later
|
||||
@ComponentScoped
|
||||
internal class DetailsModel @Inject constructor(
|
||||
private val socialsBuilder: SocialsBuilder,
|
||||
private val itemsBuilder: ItemsBuilder,
|
||||
private val appVersionProvider: AppVersionProvider,
|
||||
private val checkIsWalletConnectAvailableUseCase: CheckIsWalletConnectAvailableUseCase,
|
||||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model()
|
||||
) : Model() {
|
||||
|
||||
private val items: MutableStateFlow<ImmutableList<DetailsItemUM>> = MutableStateFlow(value = persistentListOf())
|
||||
|
||||
val state: MutableStateFlow<DetailsUM> = MutableStateFlow(
|
||||
value = DetailsUM(
|
||||
items = items.value,
|
||||
footer = DetailsFooterUM(
|
||||
socials = socialsBuilder.buildAll(),
|
||||
appVersion = getAppVersion(),
|
||||
),
|
||||
popBack = router::pop,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
items
|
||||
.onEach(::updateState)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
fun provideUserWalletId(userWalletId: UserWalletId) = modelScope.launch {
|
||||
val isWalletConnectAvailable = checkIsWalletConnectAvailableUseCase(userWalletId).getOrElse {
|
||||
Timber.w("Unable to check WalletConnect availability: $it")
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
items.value = itemsBuilder.buldAll(isWalletConnectAvailable)
|
||||
}
|
||||
|
||||
private suspend fun updateState(items: ImmutableList<DetailsItemUM>) {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
items = items,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAppVersion(): String = "${appVersionProvider.versionName} (${appVersionProvider.versionCode})"
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.features.details.model
|
||||
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.features.details.entity.UserWalletListUM
|
||||
import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.utils.UserWalletSaver
|
||||
import com.tangem.features.details.utils.UserWalletsFetcher
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
internal class UserWalletListModel @Inject constructor(
|
||||
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
|
||||
private val userWalletSaver: UserWalletSaver,
|
||||
private val userWalletsFetcher: UserWalletsFetcher,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val isWalletSavingInProgress: MutableStateFlow<Boolean> = MutableStateFlow(value = false)
|
||||
|
||||
private val userWallets: SharedFlow<ImmutableList<UserWalletUM>> = userWalletsFetcher
|
||||
.userWallets
|
||||
.share()
|
||||
|
||||
private val shouldSaveUserWallets: SharedFlow<Boolean> = shouldSaveUserWalletsUseCase()
|
||||
.distinctUntilChanged()
|
||||
.share()
|
||||
|
||||
val state: MutableStateFlow<UserWalletListUM> = MutableStateFlow(
|
||||
value = UserWalletListUM(
|
||||
userWallets = persistentListOf(),
|
||||
isWalletSavingInProgress = false,
|
||||
addNewWalletText = TextReference.EMPTY,
|
||||
onAddNewWalletClick = ::addUserWallet,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
combine(
|
||||
userWallets,
|
||||
shouldSaveUserWallets,
|
||||
isWalletSavingInProgress,
|
||||
transform = ::updateState,
|
||||
).launchIn(modelScope)
|
||||
}
|
||||
|
||||
private suspend fun updateState(
|
||||
userWallets: ImmutableList<UserWalletUM>,
|
||||
shouldSaveUserWallets: Boolean,
|
||||
isWalletSavingInProgress: Boolean,
|
||||
) = state.update { value ->
|
||||
value.copy(
|
||||
userWallets = userWallets,
|
||||
isWalletSavingInProgress = isWalletSavingInProgress,
|
||||
addNewWalletText = if (shouldSaveUserWallets) {
|
||||
resourceReference(R.string.user_wallet_list_add_button)
|
||||
} else {
|
||||
resourceReference(R.string.scan_card_settings_button)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun addUserWallet() = withProgress(isWalletSavingInProgress) {
|
||||
userWalletSaver.scanAndSaveUserWallet()
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.res.painterResource
|
|||
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.ComposableContentProvider
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -32,7 +33,12 @@ private const val COLLAPSED_APP_BAR_THRESHOLD = 0.4f
|
|||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun DetailsScreen(state: DetailsUM, snackbarHostState: SnackbarHostState, modifier: Modifier = Modifier) {
|
||||
internal fun DetailsScreen(
|
||||
state: DetailsUM,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
userWalletListBlockContent: ComposableContentProvider,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val backgroundColor = TangemTheme.colors.background.secondary
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
|
||||
|
|
@ -56,6 +62,7 @@ internal fun DetailsScreen(state: DetailsUM, snackbarHostState: SnackbarHostStat
|
|||
Content(
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
state = state,
|
||||
userWalletListBlockContent = userWalletListBlockContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +116,11 @@ private fun TopBar(state: DetailsUM, scrollBehavior: TopAppBarScrollBehavior, mo
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: DetailsUM, modifier: Modifier = Modifier) {
|
||||
private fun Content(
|
||||
state: DetailsUM,
|
||||
userWalletListBlockContent: ComposableContentProvider,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
|
|
@ -125,6 +136,7 @@ private fun Content(state: DetailsUM, modifier: Modifier = Modifier) {
|
|||
Block(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
model = block,
|
||||
userWalletListBlockContent = userWalletListBlockContent,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +150,11 @@ private fun Content(state: DetailsUM, modifier: Modifier = Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Block(model: DetailsItemUM, modifier: Modifier = Modifier) {
|
||||
private fun Block(
|
||||
model: DetailsItemUM,
|
||||
userWalletListBlockContent: ComposableContentProvider,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -149,22 +165,28 @@ private fun Block(model: DetailsItemUM, modifier: Modifier = Modifier) {
|
|||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
) {
|
||||
val itemModifier = Modifier.fillMaxWidth()
|
||||
|
||||
when (model) {
|
||||
is DetailsItemUM.Basic -> {
|
||||
model.items.forEach { item ->
|
||||
key(item.id) {
|
||||
BlockItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = itemModifier,
|
||||
model = item,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is DetailsItemUM.Component -> {
|
||||
model.content(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
is DetailsItemUM.WalletConnect -> {
|
||||
WalletConnectBlock(
|
||||
modifier = itemModifier,
|
||||
onClick = model.onClick,
|
||||
)
|
||||
}
|
||||
is DetailsItemUM.UserWalletList -> {
|
||||
userWalletListBlockContent.Content(modifier = itemModifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -222,7 +244,7 @@ private fun Footer(model: DetailsFooterUM, modifier: Modifier = Modifier) {
|
|||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview_DetailsScreen() {
|
||||
TangemThemePreview {
|
||||
PreviewDetailsComponent().View(modifier = Modifier.fillMaxSize())
|
||||
PreviewDetailsComponent().Content(modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.features.details.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -104,12 +106,24 @@ private fun AddWalletButton(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Icon(
|
||||
AnimatedContent(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_plus_24),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
targetState = isInProgress,
|
||||
) { isInProgress ->
|
||||
if (isInProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_plus_24),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
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
|
||||
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
|
||||
|
|
@ -13,47 +12,39 @@ import com.tangem.features.details.routing.DetailsRoute
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ItemsBuilder(
|
||||
private val walletConnectComponent: WalletConnectComponent,
|
||||
private val userWalletListComponent: UserWalletListComponent,
|
||||
@ComponentScoped
|
||||
internal class ItemsBuilder @Inject constructor(
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
suspend fun buldAll(): ImmutableList<DetailsItemUM> = buildList {
|
||||
buildWalletConnectBlock()?.let(::add)
|
||||
suspend fun buldAll(isWalletConnectAvailable: Boolean): ImmutableList<DetailsItemUM> = buildList {
|
||||
buildWalletConnectBlock(isWalletConnectAvailable)?.let(::add)
|
||||
buildUserWalletListBlock().let(::add)
|
||||
buildShopBlock().let(::add)
|
||||
buildSettingsBlock().let(::add)
|
||||
buildSupportBlock().let(::add)
|
||||
}.toImmutableList()
|
||||
|
||||
private suspend fun buildWalletConnectBlock(): DetailsItemUM? {
|
||||
return if (walletConnectComponent.checkIsAvailable()) {
|
||||
DetailsItemUM.Component(
|
||||
id = "wallet_connect",
|
||||
content = {
|
||||
walletConnectComponent.View(modifier = it)
|
||||
},
|
||||
private suspend fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean): DetailsItemUM? {
|
||||
return if (isWalletConnectAvailable) {
|
||||
DetailsItemUM.WalletConnect(
|
||||
onClick = { router.push(DetailsRoute.Screen(AppScreen.WalletConnectSessions)) },
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildUserWalletListBlock(): DetailsItemUM = DetailsItemUM.Component(
|
||||
id = "user_wallet_list",
|
||||
content = {
|
||||
userWalletListComponent.View(modifier = it)
|
||||
},
|
||||
)
|
||||
private fun buildUserWalletListBlock(): DetailsItemUM = DetailsItemUM.UserWalletList
|
||||
|
||||
private fun buildShopBlock(): DetailsItemUM = DetailsItemUM.Basic(
|
||||
id = "shop",
|
||||
items = persistentListOf(
|
||||
DetailsItemUM.Basic.Item(
|
||||
id = "buy_tangem_wallet",
|
||||
title = stringReference("Buy Tangem Wallet"), // TODO: Move to resources in [REDACTED_TASK_KEY]
|
||||
title = resourceReference(R.string.details_buy_wallet),
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
onClick = { router.push(DetailsRoute.Url(BUY_TANGEM_URL)) },
|
||||
),
|
||||
|
|
@ -86,7 +77,7 @@ internal class ItemsBuilder(
|
|||
items = persistentListOf(
|
||||
DetailsItemUM.Basic.Item(
|
||||
id = "send_feedback",
|
||||
title = stringReference("Send feedback"), // TODO: Move to resources in [REDACTED_TASK_KEY]
|
||||
title = resourceReference(R.string.details_send_feedback),
|
||||
iconRes = R.drawable.ic_comment_24,
|
||||
onClick = { router.push(DetailsRoute.Feedback) },
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
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.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
|
||||
|
||||
internal class SocialsBuilder(
|
||||
@ComponentScoped
|
||||
internal class SocialsBuilder @Inject constructor(
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal fun List<UserWallet>.toUiModels(
|
||||
onClick: (UserWalletId) -> Unit,
|
||||
appCurrency: AppCurrency? = null,
|
||||
balances: Map<UserWalletId, TotalFiatBalance> = emptyMap(),
|
||||
): ImmutableList<UserWalletUM> = this.map { model ->
|
||||
val balance = balances[model.walletId]
|
||||
model.mapToUiModel(
|
||||
balance = balance,
|
||||
appCurrency = appCurrency,
|
||||
onClick = { onClick(model.walletId) },
|
||||
)
|
||||
}.toImmutableList()
|
||||
|
||||
private fun UserWallet.mapToUiModel(
|
||||
balance: TotalFiatBalance?,
|
||||
appCurrency: AppCurrency?,
|
||||
onClick: () -> Unit,
|
||||
): UserWalletUM = UserWalletUM(
|
||||
id = walletId,
|
||||
name = name,
|
||||
information = getInfo(appCurrency, balance),
|
||||
imageResId = resolveImage(),
|
||||
onClick = onClick,
|
||||
)
|
||||
|
||||
private fun UserWallet.getInfo(appCurrency: AppCurrency?, balance: TotalFiatBalance?): TextReference {
|
||||
val cardCount = getCardCount()
|
||||
val cardCountRef = TextReference.PluralRes(
|
||||
id = R.plurals.card_label_card_count,
|
||||
count = cardCount,
|
||||
formatArgs = wrappedList(cardCount),
|
||||
)
|
||||
val amount = when (balance) {
|
||||
is TotalFiatBalance.Loaded -> balance.amount.takeIf { balance.isAllAmountsSummarized }
|
||||
is TotalFiatBalance.Failed,
|
||||
is TotalFiatBalance.Loading,
|
||||
null,
|
||||
-> null
|
||||
}
|
||||
|
||||
return if (amount != null && appCurrency != null) {
|
||||
val divider = stringReference(value = " • ")
|
||||
val formattedAmount = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = amount,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
val amountRef = stringReference(formattedAmount)
|
||||
TextReference.Combined(wrappedList(cardCountRef, divider, amountRef))
|
||||
} else {
|
||||
cardCountRef
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserWallet.getCardCount() = when (val status = scanResponse.card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount.inc()
|
||||
is CardDTO.BackupStatus.CardLinked -> status.cardCount.inc()
|
||||
is CardDTO.BackupStatus.NoBackup,
|
||||
null,
|
||||
-> 1
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
private fun UserWallet.resolveImage(): Int {
|
||||
// TODO: Implement image resolving [REDACTED_JIRA]
|
||||
return R.drawable.ill_card_wallet_2_211_343
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import arrow.core.raise.*
|
||||
import arrow.core.recover
|
||||
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.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.isNullOrEmpty
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.SaveWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.routing.DetailsRoute
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
@Suppress("LongParameterList")
|
||||
internal class UserWalletSaver @Inject constructor(
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val selectWalletUseCase: SelectWalletUseCase,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
suspend fun scanAndSaveUserWallet() = recover(
|
||||
block = {
|
||||
val response = scanCard()
|
||||
val userWallet = createUserWallet(response)
|
||||
|
||||
saveWallet(userWallet)
|
||||
|
||||
router.popTo(DetailsRoute.Screen(AppScreen.Wallet))
|
||||
},
|
||||
recover = { error ->
|
||||
val message = error.message
|
||||
|
||||
if (!message.isNullOrEmpty()) {
|
||||
messageSender.send(SnackbarMessage(message))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
private suspend fun Raise<Error>.saveWallet(userWallet: UserWallet) {
|
||||
saveWalletUseCase(userWallet).recover { error ->
|
||||
when (error) {
|
||||
is SaveWalletError.WalletAlreadySaved -> selectUserWallet(userWallet)
|
||||
is SaveWalletError.DataError -> {
|
||||
val messageRef = ensureNotNull(error.messageId?.let(::resourceReference)) {
|
||||
Error.Unkonwn
|
||||
}
|
||||
|
||||
raise(Error.Message(messageRef))
|
||||
}
|
||||
}
|
||||
}.bind()
|
||||
|
||||
reduxStateHolder.onUserWalletSelected(userWallet)
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.selectUserWallet(userWallet: UserWallet) {
|
||||
withError({ Error.Unkonwn }) {
|
||||
selectWalletUseCase(userWallet.walletId).bind()
|
||||
}
|
||||
|
||||
router.popTo(DetailsRoute.Screen(AppScreen.Wallet))
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.createUserWallet(response: ScanResponse): UserWallet {
|
||||
val userWallet = UserWalletBuilder(response, generateWalletNameUseCase).build()
|
||||
|
||||
return ensureNotNull(userWallet) { Error.Unkonwn }
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.scanCard(): ScanResponse {
|
||||
var response: ScanResponse? = null
|
||||
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = AnalyticsParam.ScreensSources.Settings,
|
||||
onWalletNotCreated = {
|
||||
raise(Error.WalletNotCreated)
|
||||
},
|
||||
disclaimerWillShow = {
|
||||
router.pop()
|
||||
raise(Error.DisclaimerWillShow)
|
||||
},
|
||||
onSuccess = {
|
||||
response = it
|
||||
},
|
||||
onFailure = { tangemError ->
|
||||
val error = if (!tangemError.silent) {
|
||||
val message = tangemError.messageResId
|
||||
?.let(::resourceReference)
|
||||
?: stringReference(tangemError.customMessage)
|
||||
|
||||
Error.Message(message)
|
||||
} else {
|
||||
Error.Silent
|
||||
}
|
||||
|
||||
raise(error)
|
||||
},
|
||||
)
|
||||
|
||||
return response!!
|
||||
}
|
||||
|
||||
sealed class Error {
|
||||
|
||||
open val message: TextReference? = null
|
||||
|
||||
data object WalletNotCreated : Error()
|
||||
|
||||
data object DisclaimerWillShow : Error()
|
||||
|
||||
data object Silent : Error()
|
||||
|
||||
data class Message(override val message: TextReference) : Error()
|
||||
|
||||
data object Unkonwn : Error() {
|
||||
|
||||
override val message: TextReference = resourceReference(R.string.common_unknown_error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.error.SelectedAppCurrencyError
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.lce
|
||||
import com.tangem.domain.core.utils.getOrElse
|
||||
import com.tangem.domain.core.utils.toLce
|
||||
import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
internal class UserWalletsFetcher @Inject constructor(
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
) {
|
||||
|
||||
val userWallets: Flow<ImmutableList<UserWalletUM>> = getWalletsUseCase()
|
||||
.distinctUntilChanged()
|
||||
.transform { wallets ->
|
||||
if (wallets.isEmpty()) {
|
||||
error("Wallets must not be empty")
|
||||
} else {
|
||||
emit(wallets.toUiModels(onClick = ::navigateToWalletSettings))
|
||||
}
|
||||
|
||||
combine(
|
||||
getSelectedAppCurrencyUseCase(),
|
||||
getWalletTotalBalanceUseCase(wallets.map(UserWallet::walletId)),
|
||||
) { maybeAppCurrency, maybeBalances ->
|
||||
val models = createUiModels(wallets, maybeAppCurrency, maybeBalances).getOrElse(
|
||||
ifLoading = { return@combine },
|
||||
ifError = {
|
||||
val message = resourceReference(R.string.common_unknown_error)
|
||||
messageSender.send(SnackbarMessage(message))
|
||||
|
||||
return@combine
|
||||
},
|
||||
)
|
||||
|
||||
emit(models)
|
||||
}.collect()
|
||||
}
|
||||
|
||||
private fun createUiModels(
|
||||
wallets: List<UserWallet>,
|
||||
maybeAppCurrency: Either<SelectedAppCurrencyError, AppCurrency>,
|
||||
maybeBalances: Lce<TokenListError, Map<UserWalletId, TotalFiatBalance>>,
|
||||
): Lce<Error, ImmutableList<UserWalletUM>> = lce {
|
||||
val balances = withError(
|
||||
transform = { Error.UnableToGetBalances },
|
||||
block = { maybeBalances.bind() },
|
||||
)
|
||||
val appCurrency = withError(
|
||||
transform = { Error.UnableToGetAppCurrency },
|
||||
block = { maybeAppCurrency.toLce().bind() },
|
||||
)
|
||||
|
||||
wallets.toUiModels(
|
||||
appCurrency = appCurrency,
|
||||
balances = balances,
|
||||
onClick = ::navigateToWalletSettings,
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateToWalletSettings(userWalletId: UserWalletId) {
|
||||
val message = stringReference("Wallet settings have not yet been implemented: $userWalletId")
|
||||
messageSender.send(SnackbarMessage(message))
|
||||
}
|
||||
|
||||
sealed class Error {
|
||||
|
||||
data object UnableToGetAppCurrency : Error()
|
||||
|
||||
data object UnableToGetBalances : Error()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue