Updated on 2026-08-14
This commit is contained in:
parent
50f30e9bbf
commit
2a1369fce6
9 changed files with 223 additions and 0 deletions
|
|
@ -160,6 +160,7 @@ dependencies {
|
||||||
implementation(projects.domain.hotWallet)
|
implementation(projects.domain.hotWallet)
|
||||||
implementation(projects.domain.news)
|
implementation(projects.domain.news)
|
||||||
implementation(projects.domain.earn)
|
implementation(projects.domain.earn)
|
||||||
|
implementation(projects.domain.tokensync)
|
||||||
|
|
||||||
implementation(projects.common)
|
implementation(projects.common)
|
||||||
implementation(projects.common.routing)
|
implementation(projects.common.routing)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
|
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
|
||||||
|
import com.tangem.domain.tokensync.repository.TokenSyncRepository
|
||||||
|
import com.tangem.domain.tokensync.usecase.AcknowledgeTokenSyncCompletionUseCase
|
||||||
|
import com.tangem.domain.tokensync.usecase.ObserveTokenSyncUseCase
|
||||||
|
import com.tangem.domain.tokensync.usecase.SyncTokensUseCase
|
||||||
|
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object TokenSyncDomainModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideObserveTokenSyncUseCase(tokenSyncRepository: TokenSyncRepository): ObserveTokenSyncUseCase {
|
||||||
|
return ObserveTokenSyncUseCase(
|
||||||
|
tokenSyncRepository = tokenSyncRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideAcknowledgeTokenSyncCompletionUseCase(
|
||||||
|
tokenSyncRepository: TokenSyncRepository,
|
||||||
|
): AcknowledgeTokenSyncCompletionUseCase {
|
||||||
|
return AcknowledgeTokenSyncCompletionUseCase(
|
||||||
|
tokenSyncRepository = tokenSyncRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSyncTokensUseCase(
|
||||||
|
tokenSyncRepository: TokenSyncRepository,
|
||||||
|
manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
|
||||||
|
appCoroutineScope: AppCoroutineScope,
|
||||||
|
): SyncTokensUseCase {
|
||||||
|
return SyncTokensUseCase(
|
||||||
|
tokenSyncRepository = tokenSyncRepository,
|
||||||
|
manageCryptoCurrenciesUseCase = manageCryptoCurrenciesUseCase,
|
||||||
|
appCoroutineScope = appCoroutineScope,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
domain/tokensync/build.gradle.kts
Normal file
20
domain/tokensync/build.gradle.kts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
plugins {
|
||||||
|
alias(deps.plugins.android.library)
|
||||||
|
alias(deps.plugins.kotlin.android)
|
||||||
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.tangem.domain.tokensync"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(projects.domain.core)
|
||||||
|
implementation(projects.domain.models)
|
||||||
|
implementation(projects.domain.account.status)
|
||||||
|
implementation(projects.core.utils)
|
||||||
|
|
||||||
|
implementation(deps.kotlin.coroutines)
|
||||||
|
implementation(deps.arrow.core)
|
||||||
|
implementation(deps.timber)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.tangem.domain.tokensync.model
|
||||||
|
|
||||||
|
sealed class TokenSyncProgress {
|
||||||
|
|
||||||
|
data object Idle : TokenSyncProgress()
|
||||||
|
|
||||||
|
data class InProgress(
|
||||||
|
val completedNetworks: Int,
|
||||||
|
val totalNetworks: Int,
|
||||||
|
) : TokenSyncProgress() {
|
||||||
|
val progressPercent: Int
|
||||||
|
get() = if (totalNetworks > 0) {
|
||||||
|
completedNetworks * 100 / totalNetworks
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data object Completed : TokenSyncProgress()
|
||||||
|
|
||||||
|
data class Error(val cause: Throwable) : TokenSyncProgress()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.tangem.domain.tokensync.repository
|
||||||
|
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.tokensync.model.TokenSyncProgress
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface TokenSyncRepository {
|
||||||
|
|
||||||
|
suspend fun runSync(userWalletId: UserWalletId)
|
||||||
|
|
||||||
|
suspend fun getPendingSyncWalletIds(): List<UserWalletId>
|
||||||
|
|
||||||
|
fun observeSyncProgress(userWalletId: UserWalletId): Flow<TokenSyncProgress>
|
||||||
|
|
||||||
|
fun acknowledgeCompletion(userWalletId: UserWalletId)
|
||||||
|
|
||||||
|
suspend fun clearPendingFlag(userWalletId: UserWalletId)
|
||||||
|
|
||||||
|
suspend fun getDiscoveredCurrencies(userWalletId: UserWalletId): List<CryptoCurrency>
|
||||||
|
|
||||||
|
suspend fun clearDiscoveredTokens(userWalletId: UserWalletId)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.tangem.domain.tokensync.usecase
|
||||||
|
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.tokensync.repository.TokenSyncRepository
|
||||||
|
|
||||||
|
class AcknowledgeTokenSyncCompletionUseCase(
|
||||||
|
private val tokenSyncRepository: TokenSyncRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
operator fun invoke(userWalletId: UserWalletId) {
|
||||||
|
tokenSyncRepository.acknowledgeCompletion(userWalletId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.tangem.domain.tokensync.usecase
|
||||||
|
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.tokensync.model.TokenSyncProgress
|
||||||
|
import com.tangem.domain.tokensync.repository.TokenSyncRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class ObserveTokenSyncUseCase(
|
||||||
|
private val tokenSyncRepository: TokenSyncRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
operator fun invoke(userWalletId: UserWalletId): Flow<TokenSyncProgress> {
|
||||||
|
return tokenSyncRepository.observeSyncProgress(userWalletId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.tangem.domain.tokensync.usecase
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
|
||||||
|
import com.tangem.domain.models.account.AccountId
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.tokensync.repository.TokenSyncRepository
|
||||||
|
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
class SyncTokensUseCase(
|
||||||
|
private val tokenSyncRepository: TokenSyncRepository,
|
||||||
|
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
|
||||||
|
private val appCoroutineScope: AppCoroutineScope,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val activeSyncJobs = ConcurrentHashMap<UserWalletId, Job>()
|
||||||
|
|
||||||
|
operator fun invoke(userWalletId: UserWalletId) {
|
||||||
|
activeSyncJobs[userWalletId]?.cancel()
|
||||||
|
activeSyncJobs[userWalletId] = appCoroutineScope.launch {
|
||||||
|
try {
|
||||||
|
tokenSyncRepository.runSync(userWalletId)
|
||||||
|
applyDiscoveredTokens(userWalletId)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Token sync failed for wallet: $userWalletId")
|
||||||
|
} finally {
|
||||||
|
activeSyncJobs.remove(userWalletId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun cancel(userWalletId: UserWalletId): Either<Throwable, Unit> = Either.catch {
|
||||||
|
activeSyncJobs.remove(userWalletId)?.cancel()
|
||||||
|
tokenSyncRepository.clearPendingFlag(userWalletId)
|
||||||
|
tokenSyncRepository.clearDiscoveredTokens(userWalletId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyPendingSyncs() {
|
||||||
|
appCoroutineScope.launch {
|
||||||
|
try {
|
||||||
|
val pendingIds = tokenSyncRepository.getPendingSyncWalletIds()
|
||||||
|
for (walletId in pendingIds) {
|
||||||
|
val isApplied = applyDiscoveredTokens(walletId)
|
||||||
|
if (isApplied) {
|
||||||
|
tokenSyncRepository.clearPendingFlag(walletId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Failed to apply pending syncs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun applyDiscoveredTokens(userWalletId: UserWalletId): Boolean {
|
||||||
|
val currencies = tokenSyncRepository.getDiscoveredCurrencies(userWalletId)
|
||||||
|
|
||||||
|
if (currencies.isEmpty()) return true
|
||||||
|
|
||||||
|
val accountId = AccountId.forMainCryptoPortfolio(userWalletId)
|
||||||
|
return manageCryptoCurrenciesUseCase(
|
||||||
|
accountId = accountId,
|
||||||
|
add = currencies,
|
||||||
|
).fold(
|
||||||
|
ifRight = {
|
||||||
|
tokenSyncRepository.clearDiscoveredTokens(userWalletId)
|
||||||
|
true
|
||||||
|
},
|
||||||
|
ifLeft = { error ->
|
||||||
|
Timber.e("Failed to apply discovered tokens for wallet: $userWalletId, error: $error")
|
||||||
|
false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -338,6 +338,7 @@ include(":domain:demo:models")
|
||||||
include(":domain:settings")
|
include(":domain:settings")
|
||||||
include(":domain:tokens")
|
include(":domain:tokens")
|
||||||
include(":domain:tokens:models")
|
include(":domain:tokens:models")
|
||||||
|
include(":domain:tokensync")
|
||||||
include(":domain:wallets")
|
include(":domain:wallets")
|
||||||
include(":domain:wallets:models")
|
include(":domain:wallets:models")
|
||||||
include(":domain:txhistory")
|
include(":domain:txhistory")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue