Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-27 15:59:43 +03:00
commit 71748eff67
517 changed files with 14212 additions and 3419 deletions

View file

@ -77,6 +77,7 @@ import com.tangem.tap.routing.utils.DeepLinkFactory
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import com.tangem.utils.extensions.uriValidate
import com.tangem.wallet.BuildConfig
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@ -353,7 +354,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
intentProcessor.removeAll()
// workaround: kill process when activity destroy to avoid state when lock() wallets
// and navigation to unlock screen was skipped because system kills activity but not process
android.os.Process.killProcess(android.os.Process.myPid())
if (BuildConfig.BUILD_TYPE != MOCKED_BUILD_TYPE) {
android.os.Process.killProcess(android.os.Process.myPid())
}
super.onDestroy()
}
@ -556,5 +559,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
companion object {
private const val APP_THEME_LOAD_TIMEOUT = 2
private const val MOCKED_BUILD_TYPE = "mocked"
}
}

View file

@ -3,11 +3,9 @@ package com.tangem.tap.data
import android.content.Context
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.services.secure.SecureStorage
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.domain.visa.model.VisaAuthTokens
import com.tangem.sdk.storage.AndroidSecureStorage
import com.tangem.sdk.storage.createEncryptedSharedPreferences
import com.tangem.sdk.storage.AndroidSecureStorageV2
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.withContext
@ -21,11 +19,9 @@ internal class DefaultVisaAuthTokenStorage @Inject constructor(
) : VisaAuthTokenStorage {
private val secureStorage by lazy {
AndroidSecureStorage(
preferences = SecureStorage.createEncryptedSharedPreferences(
context = applicationContext,
storageName = "visa_auth_storage",
),
AndroidSecureStorageV2(
appContext = applicationContext,
name = "visa_auth_storage",
)
}

View file

@ -3,11 +3,9 @@ package com.tangem.tap.data
import android.content.Context
import com.tangem.common.extensions.toByteArray
import com.tangem.common.extensions.toInt
import com.tangem.common.services.secure.SecureStorage
import com.tangem.datasource.local.visa.VisaOTPStorage
import com.tangem.datasource.local.visa.VisaOtpData
import com.tangem.sdk.storage.AndroidSecureStorage
import com.tangem.sdk.storage.createEncryptedSharedPreferences
import com.tangem.sdk.storage.AndroidSecureStorageV2
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.withContext
@ -21,11 +19,9 @@ class DefaultVisaOTPStorage @Inject constructor(
) : VisaOTPStorage {
private val secureStorage by lazy {
AndroidSecureStorage(
preferences = SecureStorage.createEncryptedSharedPreferences(
context = applicationContext,
storageName = "visa_otp_storage",
),
AndroidSecureStorageV2(
appContext = applicationContext,
name = "visa_otp_storage",
)
}

View file

@ -30,8 +30,10 @@ internal object RootAppComponentContextModule {
componentBuilder: ModelComponent.Builder,
@GlobalUiMessageSender messageSender: UiMessageSender,
): AppComponentContext {
val activity = context as AppCompatActivity
return DefaultAppComponentContext(
componentContext = (context as AppCompatActivity).defaultComponentContext(),
componentContext = activity.defaultComponentContext(),
activity = activity,
dispatchers = dispatchers,
hiltComponentBuilder = componentBuilder,
messageSender = messageSender,

View file

@ -5,7 +5,7 @@ import com.tangem.domain.managetokens.*
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.managetokens.repository.ManageTokensRepository
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.TokensFeatureToggles
@ -36,8 +36,10 @@ internal object ManageTokensDomainModule {
@Provides
@Singleton
fun provideCreateCurrencyUseCase(customTokensRepository: CustomTokensRepository): CreateCurrencyUseCase {
return CreateCurrencyUseCase(customTokensRepository)
fun provideCreateCryptoCurrencyUseCase(
customTokensRepository: CustomTokensRepository,
): CreateCryptoCurrencyUseCase {
return CreateCryptoCurrencyUseCase(customTokensRepository)
}
@Provides
@ -71,7 +73,7 @@ internal object ManageTokensDomainModule {
derivationsRepository: DerivationsRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteFetcher: MultiQuoteFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): SaveManagedTokensUseCase {
@ -82,7 +84,7 @@ internal object ManageTokensDomainModule {
derivationsRepository = derivationsRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteFetcher = multiQuoteFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)

View file

@ -6,8 +6,8 @@ import com.tangem.domain.markets.*
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
import com.tangem.domain.quotes.single.SingleQuoteSupplier
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
@ -52,8 +52,8 @@ object MarketsDomainModule {
@Provides
@Singleton
fun provideGetTokenQuotesUseCase(singleQuoteSupplier: SingleQuoteSupplier): GetCurrencyQuotesUseCase {
return GetCurrencyQuotesUseCase(singleQuoteSupplier = singleQuoteSupplier)
fun provideGetTokenQuotesUseCase(singleQuoteStatusSupplier: SingleQuoteStatusSupplier): GetCurrencyQuotesUseCase {
return GetCurrencyQuotesUseCase(singleQuoteStatusSupplier = singleQuoteStatusSupplier)
}
@Provides
@ -64,7 +64,7 @@ object MarketsDomainModule {
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteFetcher: MultiQuoteFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): SaveMarketTokensUseCase {
@ -74,7 +74,7 @@ object MarketsDomainModule {
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteFetcher = multiQuoteFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)

View file

@ -3,8 +3,8 @@ package com.tangem.tap.di.domain
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
import com.tangem.domain.nft.*
import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.quotes.single.SingleQuoteFetcher
import com.tangem.domain.quotes.single.SingleQuoteSupplier
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -92,18 +92,18 @@ internal object NFTDomainModule {
@Singleton
fun provideGetNFTPriceUseCase(
nftRepository: NFTRepository,
singleQuoteSupplier: SingleQuoteSupplier,
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
): GetNFTPriceUseCase {
return GetNFTPriceUseCase(nftRepository, singleQuoteSupplier)
return GetNFTPriceUseCase(nftRepository, singleQuoteStatusSupplier)
}
@Provides
@Singleton
fun provideFetchNFTPriceUseCase(
nftRepository: NFTRepository,
singleQuoteFetcher: SingleQuoteFetcher,
singleQuoteStatusFetcher: SingleQuoteStatusFetcher,
): FetchNFTPriceUseCase {
return FetchNFTPriceUseCase(nftRepository, singleQuoteFetcher)
return FetchNFTPriceUseCase(nftRepository, singleQuoteStatusFetcher)
}
@Provides

View file

@ -1,12 +1,19 @@
package com.tangem.tap.di.domain
import com.tangem.domain.swap.SwapErrorResolver
import com.tangem.domain.swap.SwapRepositoryV2
import com.tangem.domain.swap.SwapTransactionRepository
import com.tangem.domain.swap.usecase.GetSwapPairsUseCase
import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase
import com.tangem.domain.swap.usecase.SelectInitialPairUseCase
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
import com.tangem.feature.swap.domain.api.SwapRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import com.tangem.feature.swap.domain.api.SwapRepository as OldSwapRepository
/**
[REDACTED_AUTHOR]
@ -17,7 +24,53 @@ internal object SwapDomainModule {
@Provides
@Singleton
fun provideGetAvailablePairsUseCase(swapRepository: SwapRepository): GetAvailablePairsUseCase {
fun provideGetAvailablePairsUseCase(swapRepository: OldSwapRepository): GetAvailablePairsUseCase {
return GetAvailablePairsUseCase(swapRepository = swapRepository)
}
@Provides
@Singleton
fun provideGetSwapSupportedPairsUseCase(
swapRepositoryV2: SwapRepositoryV2,
swapErrorResolver: SwapErrorResolver,
): GetSwapSupportedPairsUseCase {
return GetSwapSupportedPairsUseCase(
swapRepositoryV2 = swapRepositoryV2,
swapErrorResolver = swapErrorResolver,
)
}
@Provides
@Singleton
fun provideGetSwapPairsUseCase(
swapRepositoryV2: SwapRepositoryV2,
swapErrorResolver: SwapErrorResolver,
): GetSwapPairsUseCase {
return GetSwapPairsUseCase(
swapRepositoryV2 = swapRepositoryV2,
swapErrorResolver = swapErrorResolver,
)
}
@Provides
@Singleton
fun provideSelectInitialPairUseCase(
swapTransactionRepository: SwapTransactionRepository,
): SelectInitialPairUseCase {
return SelectInitialPairUseCase(
swapTransactionRepository = swapTransactionRepository,
)
}
@Provides
@Singleton
fun provideGetSwapQuoteUseCase(
swapRepositoryV2: SwapRepositoryV2,
swapErrorResolver: SwapErrorResolver,
): GetSwapQuoteUseCase {
return GetSwapQuoteUseCase(
swapRepositoryV2 = swapRepositoryV2,
swapErrorResolver = swapErrorResolver,
)
}
}

View file

@ -8,9 +8,9 @@ import com.tangem.domain.networks.repository.NetworksRepository
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.quotes.QuotesRepositoryV2
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
import com.tangem.domain.quotes.single.SingleQuoteSupplier
import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
@ -42,7 +42,7 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteFetcher: MultiQuoteFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): AddCryptoCurrenciesUseCase {
@ -50,7 +50,7 @@ internal object TokensDomainModule {
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteFetcher = multiQuoteFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
@ -62,7 +62,7 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteFetcher: MultiQuoteFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): FetchTokenListUseCase {
@ -70,7 +70,7 @@ internal object TokensDomainModule {
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteFetcher = multiQuoteFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
@ -161,7 +161,7 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
multiQuoteFetcher: MultiQuoteFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): FetchCurrencyStatusUseCase {
@ -169,7 +169,7 @@ internal object TokensDomainModule {
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
multiQuoteFetcher = multiQuoteFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
@ -181,7 +181,7 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteFetcher: MultiQuoteFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): FetchCardTokenListUseCase {
@ -189,7 +189,7 @@ internal object TokensDomainModule {
currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteFetcher = multiQuoteFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
)
@ -346,11 +346,11 @@ internal object TokensDomainModule {
@Singleton
fun provideRefreshMultiCurrencyWalletQuotesUseCase(
currenciesRepository: CurrenciesRepository,
multiQuoteFetcher: MultiQuoteFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
): RefreshMultiCurrencyWalletQuotesUseCase {
return RefreshMultiCurrencyWalletQuotesUseCase(
currenciesRepository = currenciesRepository,
multiQuoteFetcher = multiQuoteFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
)
}
@ -368,27 +368,27 @@ internal object TokensDomainModule {
fun provideBaseCurrenciesStatusesOperations(
tokensFeatureToggles: TokensFeatureToggles,
currenciesRepository: CurrenciesRepository,
quotesRepositoryV2: QuotesRepositoryV2,
quotesRepository: QuotesRepository,
stakingRepository: StakingRepository,
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
multiQuoteFetcher: MultiQuoteFetcher,
singleQuoteSupplier: SingleQuoteSupplier,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
): BaseCurrenciesStatusesOperations {
return CachedCurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepositoryV2 = quotesRepositoryV2,
quotesRepository = quotesRepository,
stakingRepository = stakingRepository,
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
multiQuoteFetcher = multiQuoteFetcher,
singleQuoteSupplier = singleQuoteSupplier,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
@ -400,27 +400,27 @@ internal object TokensDomainModule {
fun provideBaseCurrencyStatusOperations(
tokensFeatureToggles: TokensFeatureToggles,
currenciesRepository: CurrenciesRepository,
quotesRepositoryV2: QuotesRepositoryV2,
quotesRepository: QuotesRepository,
stakingRepository: StakingRepository,
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
multiQuoteFetcher: MultiQuoteFetcher,
singleQuoteSupplier: SingleQuoteSupplier,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
): BaseCurrencyStatusOperations {
return CachedCurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepositoryV2 = quotesRepositoryV2,
quotesRepository = quotesRepository,
stakingRepository = stakingRepository,
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
multiQuoteFetcher = multiQuoteFetcher,
singleQuoteSupplier = singleQuoteSupplier,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,

View file

@ -79,6 +79,18 @@ internal object TransactionDomainModule {
)
}
@Provides
@Singleton
fun provideOpenTrustlineUseCase(
cardSdkConfigRepository: CardSdkConfigRepository,
walletManagersFacade: WalletManagersFacade,
): OpenTrustlineUseCase {
return OpenTrustlineUseCase(
cardSdkConfigRepository = cardSdkConfigRepository,
walletManagersFacade = walletManagersFacade,
)
}
@Provides
@Singleton
fun provideDismissIncompleteTransactionUseCase(

View file

@ -13,6 +13,7 @@ import com.tangem.domain.visa.model.VisaActivationRemoteState
import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.sdk.storage.AndroidSecureStorage
import com.tangem.sdk.storage.AndroidSecureStorageV2
import com.tangem.sdk.storage.createEncryptedSharedPreferences
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
import com.tangem.tap.domain.userWalletList.implementation.GeneralUserWalletsListManager
@ -80,6 +81,10 @@ internal object UserWalletsListManagerModule {
context = applicationContext,
storageName = "user_wallets_storage",
),
androidSecureStorageV2 = AndroidSecureStorageV2(
appContext = applicationContext,
name = "user_wallets_storage2",
),
)
val authenticatedStorage = AuthenticatedStorage(

View file

@ -83,11 +83,14 @@ internal class VisaCardScanHandler @Inject constructor(
Timber.i("Requesting challenge for wallet authorization")
val challengeResponse = visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value)
.getOrElse {
Timber.i("Failed to get Access token for Wallet public key authorization")
return CompletionResult.Failure(it.tangemError)
}
val challengeResponse = visaAuthRepository.getCardWalletAuthChallenge(
cardId = card.cardId,
// This is the wallet public key, not the address and it's alright, as the API expects it in this format
cardWalletAddress = wallet.publicKey.toHexString(),
).getOrElse {
Timber.i("Failed to get Access token for Wallet public key authorization")
return CompletionResult.Failure(it.tangemError)
}
val signChallengeResult = signChallengeWithWallet(
publicKey = wallet.publicKey,
@ -96,15 +99,16 @@ internal class VisaCardScanHandler @Inject constructor(
return when (signChallengeResult) {
is CompletionResult.Success -> {
val signature = signChallengeResult.data.cardSignature ?: run {
Timber.i("Failed to sign challenge with Wallet public key")
return CompletionResult.Failure(VisaCardScanError.FailedToSignChallenge.tangemError)
}
val signature = signChallengeResult.data.walletSignature
val salt = signChallengeResult.data.salt
Timber.i("Challenge signed with Wallet public key")
handleWalletAuthorizationTokens(
cardWalletAddress = walletAddress.value,
signedChallenge = challengeResponse.toSignedChallenge(signature.toHexString()),
signedChallenge = challengeResponse.toSignedChallenge(
signedChallenge = signature.toHexString(),
salt = salt.toHexString(),
),
)
}
is CompletionResult.Failure -> {

View file

@ -16,6 +16,7 @@ import com.tangem.core.ui.utils.findActivity
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.api.HomeComponent
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.compose.StoriesScreenV2
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.store
@ -57,12 +58,22 @@ internal class DefaultHomeComponent @AssistedInject constructor(
val activity = LocalContext.current.findActivity()
BackHandler(onBack = activity::finish)
SystemBarsIconsDisposable(darkIcons = false)
StoriesScreen(
homeState = homeState,
onScanButtonClick = model::onScanClick,
onShopButtonClick = model::onShopClick,
onSearchTokensClick = model::onSearchClick,
)
if (homeState.value.isV2StoriesEnabled) {
StoriesScreenV2(
homeState = homeState,
onCreateNewWalletButtonClick = model::onCreateNewWalletScreen,
onAddExistingWalletButtonClick = model::onAddExistingWalletScreen,
onScanButtonClick = model::onScanClick,
)
} else {
StoriesScreen(
homeState = homeState,
onScanButtonClick = model::onScanClick,
onShopButtonClick = model::onShopClick,
onSearchTokensClick = model::onSearchClick,
)
}
ChangeRootBackgroundColorEffect(Color(color = 0xFF010101))
}

View file

@ -71,6 +71,14 @@ internal class HomeModel @Inject constructor(
.launchIn(modelScope)
}
fun onCreateNewWalletScreen() {
// TODO implement navigation to create new wallet
}
fun onAddExistingWalletScreen() {
// TODO implement navigation to add existing wallet
}
fun onScanClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard())
scanCard()

View file

@ -0,0 +1,252 @@
@file:Suppress("MagicNumber")
package com.tangem.tap.features.home.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags
import com.tangem.tap.features.home.compose.content.*
import com.tangem.tap.features.home.compose.views.HomeButtonsV2
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.home.redux.Stories
import com.tangem.wallet.R
import kotlin.math.max
@Composable
internal fun StoriesScreenV2(
homeState: MutableState<HomeState>,
onCreateNewWalletButtonClick: () -> Unit,
onAddExistingWalletButtonClick: () -> Unit,
onScanButtonClick: () -> Unit,
) {
val state = homeState.value
var currentStory by remember { mutableStateOf(state.firstStory) }
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
val goToPreviousStory = remember(currentStory, currentStoryIndex) {
{ currentStory = state.stories[max(0, currentStoryIndex - 1)] }
}
val goToNextStory = remember(currentStory, currentStoryIndex) {
{
currentStory = if (currentStoryIndex < state.stories.lastIndex) {
state.stories[currentStoryIndex + 1]
} else {
state.firstStory
}
}
}
// todo refactor [REDACTED_TASK_KEY]
StoriesScreenContentV2(
modifier = Modifier
.fillMaxSize()
.testTag(TestTags.STORIES_SCREEN),
config = StoriesScreenContentV2Config(
storiesSize = state.stories.lastIndex,
currentStoryIndex = currentStoryIndex,
currentStory = currentStory,
isScanInProgress = homeState.value.scanInProgress,
onGoToPreviousStory = goToPreviousStory,
onGoToNextStory = goToNextStory,
onCreateNewWalletButtonClick = onCreateNewWalletButtonClick,
onAddExistingWalletButtonClick = onAddExistingWalletButtonClick,
onScanButtonClick = onScanButtonClick,
),
)
}
@Deprecated("Use StoriesContainer from core/ui")
@Suppress("LongMethod")
@Composable
private fun StoriesScreenContentV2(config: StoriesScreenContentV2Config, modifier: Modifier = Modifier) {
var isPressed by remember { mutableStateOf(value = false) }
val isPaused = isPressed || config.isScanInProgress
val currentStoryDuration = config.currentStory.duration
Box(
modifier = modifier.background(Color(0xFF010101)),
) {
Row(
modifier = Modifier.fillMaxSize(),
) {
Box(
Modifier
.weight(1f)
.fillMaxHeight()
.pointerInput(Unit) {
detectTapGestures(
onPress = {
val pressStartTime = System.currentTimeMillis()
isPressed = true
this.tryAwaitRelease()
val pressEndTime = System.currentTimeMillis()
val totalPressTime = pressEndTime - pressStartTime
if (totalPressTime < 200) config.onGoToPreviousStory()
isPressed = false
},
)
},
)
Box(
Modifier
.weight(1f)
.fillMaxHeight()
.pointerInput(Unit) {
detectTapGestures(
onPress = {
val pressStartTime = System.currentTimeMillis()
isPressed = true
this.tryAwaitRelease()
val pressEndTime = System.currentTimeMillis()
val totalPressTime = pressEndTime - pressStartTime
if (totalPressTime < 200) config.onGoToNextStory()
isPressed = false
},
)
},
)
}
Column(
modifier = Modifier
.statusBarsPadding()
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
StoriesProgressBar(
steps = config.storiesSize,
currentStep = config.currentStoryIndex,
stepDuration = currentStoryDuration,
paused = isPaused,
onStepFinish = config.onGoToNextStory,
)
Image(
painter = painterResource(id = R.drawable.ic_tangem_logo),
contentDescription = null,
contentScale = ContentScale.FillHeight,
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing16,
top = TangemTheme.dimens.spacing16,
)
.height(TangemTheme.dimens.size18)
.align(Alignment.Start),
)
when (config.currentStory) {
Stories.TangemIntro -> FirstStoriesContent(
isPaused = isPaused,
duration = currentStoryDuration,
)
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet()
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
isPaused = isPaused,
stepDuration = currentStoryDuration,
)
Stories.Currencies -> StoriesCurrencies(isPaused, currentStoryDuration)
Stories.Web3 -> StoriesWeb3(isPaused, currentStoryDuration)
Stories.WalletForEveryone -> StoriesWalletForEveryone(currentStoryDuration)
}
}
Column(
modifier = Modifier
.navigationBarsPadding()
.padding(bottom = TangemTheme.dimens.spacing16)
.padding(horizontal = TangemTheme.dimens.spacing16)
.align(Alignment.BottomCenter)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
HomeButtonsV2(
modifier = Modifier.fillMaxWidth(),
btnScanStateInProgress = config.isScanInProgress,
onScanButtonClick = config.onScanButtonClick,
onCreateNewWalletButtonClick = config.onCreateNewWalletButtonClick,
onAddExistingWalletButtonClick = config.onAddExistingWalletButtonClick,
)
}
}
}
private data class StoriesScreenContentV2Config(
val storiesSize: Int,
val currentStoryIndex: Int,
val currentStory: Stories,
val isScanInProgress: Boolean,
val onGoToPreviousStory: () -> Unit = {},
val onGoToNextStory: () -> Unit = {},
val onCreateNewWalletButtonClick: () -> Unit = {},
val onAddExistingWalletButtonClick: () -> Unit = {},
val onScanButtonClick: () -> Unit = {},
)
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun StoriesScreenContentV2Preview(
@PreviewParameter(StoriesScreenContentV2ConfigProvider::class) config: StoriesScreenContentV2Config,
) {
TangemThemePreview {
StoriesScreenContentV2(config = config)
}
}
private class StoriesScreenContentV2ConfigProvider : CollectionPreviewParameterProvider<StoriesScreenContentV2Config>(
collection = listOf(
StoriesScreenContentV2Config(
storiesSize = 6,
currentStoryIndex = 0,
currentStory = Stories.TangemIntro,
isScanInProgress = true,
),
StoriesScreenContentV2Config(
storiesSize = 6,
currentStoryIndex = 1,
currentStory = Stories.RevolutionaryWallet,
isScanInProgress = false,
),
StoriesScreenContentV2Config(
storiesSize = 6,
currentStoryIndex = 2,
currentStory = Stories.UltraSecureBackup,
isScanInProgress = false,
),
StoriesScreenContentV2Config(
storiesSize = 6,
currentStoryIndex = 3,
currentStory = Stories.Currencies,
isScanInProgress = false,
),
StoriesScreenContentV2Config(
storiesSize = 6,
currentStoryIndex = 4,
currentStory = Stories.Web3,
isScanInProgress = false,
),
StoriesScreenContentV2Config(
storiesSize = 6,
currentStoryIndex = 5,
currentStory = Stories.WalletForEveryone,
isScanInProgress = false,
),
),
)
// endregion Preview

View file

@ -0,0 +1,124 @@
package com.tangem.tap.features.home.compose.views
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags
import com.tangem.wallet.R
@Composable
internal fun HomeButtonsV2(
btnScanStateInProgress: Boolean,
onScanButtonClick: () -> Unit,
onCreateNewWalletButtonClick: () -> Unit,
onAddExistingWalletButtonClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
CreateNewWalletButton(
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.STORIES_SCREEN_CREATE_NEW_WALLET_BUTTON),
onClick = onCreateNewWalletButtonClick,
)
AddExistingWalletButton(
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.STORIES_SCREEN_ADD_EXISTING_WALLET_BUTTON),
onClick = onAddExistingWalletButtonClick,
)
ScanCardButton(
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON),
showProgress = btnScanStateInProgress,
onClick = onScanButtonClick,
)
}
}
@Composable
private fun CreateNewWalletButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
StoriesButton(
modifier = modifier,
text = stringResourceSafe(id = R.string.home_button_create_new_wallet),
useDarkerColors = false,
onClick = onClick,
)
}
@Composable
private fun AddExistingWalletButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
StoriesButton(
modifier = modifier,
text = stringResourceSafe(id = R.string.home_button_add_existing_wallet),
useDarkerColors = true,
onClick = onClick,
)
}
@Composable
private fun ScanCardButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
StoriesButton(
modifier = modifier,
text = stringResourceSafe(id = R.string.home_button_scan),
useDarkerColors = true,
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
onClick = onClick,
showProgress = showProgress,
)
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun HomeButtonsV2Preview(@PreviewParameter(HomeButtonsV2ParameterProvider::class) state: HomeButtonsV2State) {
TangemThemePreview {
Box(
modifier = Modifier.background(Color.Black),
) {
HomeButtonsV2(
btnScanStateInProgress = state.btnScanStateInProgress,
onCreateNewWalletButtonClick = {},
onAddExistingWalletButtonClick = {},
onScanButtonClick = {},
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
)
}
}
}
private class HomeButtonsV2ParameterProvider : CollectionPreviewParameterProvider<HomeButtonsV2State>(
collection = listOf(
HomeButtonsV2State(
btnScanStateInProgress = false,
),
HomeButtonsV2State(
btnScanStateInProgress = true,
),
),
)
private data class HomeButtonsV2State(
val btnScanStateInProgress: Boolean,
)
// endregion Preview

View file

@ -7,6 +7,7 @@ import org.rekotlin.StateType
// todo refactor [REDACTED_TASK_KEY]
data class HomeState(
val scanInProgress: Boolean = false,
val isV2StoriesEnabled: Boolean = false,
val stories: ImmutableList<Stories> = getRestrictedStories().toImmutableList(),
) : StateType {