Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-17 13:40:04 +04:00
parent 888d26029b
commit e1ef1c4b8a
10 changed files with 352 additions and 8 deletions

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}
}