Updated on 2026-08-14
This commit is contained in:
commit
0f606f723c
31 changed files with 165 additions and 62 deletions
|
|
@ -32,6 +32,7 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
|||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -174,6 +175,9 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
@Inject
|
||||
lateinit var cardRepository: CardRepository
|
||||
|
||||
@Inject
|
||||
lateinit var feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles
|
||||
// endregion Injected
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -261,6 +265,7 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
wasTwinsOnboardingShownUseCase = wasTwinsOnboardingShownUseCase,
|
||||
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
|
||||
cardRepository = cardRepository,
|
||||
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -87,8 +87,6 @@ sealed class Onboarding(
|
|||
class SetupFinished : Twins("Twin Setup Finished")
|
||||
}
|
||||
|
||||
class ButtonChat : Onboarding("Onboarding", "Button - Chat")
|
||||
|
||||
class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding(
|
||||
category = "Onboarding / Biometric",
|
||||
event = "Enable Biometric",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tap.common.feedback
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
|
||||
internal class DefaultFeedbackManagerFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : FeedbackManagerFeatureToggles {
|
||||
|
||||
override val isLocalLogsEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "LOCAL_USER_LOGS_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.tap.common.feedback.DefaultFeedbackManagerFeatureToggles
|
||||
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 FeedbackManagerFeatureTogglesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFeedbackManagerFeatureToggles(
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
): FeedbackManagerFeatureToggles {
|
||||
return DefaultFeedbackManagerFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ internal object CardSDKLoggerModule {
|
|||
Log.Level.View,
|
||||
Log.Level.Network,
|
||||
Log.Level.Error,
|
||||
Log.Level.Biometric,
|
||||
)
|
||||
|
||||
return TangemCardSDKLogger(
|
||||
|
|
|
|||
|
|
@ -8,11 +8,15 @@ internal class DelegatedKeystoreManager(
|
|||
private val keystoreManagerProvider: Provider<KeystoreManager>,
|
||||
) : KeystoreManager {
|
||||
|
||||
override suspend fun authenticateAndGetKey(keyAlias: String): SecretKey? {
|
||||
return keystoreManagerProvider().authenticateAndGetKey(keyAlias)
|
||||
override suspend fun get(keyAlias: String): SecretKey? {
|
||||
return keystoreManagerProvider().get(keyAlias)
|
||||
}
|
||||
|
||||
override suspend fun storeKey(keyAlias: String, key: SecretKey) {
|
||||
keystoreManagerProvider().storeKey(keyAlias, key)
|
||||
override suspend fun get(keyAliases: Collection<String>): Map<String, SecretKey> {
|
||||
return keystoreManagerProvider().get(keyAliases)
|
||||
}
|
||||
|
||||
override suspend fun store(keyAlias: String, key: SecretKey) {
|
||||
return keystoreManagerProvider().store(keyAlias, key)
|
||||
}
|
||||
}
|
||||
|
|
@ -81,41 +81,38 @@ internal class BiometricUserWalletsKeysRepository(
|
|||
}
|
||||
|
||||
private suspend fun getAllInternal(): CompletionResult<List<UserWalletEncryptionKey>> {
|
||||
return getUserWalletsIds()
|
||||
.map { userWalletId ->
|
||||
getEncryptionKey(userWalletId)
|
||||
.doOnFailure { error ->
|
||||
when (error) {
|
||||
is TangemSdkError.UserCanceledAuthentication -> {
|
||||
// If the user cancels biometric authentication, then cancel operation with error
|
||||
return CompletionResult.Failure(error)
|
||||
}
|
||||
is TangemSdkError.KeystoreInvalidated -> {
|
||||
// If the biometric cryptography key was invalidated,
|
||||
// then delete all user wallets encryption keys and cancel operation with error
|
||||
getUserWalletsIds().forEach { userWalletId ->
|
||||
deleteEncryptionKey(userWalletId)
|
||||
}
|
||||
return catching {
|
||||
val userWalletIds = getUserWalletsIds()
|
||||
|
||||
return CompletionResult.Failure(error)
|
||||
}
|
||||
getEncryptionKeys(userWalletIds)
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
when (error) {
|
||||
is TangemSdkError.KeystoreInvalidated -> {
|
||||
// If the biometric cryptography key was invalidated, then delete all encryption keys
|
||||
getUserWalletsIds().forEach { userWalletId ->
|
||||
deleteEncryptionKey(userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
.fold(listOf()) { acc, data ->
|
||||
if (data != null) acc + data else acc
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getEncryptionKey(userWalletId: UserWalletId): CompletionResult<UserWalletEncryptionKey?> {
|
||||
return catching { authenticatedStorage.get(StorageKey.UserWalletEncryptionKey(userWalletId).name) }
|
||||
.map { it.decodeToKey() }
|
||||
private suspend fun getEncryptionKeys(userWalletsIds: List<UserWalletId>): List<UserWalletEncryptionKey> {
|
||||
val keys = userWalletsIds.map { userWalletId ->
|
||||
StorageKey.UserWalletEncryptionKey(userWalletId).name
|
||||
}
|
||||
|
||||
return authenticatedStorage.get(keys).mapNotNull { (_, encodedData) ->
|
||||
encodedData.decodeToKey()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeEncryptionKey(encryptionKey: UserWalletEncryptionKey): CompletionResult<Unit> {
|
||||
return catching {
|
||||
authenticatedStorage.store(
|
||||
key = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
|
||||
keyAlias = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
|
||||
data = encryptionKey.encode(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import android.view.MenuInflater
|
|||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -21,7 +21,7 @@ class OnboardingMenuProvider : MenuProvider {
|
|||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.menu_item_chat_support -> {
|
||||
Analytics.send(Onboarding.ButtonChat())
|
||||
Analytics.send(Basic.ButtonSupport())
|
||||
// changed on email support [REDACTED_TASK_KEY]
|
||||
store.dispatch(GlobalAction.SendEmail(SupportInfo()))
|
||||
true
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.common.CardIdFormatter
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
|
|
@ -501,6 +502,7 @@ class OnboardingWalletFragment :
|
|||
private fun makeSeedPhraseRouter(): SeedPhraseRouter = SeedPhraseRouter(
|
||||
onBack = ::legacyOnBackHandler,
|
||||
onOpenChat = {
|
||||
Analytics.send(Basic.ButtonSupport())
|
||||
// changed on email support [REDACTED_TASK_KEY]
|
||||
store.dispatch(GlobalAction.SendEmail(SupportInfo()))
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
|||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -19,6 +21,7 @@ object WalletActivationErrorDialog {
|
|||
setPositiveButton(R.string.common_ok) { _, _ -> dialog.onConfirm() }
|
||||
setNegativeButton(R.string.common_support) { _, _ ->
|
||||
// changed on email support [REDACTED_TASK_KEY]
|
||||
Analytics.send(Basic.ButtonSupport())
|
||||
store.dispatch(GlobalAction.SendEmail(SupportInfo()))
|
||||
}
|
||||
setOnDismissListener { store.dispatchDialogHide() }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.tangem.tap.features.send.ui.dialogs
|
|||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
|
|
@ -19,6 +21,7 @@ object RequestFeeErrorDialog {
|
|||
setTitle(R.string.common_fee_error)
|
||||
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage))
|
||||
setNegativeButton(R.string.details_row_title_contact_to_support) { _, _ ->
|
||||
Analytics.send(Basic.ButtonSupport())
|
||||
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage)))
|
||||
}
|
||||
setPositiveButton(R.string.common_retry) { _, _ -> dialog.onRetry() }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import androidx.appcompat.app.AlertDialog
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.common.module.ModuleMessageConverter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.sdk.extensions.localizedDescription
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
|
||||
|
|
@ -31,6 +33,7 @@ object SendTransactionFailsDialog {
|
|||
setTitle(R.string.alert_failed_to_send_transaction_title)
|
||||
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage))
|
||||
setNeutralButton(R.string.details_row_title_contact_to_support) { _, _ ->
|
||||
Analytics.send(Basic.ButtonSupport())
|
||||
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage)))
|
||||
}
|
||||
setPositiveButton(R.string.common_cancel) { _, _ -> }
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ internal class DefaultTokensListRepository(
|
|||
private val testnetTokensStorage: TestnetTokensStorage,
|
||||
) : TokensListRepository {
|
||||
|
||||
override fun getAvailableTokens(searchText: String?): Flow<PagingData<Token>> {
|
||||
override fun getAvailableTokens(searchText: String?, needFilterExcluded: Boolean): Flow<PagingData<Token>> {
|
||||
return Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = 100,
|
||||
|
|
@ -42,6 +42,7 @@ internal class DefaultTokensListRepository(
|
|||
dispatchers = dispatchers,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
searchText = searchText,
|
||||
needFilterExcluded = needFilterExcluded,
|
||||
)
|
||||
|
||||
getSelectedWalletSyncUseCase().fold(
|
||||
|
|
|
|||
|
|
@ -27,8 +27,10 @@ internal class TangemApiTokensPagingSource(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val searchText: String?,
|
||||
needFilterExcluded: Boolean = false,
|
||||
) : PagingSource<Int, Token>() {
|
||||
|
||||
private val coinsResponseConverter = CoinsResponseConverter(needFilterExcluded)
|
||||
override fun getRefreshKey(state: PagingState<Int, Token>): Int? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(other = 1)
|
||||
|
|
@ -41,7 +43,7 @@ internal class TangemApiTokensPagingSource(
|
|||
|
||||
return runCatching(dispatchers.io) {
|
||||
val supportedBlockchains = getSelectedWalletSyncUseCase().fold(
|
||||
ifLeft = { Blockchain.values().toList() },
|
||||
ifLeft = { Blockchain.entries },
|
||||
ifRight = { it.scanResponse.card.supportedBlockchains(it.scanResponse.cardTypesResolver) },
|
||||
)
|
||||
|
||||
|
|
@ -55,7 +57,7 @@ internal class TangemApiTokensPagingSource(
|
|||
}.fold(
|
||||
onSuccess = { response ->
|
||||
LoadResult.Page(
|
||||
data = CoinsResponseConverter.convert(response),
|
||||
data = coinsResponseConverter.convert(response),
|
||||
prevKey = if (page == 0) null else page.minus(other = 1),
|
||||
nextKey = if (response.coins.isEmpty()) null else page.plus(other = 1),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.features.tokens.impl.data.converters
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.isSupportedInApp
|
||||
import com.tangem.tap.features.tokens.impl.domain.models.Token
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
|
|
@ -11,9 +12,7 @@ import com.tangem.utils.converter.Converter
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object CoinsResponseConverter : Converter<CoinsResponse, List<Token>> {
|
||||
|
||||
private const val DEFAULT_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/"
|
||||
internal class CoinsResponseConverter(val needFilterExcluded: Boolean) : Converter<CoinsResponse, List<Token>> {
|
||||
|
||||
override fun convert(value: CoinsResponse): List<Token> {
|
||||
return value.coins.map { token ->
|
||||
|
|
@ -25,6 +24,10 @@ internal object CoinsResponseConverter : Converter<CoinsResponse, List<Token>> {
|
|||
networks = token.networks.mapNotNull { network ->
|
||||
val blockchain = Blockchain.fromNetworkId(network.networkId) ?: return@mapNotNull null
|
||||
|
||||
if (needFilterExcluded && !blockchain.isSupportedInApp()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
// filter tokens, if contractAddress != null, assume that it is a token
|
||||
if (network.contractAddress != null &&
|
||||
!blockchain.canHandleTokens()
|
||||
|
|
@ -47,4 +50,8 @@ internal object CoinsResponseConverter : Converter<CoinsResponse, List<Token>> {
|
|||
fun getIconUrl(id: String, imageHost: String? = null): String {
|
||||
return "${imageHost ?: DEFAULT_IMAGE_HOST}large/$id.png"
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val DEFAULT_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/"
|
||||
}
|
||||
}
|
||||
|
|
@ -13,13 +13,14 @@ import com.tangem.utils.converter.Converter
|
|||
*/
|
||||
internal object TestnetTokensConfigConverter : Converter<TestnetTokensConfig, List<Token>> {
|
||||
|
||||
private val coinsResponseConverter = CoinsResponseConverter(needFilterExcluded = false)
|
||||
override fun convert(value: TestnetTokensConfig): List<Token> {
|
||||
return value.tokens.map { token ->
|
||||
Token(
|
||||
id = token.id,
|
||||
name = token.name,
|
||||
symbol = token.symbol,
|
||||
iconUrl = CoinsResponseConverter.getIconUrl(token.id),
|
||||
iconUrl = coinsResponseConverter.getIconUrl(token.id),
|
||||
networks = token.networks?.mapNotNull { network ->
|
||||
val blockchain = Blockchain.fromNetworkId(network.id) ?: return@mapNotNull null
|
||||
|
||||
|
|
@ -27,7 +28,7 @@ internal object TestnetTokensConfigConverter : Converter<TestnetTokensConfig, Li
|
|||
id = network.id,
|
||||
blockchain = blockchain,
|
||||
address = network.address,
|
||||
iconUrl = CoinsResponseConverter.getIconUrl(network.id),
|
||||
iconUrl = coinsResponseConverter.getIconUrl(network.id),
|
||||
decimalCount = network.decimalCount,
|
||||
)
|
||||
}.orEmpty(),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ import kotlinx.coroutines.flow.Flow
|
|||
*/
|
||||
internal class DefaultTokensListInteractor(private val repository: TokensListRepository) : TokensListInteractor {
|
||||
|
||||
override fun getTokensList(searchText: String): Flow<PagingData<Token>> {
|
||||
return repository.getAvailableTokens(searchText = searchText.ifBlank(defaultValue = { null }))
|
||||
override fun getTokensList(searchText: String, needFilterExcluded: Boolean): Flow<PagingData<Token>> {
|
||||
return repository.getAvailableTokens(
|
||||
searchText = searchText.ifBlank(defaultValue = { null }),
|
||||
needFilterExcluded = needFilterExcluded,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,5 +12,5 @@ import kotlinx.coroutines.flow.Flow
|
|||
internal interface TokensListInteractor {
|
||||
|
||||
/** Get tokens list using filter by text [searchText] */
|
||||
fun getTokensList(searchText: String): Flow<PagingData<Token>>
|
||||
fun getTokensList(searchText: String, needFilterExcluded: Boolean): Flow<PagingData<Token>>
|
||||
}
|
||||
|
|
@ -15,8 +15,9 @@ internal interface TokensListRepository {
|
|||
* Get available tokens list
|
||||
*
|
||||
* @param searchText search text
|
||||
* @param needFilterExcluded flag that determines if excluded tokens should be filtered
|
||||
*
|
||||
* @throws com.tangem.datasource.api.common.response.ApiResponseError
|
||||
*/
|
||||
fun getAvailableTokens(searchText: String?): Flow<PagingData<Token>>
|
||||
fun getAvailableTokens(searchText: String?, needFilterExcluded: Boolean): Flow<PagingData<Token>>
|
||||
}
|
||||
|
|
@ -250,7 +250,7 @@ internal class TokensListViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun getTokensListBySearchText(text: String): Flow<PagingData<TokenItemState>> {
|
||||
return interactor.getTokensList(searchText = text)
|
||||
return interactor.getTokensList(searchText = text, needFilterExcluded = !isManageAccess)
|
||||
.mapToTokenItemState()
|
||||
.cachedIn(viewModelScope)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package com.tangem.tap.proxy
|
|||
import androidx.core.text.isDigitsOnly
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.blockchains.algorand.AlgorandTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.blockchains.hedera.HederaTransactionBuilder
|
||||
import com.tangem.blockchain.blockchains.optimism.OptimismWalletManager
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarMemo
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
|
||||
|
|
@ -138,6 +140,8 @@ class TransactionManagerImpl(
|
|||
Blockchain.XRP -> memo.toLongOrNull()?.let { XrpTransactionBuilder.XrpTransactionExtras(it) }
|
||||
Blockchain.Cosmos -> CosmosTransactionExtras(memo)
|
||||
Blockchain.TON -> TonTransactionExtras(memo)
|
||||
Blockchain.Hedera -> HederaTransactionBuilder.HederaTransactionExtras(memo)
|
||||
Blockchain.Algorand -> AlgorandTransactionExtras(memo)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.domain.card.ScanCardProcessor
|
|||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -65,4 +66,5 @@ data class DaggerGraphState(
|
|||
val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null,
|
||||
val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null,
|
||||
val cardRepository: CardRepository? = null,
|
||||
val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles? = null,
|
||||
) : StateType
|
||||
|
|
@ -75,4 +75,6 @@ sealed class Basic(
|
|||
)
|
||||
|
||||
class WalletOpened : Basic(event = "Wallet Opened")
|
||||
|
||||
class ButtonSupport : Basic("Request Support")
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ internal object AccountCreatorModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAccountCreator(authProvider: AuthProvider, @DevTangemApi tangemTechApi: TangemTechApi): AccountCreator {
|
||||
fun provideAccountCreator(authProvider: AuthProvider, tangemTechApi: TangemTechApi): AccountCreator {
|
||||
return DefaultAccountCreator(authProvider, tangemTechApi)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,5 +18,9 @@
|
|||
{
|
||||
"name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED",
|
||||
"version": "5.7.0"
|
||||
},
|
||||
{
|
||||
"name": "LOCAL_USER_LOGS_ENABLED",
|
||||
"version": "5.8.0"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.feedback
|
||||
|
||||
interface FeedbackManagerFeatureToggles {
|
||||
|
||||
val isLocalLogsEnabled: Boolean
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.domain.tokens
|
|||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.FeePaidCurrency
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
|
|
@ -35,17 +34,13 @@ class GetFeePaidCryptoCurrencyStatusSyncUseCase(
|
|||
|
||||
return either {
|
||||
when (feePaidCurrency) {
|
||||
FeePaidCurrency.Coin -> getCryptoCurrencyStatus(operations, cryptoCurrency.id)
|
||||
FeePaidCurrency.Coin ->
|
||||
operations
|
||||
.getNetworkCoinSync(cryptoCurrency.network.id, cryptoCurrency.network.derivationPath)
|
||||
.getOrNull()
|
||||
FeePaidCurrency.SameCurrency -> cryptoCurrencyStatus
|
||||
is FeePaidCurrency.Token -> getCryptoCurrencyStatus(operations, feePaidCurrency.tokenId)
|
||||
is FeePaidCurrency.Token -> operations.getCurrencyStatusSync(feePaidCurrency.tokenId).getOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCryptoCurrencyStatus(
|
||||
operations: CurrenciesStatusesOperations,
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
): CryptoCurrencyStatus? {
|
||||
return operations.getCurrencyStatusSync(cryptoCurrencyId).getOrNull()
|
||||
}
|
||||
}
|
||||
|
|
@ -109,6 +109,18 @@ internal class CurrenciesStatusesOperations(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun getNetworkCoinSync(
|
||||
networkId: Network.ID,
|
||||
derivationPath: Network.DerivationPath,
|
||||
): Either<Error, CryptoCurrencyStatus> {
|
||||
val currency = recover(
|
||||
block = { getNetworkCoin(networkId, derivationPath) },
|
||||
recover = { return it.left() },
|
||||
)
|
||||
|
||||
return getCurrencyStatusSync(currency.id)
|
||||
}
|
||||
|
||||
suspend fun getPrimaryCurrencyStatusSync(): Either<Error, CryptoCurrencyStatus> = either {
|
||||
val currency = catch(
|
||||
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
|
||||
|
|
|
|||
|
|
@ -915,10 +915,10 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
|
||||
fun addAlert(uiState: SwapStateHolder, onClick: () -> Unit): SwapStateHolder {
|
||||
fun addAlert(uiState: SwapStateHolder, message: TextReference? = null, onClick: () -> Unit): SwapStateHolder {
|
||||
return uiState.copy(
|
||||
alert = SwapWarning.GenericWarning(
|
||||
message = null,
|
||||
message = message,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import arrow.core.getOrElse
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.utils.InputNumberFormatter
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -119,7 +120,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
val cryptoCurrencyStatus =
|
||||
getCryptoCurrencyStatusUseCase(it.walletId, initialCryptoCurrency.id).getOrNull()
|
||||
if (cryptoCurrencyStatus == null) {
|
||||
uiState = stateBuilder.addAlert(uiState, swapRouter::back)
|
||||
uiState = stateBuilder.addAlert(uiState = uiState, onClick = swapRouter::back)
|
||||
} else {
|
||||
initialCryptoCurrencyStatus = cryptoCurrencyStatus
|
||||
initTokens()
|
||||
|
|
@ -482,7 +483,12 @@ internal class SwapViewModel @Inject constructor(
|
|||
return
|
||||
}
|
||||
val fromCurrency = requireNotNull(dataState.fromCryptoCurrency)
|
||||
val fee = requireNotNull(dataState.selectedFee)
|
||||
val fee = dataState.selectedFee
|
||||
// TODO: unexpected crash for some users, this workaround to prevent app crash and follow to support
|
||||
if (fee == null) {
|
||||
makeDefaultAlert(TextReference.Str("Fee estimation error. Please send feedback to support."))
|
||||
return
|
||||
}
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
runCatching(dispatchers.io) {
|
||||
swapInteractor.onSwap(
|
||||
|
|
@ -821,6 +827,12 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun makeDefaultAlert(message: TextReference) {
|
||||
uiState = stateBuilder.addAlert(uiState, message) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun createUiActions(): UiActions {
|
||||
return UiActions(
|
||||
|
|
|
|||
|
|
@ -85,9 +85,9 @@ web3j = "4.10.1"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-510"
|
||||
tangemBlockchainSdk = "develop-514"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-324"
|
||||
tangemCardSdk = "develop-332"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
# endregion Tangem
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue