Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-10 15:44:59 +04:00
parent c83081cc11
commit 1ca7f17e37
7 changed files with 144 additions and 89 deletions

View file

@ -2,110 +2,32 @@ 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.GetWalletNamesUseCase
import com.tangem.domain.wallets.usecase.RenameWalletUseCase
import com.tangem.core.decompose.model.getOrCreateModel
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.component.impl.model.RenameWalletModel
import com.tangem.feature.walletsettings.ui.RenameWalletDialog
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
internal class DefaultRenameWalletComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: RenameWalletComponent.Params,
getWalletNamesUseCase: GetWalletNamesUseCase,
private val renameWalletUseCase: RenameWalletUseCase,
) : RenameWalletComponent, AppComponentContext by context {
private val initialName = params.currentName
private val walletNames by lazy(getWalletNamesUseCase::invoke)
private val model: RenameWalletModel = getOrCreateModel(params)
private val stateFlow: MutableStateFlow<RenameWalletUM> = MutableStateFlow(
value = RenameWalletUM(
walletNameValue = TextFieldValue(text = params.currentName),
onValueChange = ::updateValue,
isConfirmEnabled = false,
onConfirm = { renameWallet(params.userWalletId) },
error = null,
),
)
override fun dismiss() {
params.onDismiss()
}
override fun dismiss() = model.dismiss()
@Composable
override fun Dialog() {
val model by stateFlow.collectAsStateWithLifecycle()
val model by model.state.collectAsStateWithLifecycle()
RenameWalletDialog(model = model, onDismiss = ::dismiss)
}
private fun updateValue(value: TextFieldValue) {
stateFlow.update {
val isNameAlreadyExists = initialName != value.text && walletNames.contains(value.text)
it.copy(
walletNameValue = value,
isConfirmEnabled = value.text.isNotBlank() &&
value.text != initialName &&
!isNameAlreadyExists,
error = if (isNameAlreadyExists) {
resourceReference(
id = R.string.user_wallet_list_rename_popup_error_already_exists,
formatArgs = wrappedList(value.text),
)
} else {
null
},
)
}
}
private fun renameWallet(userWalletId: UserWalletId) {
componentScope.launch {
withContext(NonCancellable) {
val newName = stateFlow.value.walletNameValue
renameWalletUseCase(userWalletId, newName.text)
.onLeft {
Timber.e("Unable to rename wallet: $it")
showRenameWalletError(error = it, updatedName = newName.text)
}
}
}
dismiss()
}
private fun showRenameWalletError(error: UpdateWalletError, updatedName: String) {
val message = when (error) {
is UpdateWalletError.DataError -> resourceReference(id = R.string.common_unknown_error)
is UpdateWalletError.NameAlreadyExists -> resourceReference(
id = R.string.user_wallet_list_rename_popup_error_already_exists,
formatArgs = wrappedList(updatedName),
)
}
messageSender.send(message = SnackbarMessage(message))
}
@AssistedFactory
interface Factory : RenameWalletComponent.Factory {
override fun create(

View file

@ -0,0 +1,127 @@
package com.tangem.feature.walletsettings.component.impl.model
import androidx.compose.ui.text.input.TextFieldValue
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.ui.UiMessageSender
import com.tangem.core.ui.extensions.TextReference
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.usecase.GetWalletNamesUseCase
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.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
/**
* Rename wallet model
*
* @param paramsContainer params container
* @param getWalletNamesUseCase use case for getting wallets names
* @property dispatchers dispatchers
* @property renameWalletUseCase use case for renaming the wallet by id
* @property uiMessageSender UI message sender
*
[REDACTED_AUTHOR]
*/
@ComponentScoped
internal class RenameWalletModel @Inject constructor(
paramsContainer: ParamsContainer,
getWalletNamesUseCase: GetWalletNamesUseCase,
override val dispatchers: CoroutineDispatcherProvider,
private val renameWalletUseCase: RenameWalletUseCase,
private val uiMessageSender: UiMessageSender,
) : Model() {
private val params: RenameWalletComponent.Params = paramsContainer.require()
private val initialName = params.currentName
private val walletNames by lazy(getWalletNamesUseCase::invoke)
val state: StateFlow<RenameWalletUM> get() = _state
private val _state = MutableStateFlow(
value = RenameWalletUM(
walletNameValue = TextFieldValue(text = params.currentName),
onValueChange = ::onValueChange,
isConfirmEnabled = false,
onConfirmClick = ::onConfirmClick,
error = null,
),
)
fun dismiss() {
params.onDismiss()
}
private fun onValueChange(value: TextFieldValue) {
_state.update {
val newName = value.text
it.copy(
walletNameValue = value,
isConfirmEnabled = getConfirmButtonAvailability(newName),
error = getErrorOrNull(newName),
)
}
}
private fun getConfirmButtonAvailability(newName: String): Boolean {
return newName.isNotBlank() && newName != initialName && !isNameAlreadyExists(newName)
}
private fun getErrorOrNull(newName: String): TextReference? {
return if (isNameAlreadyExists(newName)) {
resourceReference(
id = R.string.user_wallet_list_rename_popup_error_already_exists,
formatArgs = wrappedList(newName),
)
} else {
null
}
}
private fun isNameAlreadyExists(newName: String): Boolean {
return initialName != newName && walletNames.contains(newName)
}
private fun onConfirmClick() {
modelScope.launch {
withContext(NonCancellable) {
val newName = _state.value.walletNameValue
renameWalletUseCase(userWalletId = params.userWalletId, name = newName.text)
.onLeft {
Timber.e("Unable to rename wallet: $it")
showRenameWalletError(error = it, updatedName = newName.text)
}
}
}
dismiss()
}
private fun showRenameWalletError(error: UpdateWalletError, updatedName: String) {
val message = when (error) {
is UpdateWalletError.DataError -> resourceReference(id = R.string.common_unknown_error)
is UpdateWalletError.NameAlreadyExists -> resourceReference(
id = R.string.user_wallet_list_rename_popup_error_already_exists,
formatArgs = wrappedList(updatedName),
)
}
uiMessageSender.send(message = SnackbarMessage(message))
}
}

View file

@ -12,7 +12,7 @@ internal class PreviewRenameWalletComponent : RenameWalletComponent {
walletNameValue = TextFieldValue(text = "My Wallet"),
isConfirmEnabled = false,
onValueChange = {},
onConfirm = {},
onConfirmClick = {},
error = null,
)

View file

@ -12,7 +12,7 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ComponentModule {
internal interface WalletSettingsComponentModule {
@Binds
@Singleton

View file

@ -2,6 +2,7 @@ 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.component.impl.model.RenameWalletModel
import com.tangem.feature.walletsettings.model.WalletSettingsModel
import dagger.Binds
import dagger.Module
@ -11,10 +12,15 @@ import dagger.multibindings.IntoMap
@Module
@InstallIn(DecomposeComponent::class)
internal interface ModelModule {
internal interface WalletSettingsModelModule {
@Binds
@IntoMap
@ClassKey(WalletSettingsModel::class)
fun provideWalletSettingsModel(model: WalletSettingsModel): Model
fun bindWalletSettingsModel(model: WalletSettingsModel): Model
@Binds
@IntoMap
@ClassKey(RenameWalletModel::class)
fun bindRenameWalletModel(model: RenameWalletModel): Model
}

View file

@ -9,6 +9,6 @@ internal data class RenameWalletUM(
val walletNameValue: TextFieldValue,
val onValueChange: (value: TextFieldValue) -> Unit,
val isConfirmEnabled: Boolean,
val onConfirm: () -> Unit,
val onConfirmClick: () -> Unit,
val error: TextReference?,
)

View file

@ -25,7 +25,7 @@ internal fun RenameWalletDialog(model: RenameWalletUM, onDismiss: () -> Unit) {
confirmButton = DialogButtonUM(
title = stringResourceSafe(id = R.string.common_ok),
enabled = model.isConfirmEnabled,
onClick = model.onConfirm,
onClick = model.onConfirmClick,
),
dismissButton = DialogButtonUM(
title = stringResourceSafe(id = R.string.common_cancel),