Updated on 2026-08-14
This commit is contained in:
parent
b316ef4d87
commit
370690a359
42 changed files with 486 additions and 35 deletions
|
|
@ -57,6 +57,7 @@ interface PortfolioSelectorController {
|
|||
* combine and update with your Feature data and [PortfolioFetcher.data]
|
||||
*/
|
||||
val isEnabled: MutableStateFlow<(UserWallet, AccountStatus) -> Boolean>
|
||||
suspend fun isAccountModeSync(): Boolean
|
||||
|
||||
fun selectAccount(accountId: AccountId?)
|
||||
fun selectedAccountWithData(portfolioFetcher: PortfolioFetcher): Flow<Pair<UserWallet, AccountStatus>?>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
package com.tangem.features.account.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.features.account.AccountCreateEditComponent
|
||||
|
||||
sealed class AccountSettingsAnalyticEvents(
|
||||
category: String = "Settings / Account",
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent(category, event, params) {
|
||||
|
||||
class AccountSettingsScreenOpened : AccountSettingsAnalyticEvents(
|
||||
event = "Account Settings Screen Opened",
|
||||
)
|
||||
|
||||
class ButtonManageTokens : AccountSettingsAnalyticEvents(
|
||||
event = "Button - Manage Tokens",
|
||||
)
|
||||
|
||||
class ButtonArchiveAccount : AccountSettingsAnalyticEvents(
|
||||
event = "Button - Archive Account",
|
||||
)
|
||||
|
||||
class ButtonArchiveAccountConfirmation : AccountSettingsAnalyticEvents(
|
||||
event = "Button - Archive Account Confirmation",
|
||||
)
|
||||
|
||||
class ButtonCancelAccountArchivation : AccountSettingsAnalyticEvents(
|
||||
event = "Button - Cancel Account Archivation",
|
||||
)
|
||||
|
||||
class AccountArchived : AccountSettingsAnalyticEvents(
|
||||
event = "Account Archived",
|
||||
)
|
||||
|
||||
class ButtonEdit : AccountSettingsAnalyticEvents(
|
||||
event = "Button - Edit",
|
||||
)
|
||||
|
||||
class AccountEditScreenOpened : AccountSettingsAnalyticEvents(
|
||||
event = "Account Edit Screen Opened",
|
||||
)
|
||||
|
||||
class ButtonSave(
|
||||
val name: AccountName,
|
||||
val icon: CryptoPortfolioIcon,
|
||||
) : AccountSettingsAnalyticEvents(
|
||||
event = "Button - Save",
|
||||
params = buildMap {
|
||||
val accountName = when (name) {
|
||||
is AccountName.Custom -> name.value
|
||||
AccountName.DefaultMain -> "DefaultMain"
|
||||
}
|
||||
put("Name", accountName)
|
||||
put("Color", icon.color.name)
|
||||
put("Icon", icon.value.name)
|
||||
},
|
||||
)
|
||||
|
||||
class ButtonAddNewAccount(
|
||||
val name: AccountName,
|
||||
val icon: CryptoPortfolioIcon,
|
||||
val derivationIndex: Int,
|
||||
) : AccountSettingsAnalyticEvents(
|
||||
event = "Button - Add New Account",
|
||||
params = buildMap {
|
||||
val accountName = when (name) {
|
||||
is AccountName.Custom -> name.value
|
||||
AccountName.DefaultMain -> "DefaultMain"
|
||||
}
|
||||
put("Name", accountName)
|
||||
put("Color", icon.color.name)
|
||||
put("Icon", icon.value.name)
|
||||
put("Derivation", derivationIndex.toString())
|
||||
},
|
||||
)
|
||||
|
||||
class AccountError(
|
||||
val source: Source,
|
||||
val error: String,
|
||||
) : AccountSettingsAnalyticEvents(
|
||||
event = "Account Error",
|
||||
params = buildMap {
|
||||
put("Error", error)
|
||||
},
|
||||
)
|
||||
|
||||
enum class Source(val value: String) {
|
||||
NEW_ACCOUNT("New Account"), EDIT("Edit"), ARCHIVE("Archive")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun AccountCreateEditComponent.Params.toAnalyticSource() = when (this) {
|
||||
is AccountCreateEditComponent.Params.Create -> Source.NEW_ACCOUNT
|
||||
is AccountCreateEditComponent.Params.Edit -> Source.EDIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.account.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
sealed class WalletSettingsAccountAnalyticEvents(
|
||||
category: String = "Settings / Wallet Settings",
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent(category, event, params) {
|
||||
|
||||
class AccountCreated : WalletSettingsAccountAnalyticEvents(
|
||||
event = "Account Created",
|
||||
)
|
||||
|
||||
class AccountRecovered : WalletSettingsAccountAnalyticEvents(
|
||||
event = "Account Recovered",
|
||||
)
|
||||
|
||||
class ArchivedAccountsScreenOpened : WalletSettingsAccountAnalyticEvents(
|
||||
event = "Archived Accounts Screen Opened",
|
||||
)
|
||||
|
||||
class ButtonRecoverAccount : WalletSettingsAccountAnalyticEvents(
|
||||
event = "Button - Recover Account",
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.account.archived
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
|
|
@ -18,6 +19,7 @@ import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase
|
|||
import com.tangem.domain.account.usecase.GetArchivedAccountsUseCase
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.features.account.ArchivedAccountListComponent
|
||||
import com.tangem.features.account.analytics.WalletSettingsAccountAnalyticEvents
|
||||
import com.tangem.features.account.archived.entity.AccountArchivedUM
|
||||
import com.tangem.features.account.archived.entity.AccountArchivedUMBuilder
|
||||
import com.tangem.features.account.archived.entity.AccountArchivedUMBuilder.Companion.toggleProgress
|
||||
|
|
@ -40,6 +42,7 @@ internal class ArchivedAccountListModel @Inject constructor(
|
|||
private val recoverCryptoPortfolioUseCase: RecoverCryptoPortfolioUseCase,
|
||||
private val getArchivedAccountsUseCase: GetArchivedAccountsUseCase,
|
||||
private val umBuilder: AccountArchivedUMBuilder,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -52,6 +55,7 @@ internal class ArchivedAccountListModel @Inject constructor(
|
|||
private val getArchivedAccountsJob = JobHolder()
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(WalletSettingsAccountAnalyticEvents.ArchivedAccountsScreenOpened())
|
||||
getArchivedAccounts()
|
||||
}
|
||||
|
||||
|
|
@ -96,6 +100,7 @@ internal class ArchivedAccountListModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun recoverCryptoPortfolio(accountId: AccountId) = modelScope.launch {
|
||||
analyticsEventHandler.send(WalletSettingsAccountAnalyticEvents.ButtonRecoverAccount())
|
||||
uiState.update { it.toggleProgress(accountId, isLoading = true) }
|
||||
val result = withContext(dispatchers.default) {
|
||||
recoverCryptoPortfolioUseCase(accountId)
|
||||
|
|
@ -148,6 +153,7 @@ internal class ArchivedAccountListModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun showSuccessRecoverMessage() {
|
||||
analyticsEventHandler.send(WalletSettingsAccountAnalyticEvents.AccountRecovered())
|
||||
val message = resourceReference(R.string.account_recover_success_message)
|
||||
messageSender.send(ToastMessage(message = message))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.annotation.StringRes
|
|||
import com.tangem.common.ui.account.AccountNameUM
|
||||
import com.tangem.common.ui.account.toDomain
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
|
|
@ -25,6 +26,9 @@ import com.tangem.domain.models.account.CryptoPortfolioIcon
|
|||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.account.AccountCreateEditComponent
|
||||
import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents
|
||||
import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents.Companion.toAnalyticSource
|
||||
import com.tangem.features.account.analytics.WalletSettingsAccountAnalyticEvents
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUM
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon
|
||||
|
|
@ -54,6 +58,7 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
private val addCryptoPortfolioUseCase: AddCryptoPortfolioUseCase,
|
||||
private val getUnoccupiedAccountIndexUseCase: GetUnoccupiedAccountIndexUseCase,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<AccountCreateEditComponent.Params>()
|
||||
|
|
@ -65,6 +70,8 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
init {
|
||||
if (params is AccountCreateEditComponent.Params.Create) {
|
||||
updateDerivationInfo(userWalletId = params.userWalletId)
|
||||
} else {
|
||||
analyticsEventHandler.send(AccountSettingsAnalyticEvents.AccountEditScreenOpened())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +112,12 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
val icon = state.account.portfolioIcon.toDomain()
|
||||
val index = state.account.derivationInfo.index ?: return
|
||||
val derivationIndex = DerivationIndex(value = index).getOrNull() ?: return
|
||||
val event = AccountSettingsAnalyticEvents.ButtonAddNewAccount(
|
||||
name = name,
|
||||
icon = icon,
|
||||
derivationIndex = derivationIndex.value,
|
||||
)
|
||||
analyticsEventHandler.send(event)
|
||||
|
||||
uiState.value = uiState.value.toggleProgress(showProgress = true)
|
||||
val result = addCryptoPortfolioUseCase(
|
||||
|
|
@ -118,12 +131,22 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
result
|
||||
.onLeft(::handleAddAccountError)
|
||||
.onRight {
|
||||
analyticsEventHandler.send(WalletSettingsAccountAnalyticEvents.AccountCreated())
|
||||
showMessage(R.string.account_create_success_message)
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAddAccountError(error: AddCryptoPortfolioUseCase.Error) {
|
||||
val event = AccountSettingsAnalyticEvents.AccountError(
|
||||
source = params.toAnalyticSource(),
|
||||
error = when (error) {
|
||||
is AddCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet -> error.cause.tag
|
||||
is AddCryptoPortfolioUseCase.Error.DataOperationFailed -> error.cause.message.orEmpty()
|
||||
},
|
||||
)
|
||||
analyticsEventHandler.send(event)
|
||||
|
||||
val isDuplicateAccountNamesError = (error as? AddCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet)
|
||||
?.cause is AccountList.Error.DuplicateAccountNames
|
||||
when {
|
||||
|
|
@ -141,6 +164,7 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
val icon = state.account.portfolioIcon.toDomain()
|
||||
val isNewName = name != params.account.accountName
|
||||
val isNewIcon = icon != params.account.portfolioIcon
|
||||
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonSave(name, icon))
|
||||
|
||||
uiState.value = uiState.value.toggleProgress(showProgress = true)
|
||||
val result = updateCryptoPortfolioUseCase(
|
||||
|
|
@ -159,6 +183,15 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleEditAccountError(error: UpdateCryptoPortfolioUseCase.Error) {
|
||||
val event = AccountSettingsAnalyticEvents.AccountError(
|
||||
source = params.toAnalyticSource(),
|
||||
error = when (error) {
|
||||
is UpdateCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet -> error.cause.tag
|
||||
is UpdateCryptoPortfolioUseCase.Error.DataOperationFailed -> error.cause.message.orEmpty()
|
||||
UpdateCryptoPortfolioUseCase.Error.NothingToUpdate -> error::class.simpleName.orEmpty()
|
||||
},
|
||||
)
|
||||
analyticsEventHandler.send(event)
|
||||
val isDuplicateAccountNamesError = (error as? UpdateCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet)
|
||||
?.cause is AccountList.Error.DuplicateAccountNames
|
||||
when {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.account.details
|
|||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -20,6 +21,7 @@ import com.tangem.domain.models.account.Account
|
|||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.account.AccountDetailsComponent
|
||||
import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon
|
||||
import com.tangem.features.account.details.entity.AccountDetailsUM
|
||||
import com.tangem.features.account.details.entity.AccountDetailsUM.ArchiveMode
|
||||
|
|
@ -37,6 +39,7 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val archiveCryptoPortfolioUseCase: ArchiveCryptoPortfolioUseCase,
|
||||
singleAccountSupplier: SingleAccountSupplier,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -48,12 +51,14 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
private val accountId = params.account.accountId
|
||||
|
||||
init {
|
||||
analyticsEventHandler.send(AccountSettingsAnalyticEvents.AccountSettingsScreenOpened())
|
||||
singleAccountSupplier(SingleAccountProducer.Params(accountId))
|
||||
.onEach { account -> uiState.update { buildUI(account) } }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun onEditAccountClick(account: Account) {
|
||||
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonEdit())
|
||||
router.push(AppRoute.EditAccount(account))
|
||||
}
|
||||
|
||||
|
|
@ -62,17 +67,21 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
source = AppRoute.ManageTokens.Source.SETTINGS,
|
||||
portfolioId = PortfolioId(account.accountId),
|
||||
)
|
||||
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonManageTokens())
|
||||
router.push(route)
|
||||
}
|
||||
|
||||
private fun onArchiveAccountClick() {
|
||||
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonArchiveAccount())
|
||||
confirmArchiveDialog()
|
||||
}
|
||||
|
||||
private fun confirmArchiveDialog() {
|
||||
val secondAction = EventMessageAction(
|
||||
title = resourceReference(R.string.common_cancel),
|
||||
onClick = {},
|
||||
onClick = {
|
||||
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonCancelAccountArchivation())
|
||||
},
|
||||
)
|
||||
val firstAction = EventMessageAction(
|
||||
title = resourceReference(R.string.account_details_archive_action),
|
||||
|
|
@ -90,6 +99,7 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun archiveCryptoPortfolio() = modelScope.launch {
|
||||
analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonArchiveAccountConfirmation())
|
||||
uiState.update { it.toggleProgress(true) }
|
||||
archiveCryptoPortfolioUseCase(accountId)
|
||||
.onLeft { error ->
|
||||
|
|
@ -97,6 +107,7 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
uiState.update { it.toggleProgress(false) }
|
||||
}
|
||||
.onRight {
|
||||
analyticsEventHandler.send(AccountSettingsAnalyticEvents.AccountArchived())
|
||||
val message = resourceReference(R.string.account_archive_success_message)
|
||||
messageSender.send(ToastMessage(message = message))
|
||||
router.pop()
|
||||
|
|
@ -104,6 +115,11 @@ internal class AccountDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun failedArchiveDialog(error: ArchiveCryptoPortfolioUseCase.Error) {
|
||||
val event = AccountSettingsAnalyticEvents.AccountError(
|
||||
source = AccountSettingsAnalyticEvents.Source.ARCHIVE,
|
||||
error = error.tag,
|
||||
)
|
||||
analyticsEventHandler.send(event)
|
||||
val titleRes = R.string.common_something_went_wrong
|
||||
val messageRes = when (error) {
|
||||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountListRequirementsNotMet,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ internal class DefaultPortfolioSelectorController @Inject constructor(
|
|||
|
||||
override val isEnabled: MutableStateFlow<(UserWallet, AccountStatus) -> Boolean> = MutableStateFlow { _, _ -> true }
|
||||
|
||||
override suspend fun isAccountModeSync(): Boolean = isAccountsModeEnabledUseCase.invokeSync()
|
||||
|
||||
override fun selectAccount(accountId: AccountId?) {
|
||||
_selectedAccount.tryEmit(accountId)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue