diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml
index 41c4f24cdb..34d53f84b5 100644
--- a/core/res/src/main/res/values-de/strings.xml
+++ b/core/res/src/main/res/values-de/strings.xml
@@ -180,8 +180,8 @@
- %d Geräte
- - Token
- - Tokens
+ - %d Token
+ - %d Tokens
Bitte setze das nächste Gerät zurück, um fortzufahren.
Wallet zurückgesetzt
diff --git a/core/res/src/main/res/values-pt-rBR/strings.xml b/core/res/src/main/res/values-pt-rBR/strings.xml
index 4cd264c962..890ff18a50 100644
--- a/core/res/src/main/res/values-pt-rBR/strings.xml
+++ b/core/res/src/main/res/values-pt-rBR/strings.xml
@@ -176,12 +176,12 @@
Iniciar processo de backup
Use um cartão bancário ou outros métodos de pagamento.
- - dispositivo
- - dispositivos
+ - %d dispositivo
+ - %d dispositivos
- - token
- - tokens
+ - %d token
+ - %d tokens
Reinicie o próximo dispositivo para continuar.
Reiniciar Carteira
diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt
index bc789c6728..ca4dad4ed3 100644
--- a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt
+++ b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt
@@ -50,7 +50,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
override suspend fun invoke(params: PaymentAccountStatusFetcher.Params): Either =
Either.catchOn(dispatchers.default) {
val account = Account.Payment(userWalletId = params.userWalletId)
- logger.i("fetch: ${params.userWalletId.stringValue}")
+ logger.i("invoke() start: ${params.userWalletId.stringValue}")
if (deviceSecurity.isSecurityExposed()) {
logger.i("fetch security info: rooted: ${deviceSecurity.isRooted}")
@@ -84,11 +84,14 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
userWalletId = params.userWalletId,
status = AccountStatus.Payment(account = account, value = status),
)
- }.onLeft {
+ }.onLeft { throwable ->
+ logger.e("invoke() ${params.userWalletId} threw, falling back to ONLY_CACHE", throwable)
paymentAccountStatusesStore.updateStatusSource(
userWalletId = params.userWalletId,
source = StatusSource.ONLY_CACHE,
)
+ }.also { result ->
+ logger.i("invoke() end ${params.userWalletId}: isRight=${result.isRight()}")
}
private suspend fun proceedHasTangemPayResult(
@@ -105,7 +108,12 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
private suspend fun fetchTangemPayAccountStatus(account: Account.Payment): PaymentAccountStatusValue {
val prevResult = paymentAccountStatusesStore.getSyncOrNull(account.userWalletId)
+ logger.i(
+ "fetchTangemPayAccountStatus ${account.userWalletId}: " +
+ "prevResultType=${prevResult?.value?.let { it::class.simpleName } ?: "null"}",
+ )
if (prevResult == null || prevResult.value is PaymentAccountStatusValue.Error.Unavailable) {
+ logger.i("fetchTangemPayAccountStatus ${account.userWalletId}: writing Loading placeholder to store")
paymentAccountStatusesStore.store(
userWalletId = account.userWalletId,
status = AccountStatus.Payment(account = account, value = PaymentAccountStatusValue.Loading),
@@ -116,10 +124,13 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
}
private suspend fun proceedWithOrderId(account: Account.Payment): PaymentAccountStatusValue {
- return if (!onboardingRepository.isTangemPayInitialDataProduced(account.userWalletId)) {
+ val isInitial = onboardingRepository.isTangemPayInitialDataProduced(account.userWalletId)
+ logger.i("proceedWithOrderId ${account.userWalletId}: isTangemPayInitialDataProduced=$isInitial")
+ return if (!isInitial) {
PaymentAccountStatusValue.Error.NotSynced
} else {
val orderId = onboardingRepository.getOrderId(account.userWalletId)
+ logger.i("proceedWithOrderId ${account.userWalletId}: orderIdPresent=${orderId != null}")
if (orderId != null) {
proceedWithOrderId(account = account, orderId = orderId)
} else {
diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusProducer.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusProducer.kt
index c2c53ae7cd..153f0d327d 100644
--- a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusProducer.kt
+++ b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusProducer.kt
@@ -9,12 +9,17 @@ import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.pay.flow.PaymentAccountStatusProducer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import com.tangem.utils.logging.TangemLogger
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onEmpty
+import kotlinx.coroutines.flow.onStart
+
+private const val TAG = "PaymentAccountStatusProducer"
internal class DefaultPaymentAccountStatusProducer @AssistedInject constructor(
@Assisted private val params: PaymentAccountStatusProducer.Params,
@@ -24,13 +29,20 @@ internal class DefaultPaymentAccountStatusProducer @AssistedInject constructor(
) : PaymentAccountStatusProducer {
private val account = Account.Payment(userWalletId = params.userWalletId)
+ private val logger = TangemLogger.withTag(TAG)
override val fallback: Option
get() = AccountStatus.Payment(account = account, value = PaymentAccountStatusValue.Error.Unavailable).some()
override fun produce(): Flow {
+ logger.i("[${params.userWalletId}] produce() called")
return paymentAccountStatusesStore.get(userWalletId = params.userWalletId)
- .onEmpty { emit(value = AccountStatus.Payment(account, PaymentAccountStatusValue.NotCreated)) }
+ .onStart { logger.i("[${params.userWalletId}] flow subscribed to store") }
+ .onEach { logger.i("[${params.userWalletId}] flow emits statusType=${it.value::class.simpleName}") }
+ .onEmpty {
+ logger.i("[${params.userWalletId}] onEmpty triggered: emitting NotCreated fallback")
+ emit(value = AccountStatus.Payment(account, PaymentAccountStatusValue.NotCreated))
+ }
.flowOn(dispatchers.default)
}
diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/store/PaymentAccountStatusesStore.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/store/PaymentAccountStatusesStore.kt
index c39190f6d8..fd60f231b4 100644
--- a/data/visa/src/main/kotlin/com/tangem/data/pay/store/PaymentAccountStatusesStore.kt
+++ b/data/visa/src/main/kotlin/com/tangem/data/pay/store/PaymentAccountStatusesStore.kt
@@ -16,11 +16,15 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
internal typealias WalletIdWithPaymentStatus = Map
internal typealias WalletIdWithPaymentStatusDM = Map
+private const val TAG = "PaymentAccountStatusesStore"
+
/**
* Store for payment account statuses with dual storage (runtime + persistence).
*
@@ -34,10 +38,18 @@ internal class PaymentAccountStatusesStore(
scope: AppCoroutineScope,
) {
+ private val logger = TangemLogger.withTag(TAG)
+
init {
scope.launch {
+ logger.i("init: loading cached payment statuses from persistence")
try {
- val cachedStatuses = persistenceDataStore.data.firstOrNull() ?: return@launch
+ val cachedStatuses = persistenceDataStore.data.firstOrNull()
+ if (cachedStatuses == null) {
+ logger.i("init: persistence empty (firstOrNull == null), runtimeStore stays empty")
+ return@launch
+ }
+ logger.i("init: loaded ${cachedStatuses.size} cached entries; populating runtimeStore")
runtimeStore.store(
value = cachedStatuses.mapValues { (rawUserWalletId, statusDM) ->
val account = Account.Payment(userWalletId = UserWalletId(rawUserWalletId))
@@ -45,19 +57,33 @@ internal class PaymentAccountStatusesStore(
AccountStatus.Payment(account = account, value = statusValue)
},
)
+ logger.i("init: runtimeStore populated with ${cachedStatuses.size} entries")
} catch (e: Exception) {
runSuspendCatching { persistenceDataStore.updateData { emptyMap() } }
- TangemLogger.e("Error while loading cached payment account statuses", e)
+ logger.e("Error while loading cached payment account statuses", e)
}
}
}
fun get(userWalletId: UserWalletId): Flow {
- return runtimeStore.get().mapNotNull { it[userWalletId.stringValue] }
+ return runtimeStore.get()
+ .onStart { logger.i("get($userWalletId): subscribed to runtimeStore") }
+ .onEach { map ->
+ logger.i(
+ "get($userWalletId): runtimeStore emitted map size=${map.size}, " +
+ "hasEntry=${map.containsKey(userWalletId.stringValue)}",
+ )
+ }
+ .mapNotNull { it[userWalletId.stringValue] }
+ .onEach { status ->
+ logger.i("get($userWalletId): emitting statusType=${status.value::class.simpleName}")
+ }
}
suspend fun getSyncOrNull(userWalletId: UserWalletId): AccountStatus.Payment? {
- return runtimeStore.getSyncOrNull()?.get(userWalletId.stringValue)
+ val result = runtimeStore.getSyncOrNull()?.get(userWalletId.stringValue)
+ logger.i("getSyncOrNull($userWalletId) valueType=${result?.value?.let { it::class.simpleName } ?: "null"}")
+ return result
}
suspend fun updateStatusSource(userWalletId: UserWalletId, source: StatusSource) {
@@ -71,6 +97,7 @@ internal class PaymentAccountStatusesStore(
}
suspend fun store(userWalletId: UserWalletId, status: AccountStatus.Payment) {
+ logger.i("store($userWalletId): valueType=${status.value::class.simpleName}")
coroutineScope {
launch { storeInRuntime(userWalletId = userWalletId, status = status) }
launch { storeInPersistence(userWalletId = userWalletId, status = status.value) }
diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt
index a2bf732b26..1da0c170d6 100644
--- a/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt
+++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt
@@ -46,6 +46,7 @@ import com.tangem.domain.tokens.operations.TokenListFactory
import com.tangem.domain.tokens.operations.TotalFiatBalanceCalculator
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import com.tangem.utils.logging.TangemLogger
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -73,7 +74,7 @@ import java.math.BigDecimal
[REDACTED_AUTHOR]
*/
// TODO: Move to :data:account:status [REDACTED_JIRA]
-@Suppress("LongParameterList")
+@Suppress("LongParameterList", "LargeClass")
@OptIn(ExperimentalCoroutinesApi::class)
internal class DefaultSingleAccountStatusListProducer @AssistedInject constructor(
@Assisted private val params: SingleAccountStatusListProducer.Params,
@@ -90,17 +91,29 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
) : SingleAccountStatusListProducer {
+ private val logger = TangemLogger.withTag(TAG)
+
override val fallback: Option = none()
override fun produce(): Flow {
+ logger.i("produce() called for ${params.userWalletId}")
return flattenFlow()
+ .onEach { list ->
+ logger.i(
+ "produce()[${params.userWalletId}] emit: accounts=${list.accountStatuses.size}, " +
+ "currencies=${list.flattenCurrencies().size}, " +
+ "totalFiatType=${list.totalFiatBalance::class.simpleName}",
+ )
+ }
.flowOn(dispatchers.default)
}
@Suppress("LongMethod")
private fun flattenFlow(): Flow = channelFlow {
val walletId = params.userWalletId
+ logger.i("flattenFlow[$walletId]: start")
val userWallet = userWalletsListRepository.getSyncStrict(id = params.userWalletId)
+ logger.i("flattenFlow[$walletId]: userWallet resolved (type=${userWallet::class.simpleName})")
val flattenCurrency: MutableSharedFlow