Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-27 09:19:28 +03:00
commit bab4923434
283 changed files with 1541 additions and 11004 deletions

View file

@ -30,12 +30,6 @@ internal class DefaultCardSdkConfigRepository(private val cardSdkProvider: CardS
}
}
override fun isBiometricsRequestPolicy(): Boolean {
return with(sdk.config.userCodeRequestPolicy) {
this is UserCodeRequestPolicy.AlwaysWithBiometrics && codeType == UserCodeType.AccessCode
}
}
override fun resetCardIdDisplayFormat() {
sdk.config.cardIdDisplayFormat = CardIdDisplayFormat.Full
}

View file

@ -31,9 +31,10 @@ dependencies {
// region Core modules
implementation(projects.core.datasource)
implementation(projects.core.navigation)
implementation(projects.core.utils)
// endregion
implementation(projects.core.utils)
implementation(projects.domain.feedback)
implementation(projects.domain.legacy)

View file

@ -1,21 +1,18 @@
package com.tangem.data.feedback
import android.content.Context
import android.content.pm.PackageInfo
import android.os.Build
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.navigation.email.EmailSender
import com.tangem.data.feedback.converters.BlockchainInfoConverter
import com.tangem.data.feedback.converters.CardInfoConverter
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.BlockchainInfo
import com.tangem.domain.feedback.models.PhoneInfo
import com.tangem.domain.feedback.models.UserWalletsInfo
import com.tangem.domain.feedback.models.*
import com.tangem.domain.feedback.repository.FeedbackRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.version.AppVersionProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import timber.log.Timber
@ -27,7 +24,8 @@ import java.io.File
* @property appLogsStore app logs store
* @property userWalletsListManager user wallets list manager
* @property walletManagersStore wallet managers store
* @property context context for getting app version
* @property emailSender email sender
* @property appVersionProvider app version provider
*
[REDACTED_AUTHOR]
*/
@ -35,7 +33,8 @@ internal class DefaultFeedbackRepository(
private val appLogsStore: AppLogsStore,
private val userWalletsListManager: UserWalletsListManager,
private val walletManagersStore: WalletManagersStore,
private val context: Context,
private val emailSender: EmailSender,
private val appVersionProvider: AppVersionProvider,
) : FeedbackRepository {
private val blockchainsErrors = MutableStateFlow<Map<UserWalletId, BlockchainErrorInfo>>(emptyMap())
@ -73,7 +72,7 @@ internal class DefaultFeedbackRepository(
return PhoneInfo(
phoneModel = Build.MODEL,
osVersion = Build.VERSION.SDK_INT.toString(),
appVersion = getAppVersion(),
appVersion = appVersionProvider.versionName,
)
}
@ -95,14 +94,14 @@ internal class DefaultFeedbackRepository(
override fun getLogFile(): File? = appLogsStore.getFile()
private fun getAppVersion(): String {
return runCatching { context.packageManager.getPackageInfo(context.packageName, 0) }
.fold(
onSuccess = PackageInfo::versionName,
onFailure = {
Timber.e(it)
"x.y.z"
},
)
override fun sendEmail(feedbackEmail: FeedbackEmail) {
emailSender.send(
EmailSender.Email(
address = feedbackEmail.address,
subject = feedbackEmail.subject,
message = feedbackEmail.message,
attachment = feedbackEmail.file,
),
)
}
}

View file

@ -1,15 +1,15 @@
package com.tangem.data.feedback.di
import android.content.Context
import com.tangem.core.navigation.email.EmailSender
import com.tangem.data.feedback.DefaultFeedbackRepository
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.feedback.repository.FeedbackRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.utils.version.AppVersionProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@ -23,13 +23,15 @@ internal object FeedbackRepositoryModule {
appLogsStore: AppLogsStore,
userWalletsListManager: UserWalletsListManager,
walletManagersStore: WalletManagersStore,
@ApplicationContext context: Context,
emailSender: EmailSender,
appVersionProvider: AppVersionProvider,
): FeedbackRepository {
return DefaultFeedbackRepository(
appLogsStore = appLogsStore,
userWalletsListManager = userWalletsListManager,
walletManagersStore = walletManagersStore,
context = context,
emailSender = emailSender,
appVersionProvider = appVersionProvider,
)
}
}

View file

@ -30,5 +30,6 @@ dependencies {
implementation(deps.kotlin.coroutines)
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
implementation(deps.timber)
// endregion
}

View file

@ -1,17 +1,31 @@
package com.tangem.data.settings
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.GeoResponse
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
import com.tangem.datasource.local.preferences.utils.store
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.Locale
internal class DefaultSettingsRepository(
private val appPreferencesStore: AppPreferencesStore,
private val appLogsStore: AppLogsStore,
private val tangemTechApi: TangemTechApi,
private val dispatchers: CoroutineDispatcherProvider,
) : SettingsRepository {
private val userCountryFlow = MutableStateFlow<UserCountry?>(value = null)
override suspend fun shouldShowSaveUserWalletScreen(): Boolean {
return appPreferencesStore.getSyncOrDefault(
key = PreferencesKeys.SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY,
@ -99,4 +113,38 @@ internal class DefaultSettingsRepository(
override suspend fun setMarketsTooltipShown(value: Boolean) {
appPreferencesStore.store(key = PreferencesKeys.SHOULD_SHOW_MARKETS_TOOLTIP_KEY, value = !value)
}
override suspend fun getUserCountryCodeSync(): UserCountry? {
// If user country code is already set, return it
val countryCode = userCountryFlow.value
if (countryCode != null) return countryCode
coroutineScope {
launch { fetchUserCountryCode() }
}
return null
}
override suspend fun fetchUserCountryCode() {
Timber.i("Start fetching user country code")
withContext(dispatchers.io) {
val country = runCatching { tangemTechApi.getUserCountryCode() }
.fold(
onSuccess = GeoResponse::code,
onFailure = { Locale.getDefault().country },
)
.lowercase()
val code = when (country) {
UserCountry.Russia.code -> UserCountry.Russia
else -> UserCountry.Other(code = country)
}
Timber.i("User code country is $code")
userCountryFlow.value = code
}
}
}

View file

@ -4,12 +4,14 @@ import com.tangem.data.settings.DefaultAppRatingRepository
import com.tangem.data.settings.DefaultPermissionRepository
import com.tangem.data.settings.DefaultPromoSettingsRepository
import com.tangem.data.settings.DefaultSettingsRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.PermissionRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -25,8 +27,15 @@ internal object SettingsDataModule {
fun provideSettingsRepository(
appPreferencesStore: AppPreferencesStore,
appLogsStore: AppLogsStore,
tangemTechApi: TangemTechApi,
dispatchers: CoroutineDispatcherProvider,
): SettingsRepository {
return DefaultSettingsRepository(appPreferencesStore = appPreferencesStore, appLogsStore = appLogsStore)
return DefaultSettingsRepository(
appPreferencesStore = appPreferencesStore,
appLogsStore = appLogsStore,
tangemTechApi = tangemTechApi,
dispatchers = dispatchers,
)
}
@Provides

View file

@ -93,29 +93,6 @@ internal object TokensDataModule {
return DefaultMarketCryptoCurrencyRepository(expressAssetsStore, coroutineDispatcherProvider)
}
@Provides
@Singleton
fun providesTokensListRepository(
tangemTechApi: TangemTechApi,
dispatchers: CoroutineDispatcherProvider,
quotesRepository: QuotesRepository,
): TokensListRepository {
return DefaultTokensListRepository(
tangemTechApi = tangemTechApi,
dispatchers = dispatchers,
quotesRepository = quotesRepository,
)
}
@Provides
@Singleton
fun provideCardNetworksRepository(
userWalletsStore: UserWalletsStore,
dispatchers: CoroutineDispatcherProvider,
): NetworksCompatibilityRepository {
return DefaultNetworksCompatibilityRepository(userWalletsStore = userWalletsStore, dispatchers = dispatchers)
}
@Provides
@Singleton
fun provideCurrencyChecksRepository(walletManagersFacade: WalletManagersFacade): CurrencyChecksRepository {

View file

@ -27,7 +27,6 @@ import com.tangem.datasource.local.preferences.utils.storeObject
import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.core.error.DataError
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.lce.lceFlow
@ -376,24 +375,6 @@ internal class DefaultCurrenciesRepository(
}.cancellable()
}
override fun getMissedAddressesCryptoCurrencies(userWalletId: UserWalletId): Flow<List<CryptoCurrency>> {
return channelFlow {
ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true)
val userWallet = getUserWallet(userWalletId = userWalletId)
getMultiCurrencyWalletCurrencies(userWallet = userWallet)
.map {
it.filter { currency ->
val blockchain = Blockchain.fromId(id = currency.network.id.value)
val derivationPath = currency.network.derivationPath.value
derivationPath != null && !userWallet.scanResponse.hasDerivation(blockchain, derivationPath)
}
}
.collectLatest(::send)
}
}
override fun isSendBlockedByPendingTransactions(
cryptoCurrencyStatus: CryptoCurrencyStatus,
coinStatus: CryptoCurrencyStatus?,

View file

@ -1,101 +0,0 @@
package com.tangem.data.tokens.repository
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.common.card.EllipticCurve
import com.tangem.data.common.currency.getNetwork
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.extensions.*
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.NetworksCompatibilityRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
internal class DefaultNetworksCompatibilityRepository(
private val userWalletsStore: UserWalletsStore,
private val dispatchers: CoroutineDispatcherProvider,
) : NetworksCompatibilityRepository {
/**
* @return returns true if either the network is not Solana (check is not relevant) or if it is Solana and
* UserWallet supports tokens on Solana Network
*/
@Throws(IllegalArgumentException::class)
override suspend fun areSolanaTokensSupportedIfRelevant(networkId: String, userWalletId: UserWalletId): Boolean {
return withContext(dispatchers.io) {
val scanResponse = getWalletOrThrow(userWalletId).scanResponse
val blockchain = getBlockchainOrThrow(networkId)
val blockchainsSupportingTokens = scanResponse.card.supportedTokens(scanResponse.cardTypesResolver)
blockchain != Blockchain.Solana || blockchainsSupportingTokens.contains(Blockchain.Solana)
}
}
@Throws(IllegalArgumentException::class)
override suspend fun areTokensSupportedByNetwork(networkId: String, userWalletId: UserWalletId): Boolean {
return withContext(dispatchers.io) {
val scanResponse = getWalletOrThrow(userWalletId).scanResponse
val blockchain = getBlockchainOrThrow(networkId)
val blockchainsSupportingTokens = scanResponse.card.supportedTokens(scanResponse.cardTypesResolver)
scanResponse.card.canHandleToken(
supportedTokens = blockchainsSupportingTokens,
blockchain = blockchain,
cardTypesResolver = scanResponse.cardTypesResolver,
)
}
}
@Throws(IllegalArgumentException::class)
override suspend fun isNetworkSupported(networkId: String, userWalletId: UserWalletId): Boolean {
return withContext(dispatchers.io) {
val scanResponse = getWalletOrThrow(userWalletId).scanResponse
val blockchain = getBlockchainOrThrow(networkId)
scanResponse.card.canHandleBlockchain(
blockchain = blockchain,
cardTypesResolver = scanResponse.cardTypesResolver,
)
}
}
@Throws(IllegalArgumentException::class)
override suspend fun getSupportedNetworks(userWalletId: UserWalletId): List<Network> {
val scanResponse = getWalletOrThrow(userWalletId).scanResponse
return Blockchain.entries
.filter { blockchain ->
scanResponse.card.supportedBlockchains(scanResponse.cardTypesResolver).contains(blockchain)
}
.sortedBy(Blockchain::fullName)
.mapNotNull { blockchain ->
getNetwork(blockchain, null, scanResponse.derivationStyleProvider)
}
}
override suspend fun requiresHardenedDerivationOnly(networkId: String, userWalletId: UserWalletId): Boolean {
val scanResponse = getWalletOrThrow(userWalletId).scanResponse
val config = CardConfig.createConfig(scanResponse.card)
val blockchain = Blockchain.fromNetworkId(networkId) ?: return false
return config.primaryCurve(blockchain) == EllipticCurve.Ed25519Slip0010 &&
scanResponse.cardTypesResolver.isWallet2()
}
override fun areTokensSupportedByNetwork(networkId: String): Boolean {
return Blockchain.fromNetworkId(networkId)?.canHandleTokens() ?: false
}
private suspend fun getWalletOrThrow(userWalletId: UserWalletId): UserWallet {
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Requested UserWallet not found"
}
}
private fun getBlockchainOrThrow(networkId: String): Blockchain {
return requireNotNull(Blockchain.fromNetworkId(networkId)) {
"Requested network not found"
}
}
}

View file

@ -1,79 +0,0 @@
package com.tangem.data.tokens.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.data.tokens.paging.CoinsPagingSource
import com.tangem.data.tokens.utils.FoundTokenConverter
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.domain.tokens.model.FoundToken
import com.tangem.domain.tokens.model.Token
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensListRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
/**
* Default repository implementation for managing operations related to a complete set of tokens
*
* @property tangemTechApi Tangem Tech API
* @property dispatchers coroutine dispatchers provider
* @property quotesRepository responsible for providing cryptocurrency quotes data
*
*/
internal class DefaultTokensListRepository(
private val tangemTechApi: TangemTechApi,
private val dispatchers: CoroutineDispatcherProvider,
private val quotesRepository: QuotesRepository,
) : TokensListRepository {
override fun getTokens(searchText: String?): Flow<PagingData<Token>> {
return Pager(
config = PagingConfig(
pageSize = 100,
prefetchDistance = 70,
enablePlaceholders = false,
),
pagingSourceFactory = {
CoinsPagingSource(
api = tangemTechApi,
dispatchers = dispatchers,
searchText = searchText,
quotesRepository = quotesRepository,
)
},
).flow
}
override suspend fun findToken(contractAddress: String, networkId: String): FoundToken? {
return withContext(dispatchers.io) {
val foundCoin = tangemTechApi.getCoins(
contractAddress = contractAddress,
networkIds = networkId,
).getOrThrow().coins.firstNotNullOfOrNull { coin ->
val tokenNetwork = coin.networks.filter { network ->
network.contractAddress != null && network.decimalCount != null &&
network.contractAddress?.equals(contractAddress, ignoreCase = true) == true &&
networkId == network.networkId
}
if (tokenNetwork.isNotEmpty()) {
coin.copy(networks = tokenNetwork)
} else {
null
}
}
foundCoin?.let { FoundTokenConverter.convert(foundCoin) }
}
}
override fun validateAddress(contractAddress: String, networkId: String): Boolean {
return when (val blockchain = Blockchain.fromNetworkId(networkId) ?: Blockchain.Unknown) {
Blockchain.Unknown, Blockchain.Binance, Blockchain.BinanceTestnet -> true
else -> blockchain.validateAddress(contractAddress)
}
}
}