Updated on 2026-08-14
This commit is contained in:
parent
888d26029b
commit
e1ef1c4b8a
10 changed files with 352 additions and 8 deletions
|
|
@ -5,6 +5,7 @@ import com.tangem.core.decompose.context.AppComponentContext
|
|||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.feature.referral.ReferralFragment
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.disclaimer.api.DisclaimerRouter
|
||||
|
|
@ -44,6 +45,7 @@ import javax.inject.Inject
|
|||
@Suppress("LongParameterList")
|
||||
internal class ChildFactory @Inject constructor(
|
||||
private val detailsComponentFactory: DetailsComponent.Factory,
|
||||
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
|
||||
private val sendRouter: SendRouter,
|
||||
private val tokenDetailsRouter: TokenDetailsRouter,
|
||||
private val walletRouter: WalletRouter,
|
||||
|
|
@ -160,6 +162,13 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.PushNotification -> {
|
||||
route.asFragmentChild(Provider { pushNotificationRouter.entryFragment() })
|
||||
}
|
||||
is AppRoute.WalletSettings -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = WalletSettingsComponent.Params(route.userWalletId),
|
||||
componentFactory = walletSettingsComponentFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -200,4 +200,9 @@ sealed class AppRoute(val path: String) : Route {
|
|||
|
||||
@Serializable
|
||||
data object PushNotification : AppRoute(path = "/push_notification")
|
||||
|
||||
@Serializable
|
||||
data class WalletSettings(
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/wallet_settings/${userWalletId.stringValue}")
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ dependencies {
|
|||
// endregion
|
||||
|
||||
// region Domain modules
|
||||
api(projects.domain.core)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.models)
|
||||
|
|
@ -28,9 +29,4 @@ dependencies {
|
|||
implementation(deps.tangem.blockchain) // android-library
|
||||
implementation(deps.tangem.card.core)
|
||||
// endregion
|
||||
|
||||
// region Other libraries
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -1,12 +1,17 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.GetUserWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
|
||||
class GetUserWalletUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
|
|
@ -17,4 +22,13 @@ class GetUserWalletUseCase(private val userWalletsListManager: UserWalletsListMa
|
|||
raise(GetUserWalletError.UserWalletNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun invokeFlow(userWalletId: UserWalletId): EitherFlow<GetUserWalletError, UserWallet> {
|
||||
return userWalletsListManager.userWallets.transformLatest { userWallets ->
|
||||
userWallets.firstOrNull { it.walletId == userWalletId }
|
||||
?.let { emit(it.right()) }
|
||||
?: emit(GetUserWalletError.UserWalletNotFound.left())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.routing.AppRoute
|
||||
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.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
|
||||
|
|
@ -30,6 +31,7 @@ internal class UserWalletsFetcher @Inject constructor(
|
|||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val router: Router,
|
||||
private val messageSender: UiMessageSender,
|
||||
) {
|
||||
|
||||
|
|
@ -82,8 +84,7 @@ internal class UserWalletsFetcher @Inject constructor(
|
|||
}
|
||||
|
||||
private fun navigateToWalletSettings(userWalletId: UserWalletId) {
|
||||
val message = stringReference("Wallet settings have not yet been implemented: $userWalletId")
|
||||
messageSender.send(SnackbarMessage(message))
|
||||
router.push(AppRoute.WalletSettings(userWalletId))
|
||||
}
|
||||
|
||||
sealed class Error {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
package com.tangem.feature.walletsettings.component.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.wallets.models.UpdateWalletError
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.RenameWalletUseCase
|
||||
import com.tangem.feature.walletsettings.component.RenameWalletComponent
|
||||
import com.tangem.feature.walletsettings.entity.RenameWalletUM
|
||||
import com.tangem.feature.walletsettings.impl.R
|
||||
import com.tangem.feature.walletsettings.ui.RenameWalletDialog
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class DefaultRenameWalletComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted params: RenameWalletComponent.Params,
|
||||
private val renameWalletUseCase: RenameWalletUseCase,
|
||||
) : RenameWalletComponent, AppComponentContext by context {
|
||||
|
||||
private val userWalletId = params.userWalletId
|
||||
private val currentWalletName = params.currentName
|
||||
|
||||
override val doOnDismiss: () -> Unit = params.onDismiss
|
||||
|
||||
private val stateFlow: MutableStateFlow<RenameWalletUM> = MutableStateFlow(
|
||||
value = RenameWalletUM(
|
||||
walletNameValue = TextFieldValue(text = params.currentName),
|
||||
isNameCorrect = false,
|
||||
updateValue = ::updateValue,
|
||||
onConfirm = { renameWallet(params.userWalletId) },
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Dialog() {
|
||||
val model by stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
RenameWalletDialog(
|
||||
model = model,
|
||||
onDismiss = doOnDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateValue(value: TextFieldValue) {
|
||||
stateFlow.update {
|
||||
it.copy(
|
||||
walletNameValue = value,
|
||||
isNameCorrect = value.text.isNotBlank() && value.text != currentWalletName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renameWallet(userWalletId: UserWalletId) = componentScope.launch {
|
||||
val newName = stateFlow.value.walletNameValue
|
||||
val maybeError = renameWalletUseCase(userWalletId, newName.text).leftOrNull()
|
||||
|
||||
if (maybeError != null) {
|
||||
val message = when (maybeError) {
|
||||
UpdateWalletError.DataError -> resourceReference(
|
||||
id = R.string.common_unknown_error,
|
||||
)
|
||||
UpdateWalletError.NameAlreadyExists -> resourceReference(
|
||||
id = R.string.user_wallet_list_rename_popup_error_already_exists,
|
||||
formatArgs = wrappedList(newName),
|
||||
)
|
||||
}
|
||||
|
||||
messageSender.send(message = SnackbarMessage(message))
|
||||
}
|
||||
|
||||
doOnDismiss()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : RenameWalletComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: RenameWalletComponent.Params,
|
||||
): DefaultRenameWalletComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.feature.walletsettings.component.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableDialogComponent
|
||||
import com.tangem.feature.walletsettings.component.RenameWalletComponent
|
||||
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
|
||||
import com.tangem.feature.walletsettings.entity.DialogConfig
|
||||
import com.tangem.feature.walletsettings.model.WalletSettingsModel
|
||||
import com.tangem.feature.walletsettings.ui.WalletSettingsScreen
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultWalletSettingsComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted params: WalletSettingsComponent.Params,
|
||||
private val renameWalletComponentFactory: RenameWalletComponent.Factory,
|
||||
) : WalletSettingsComponent, AppComponentContext by context {
|
||||
|
||||
private val model: WalletSettingsModel = getOrCreateModel(params)
|
||||
|
||||
private val dialog = childSlot(
|
||||
source = model.dialogNavigation,
|
||||
serializer = DialogConfig.serializer(),
|
||||
handleBackButton = true,
|
||||
childFactory = ::dialogChild,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val dialog by dialog.subscribeAsState()
|
||||
|
||||
WalletSettingsScreen(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
dialog = { dialog.child?.instance?.Dialog() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun dialogChild(
|
||||
dialogConfig: DialogConfig,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableDialogComponent = when (dialogConfig) {
|
||||
is DialogConfig.RenameWallet -> {
|
||||
renameWalletComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = RenameWalletComponent.Params(
|
||||
userWalletId = dialogConfig.userWalletId,
|
||||
currentName = dialogConfig.currentName,
|
||||
onDismiss = model.dialogNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : WalletSettingsComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: WalletSettingsComponent.Params,
|
||||
): DefaultWalletSettingsComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.feature.walletsettings.di
|
||||
|
||||
import com.tangem.feature.walletsettings.component.RenameWalletComponent
|
||||
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
|
||||
import com.tangem.feature.walletsettings.component.impl.DefaultRenameWalletComponent
|
||||
import com.tangem.feature.walletsettings.component.impl.DefaultWalletSettingsComponent
|
||||
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 bindWalletSettingsComponentFactory(
|
||||
factory: DefaultWalletSettingsComponent.Factory,
|
||||
): WalletSettingsComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindRenameWalletComponentFactory(factory: DefaultRenameWalletComponent.Factory): RenameWalletComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.feature.walletsettings.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.feature.walletsettings.model.WalletSettingsModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface ModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(WalletSettingsModel::class)
|
||||
fun provideWalletSettingsModel(model: WalletSettingsModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.tangem.feature.walletsettings.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
|
||||
import com.tangem.feature.walletsettings.entity.DialogConfig
|
||||
import com.tangem.feature.walletsettings.entity.WalletSettingsItemUM
|
||||
import com.tangem.feature.walletsettings.entity.WalletSettingsUM
|
||||
import com.tangem.feature.walletsettings.impl.R
|
||||
import com.tangem.feature.walletsettings.utils.ItemsBuilder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ComponentScoped
|
||||
internal class WalletSettingsModel @Inject constructor(
|
||||
private val router: Router,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val getWalletUseCase: GetUserWalletUseCase,
|
||||
private val deleteWalletUseCase: DeleteWalletUseCase,
|
||||
private val itemsBuilder: ItemsBuilder,
|
||||
private val paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
val params: WalletSettingsComponent.Params = paramsContainer.require()
|
||||
val dialogNavigation = SlotNavigation<DialogConfig>()
|
||||
|
||||
val state: MutableStateFlow<WalletSettingsUM> = MutableStateFlow(
|
||||
value = WalletSettingsUM(
|
||||
popBack = router::pop,
|
||||
items = persistentListOf(),
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
getWalletUseCase.invokeFlow(params.userWalletId)
|
||||
.distinctUntilChanged()
|
||||
.onEach { maybeWallet ->
|
||||
val wallet = maybeWallet.getOrElse { error ->
|
||||
error(
|
||||
"""
|
||||
Failed to get user wallet
|
||||
|- User wallet ID: $params
|
||||
|- Cause: $error
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
|
||||
state.update { value ->
|
||||
value.copy(items = buildItems(wallet, dialogNavigation))
|
||||
}
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun buildItems(
|
||||
userWallet: UserWallet,
|
||||
dialogNavigation: SlotNavigation<DialogConfig>,
|
||||
): PersistentList<WalletSettingsItemUM> = itemsBuilder.buildItems(
|
||||
walletName = userWallet.name,
|
||||
renameWallet = { openRenameWalletDialog(userWallet, dialogNavigation) },
|
||||
forgetWallet = { forgetWallet(userWallet.walletId) },
|
||||
)
|
||||
|
||||
private fun openRenameWalletDialog(userWallet: UserWallet, dialogNavigation: SlotNavigation<DialogConfig>) {
|
||||
val config = DialogConfig.RenameWallet(
|
||||
userWalletId = userWallet.walletId,
|
||||
currentName = userWallet.name,
|
||||
)
|
||||
|
||||
dialogNavigation.activate(config)
|
||||
}
|
||||
|
||||
private fun forgetWallet(userWalletId: UserWalletId) = modelScope.launch {
|
||||
val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse {
|
||||
messageSender.send(
|
||||
message = SnackbarMessage(resourceReference(R.string.common_unknown_error)),
|
||||
)
|
||||
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (hasUserWallets) {
|
||||
router.pop()
|
||||
} else {
|
||||
router.replaceAll(AppRoute.Home)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue