Updated on 2026-08-14
This commit is contained in:
commit
8553e2b722
156 changed files with 2318 additions and 742 deletions
|
|
@ -108,10 +108,10 @@ repositories {
|
|||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
|
||||
implementation(project(":domain"))
|
||||
implementation(project(":network"))
|
||||
implementation(project(":common"))
|
||||
implementation(project(":core:res"))
|
||||
implementation(project(":core:ui"))
|
||||
implementation(project(":core:datasource"))
|
||||
implementation(project(":core:utils"))
|
||||
implementation(project(":libs:crypto"))
|
||||
implementation(project(":libs:auth"))
|
||||
|
|
@ -120,6 +120,9 @@ dependencies {
|
|||
implementation(project(":features:referral:presentation"))
|
||||
implementation(project(":features:referral:domain"))
|
||||
implementation(project(":features:referral:data"))
|
||||
implementation(project(":features:swap:presentation"))
|
||||
implementation(project(":features:swap:domain"))
|
||||
implementation(project(":features:swap:data"))
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(AndroidX.coreKtx)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit bfc2bf8157089bce6b44779bdae66df2c920de70
|
||||
Subproject commit 897f0f7c402fd53c5ee1fa4ab601ecd4e43ff389
|
||||
|
|
@ -6,7 +6,11 @@ import androidx.lifecycle.lifecycleScope
|
|||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.operations.backup.BackupService
|
|||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tap.common.ActivityResultCallbackHolder
|
||||
import com.tangem.tap.common.DialogManager
|
||||
import com.tangem.tap.common.IntentHandler
|
||||
import com.tangem.tap.common.OnActivityResultCallback
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
|
|
@ -29,6 +28,7 @@ import com.tangem.tap.domain.TangemSdkManager
|
|||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
|
||||
import com.tangem.tap.features.shop.redux.ShopAction
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ActivityMainBinding
|
||||
|
|
@ -66,7 +66,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
|
||||
private var snackbar: Snackbar? = null
|
||||
private val dialogManager = DialogManager()
|
||||
private val intentHandler = IntentHandler()
|
||||
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
|
||||
|
||||
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
|
||||
|
|
@ -119,16 +118,23 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted
|
||||
val shopOpened = store.state.shopState.total != null
|
||||
if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore && !shopOpened)) {
|
||||
val navigateTo = if (userWalletsListManager.hasSavedUserWallets) AppScreen.Welcome else AppScreen.Home
|
||||
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(navigateTo))
|
||||
if (userWalletsListManager.hasSavedUserWallets) {
|
||||
store.dispatchOnMain(WelcomeAction.HandleDeepLink(intent))
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome))
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
intentHandler.handleWalletConnectLink(intent)
|
||||
}
|
||||
}
|
||||
intentHandler.handleIntent(intent)
|
||||
intentHandler.handleBackgroundScan(intent)
|
||||
intentHandler.handleSellCurrencyCallback(intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
intentHandler.handleIntent(intent)
|
||||
intentHandler.handleBackgroundScan(intent)
|
||||
intentHandler.handleSellCurrencyCallback(intent)
|
||||
intentHandler.handleWalletConnectLink(intent)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
|
|||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.domain.DomainLayer
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.AndroidAssetReader
|
||||
import com.tangem.tap.common.AssetReader
|
||||
import com.tangem.tap.common.IntentHandler
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
|
|
@ -109,6 +110,7 @@ val walletCurrenciesManager by lazy {
|
|||
val totalFiatBalanceCalculator by lazy {
|
||||
TotalFiatBalanceCalculator.provideDefaultImplementation()
|
||||
}
|
||||
val intentHandler by lazy { IntentHandler() }
|
||||
|
||||
@HiltAndroidApp
|
||||
class TapApplication : Application(), ImageLoaderFactory {
|
||||
|
|
@ -156,6 +158,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
appStateHolder.userTokensRepository = userTokensRepository
|
||||
}
|
||||
|
||||
//todo refactor: move to datasource and provide via DI
|
||||
private fun initMoshiConverter() {
|
||||
fun appAdapters(): List<Any> = listOf(
|
||||
BigDecimalAdapter(),
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ class IntentHandler {
|
|||
private val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
|
||||
private val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
|
||||
|
||||
fun handleIntent(intent: Intent?) {
|
||||
handleBackgroundScan(intent)
|
||||
handleWalletConnectLink(intent)
|
||||
handleSellCurrencyCallback(intent)
|
||||
fun handleWalletConnectLink(intent: Intent?) {
|
||||
if (intent?.scheme == WalletConnectManager.WC_SCHEME) {
|
||||
store.dispatch(WalletConnectAction.HandleDeepLink(intent.data?.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackgroundScan(intent: Intent?) {
|
||||
fun handleBackgroundScan(intent: Intent?) {
|
||||
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action ||
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action
|
||||
)
|
||||
|
|
@ -40,13 +40,7 @@ class IntentHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleWalletConnectLink(intent: Intent?) {
|
||||
if (intent?.scheme == WalletConnectManager.WC_SCHEME) {
|
||||
store.dispatch(WalletConnectAction.HandleDeepLink(intent.data?.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSellCurrencyCallback(intent: Intent?) {
|
||||
fun handleSellCurrencyCallback(intent: Intent?) {
|
||||
try {
|
||||
val transactionID =
|
||||
intent?.data?.getQueryParameter(TRANSACTION_ID_PARAM) ?: return
|
||||
|
|
|
|||
|
|
@ -54,12 +54,12 @@ fun FragmentActivity.openFragment(
|
|||
if (screen.isDialogFragment) {
|
||||
(fragment as DialogFragment).show(transaction, screen.name)
|
||||
if (addToBackstack) {
|
||||
transaction.addToBackStack(null)
|
||||
transaction.addToBackStack(screen.name)
|
||||
}
|
||||
} else {
|
||||
transaction.replace(R.id.fragment_container, fragment, screen.name)
|
||||
if (addToBackstack && (screen != AppScreen.Home && screen != AppScreen.Welcome)) {
|
||||
transaction.addToBackStack(null)
|
||||
if (addToBackstack) {
|
||||
transaction.addToBackStack(screen.name)
|
||||
}
|
||||
transaction.commitAllowingStateLoss()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ sealed class NavigationAction : Action {
|
|||
val addToBackstack: Boolean = true,
|
||||
) : NavigationAction()
|
||||
|
||||
data class PopBackTo(val screen: AppScreen? = null) : NavigationAction()
|
||||
data class PopBackTo(
|
||||
val screen: AppScreen? = null,
|
||||
val inclusive: Boolean = false,
|
||||
) : NavigationAction()
|
||||
|
||||
data class OpenUrl(val url: String) : NavigationAction()
|
||||
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@ val navigationMiddleware: Middleware<AppState> = { _, state ->
|
|||
AppScreen.Home,
|
||||
AppScreen.Welcome,
|
||||
-> {
|
||||
navState?.activity?.get()?.popBackTo(screen = null, inclusive = true)
|
||||
navState?.activity?.get()?.popBackTo(screen, action.inclusive)
|
||||
if (navState?.backStack?.contains(screen) != true) {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
navState?.activity?.get()?.popBackTo(screen = action.screen)
|
||||
navState?.activity?.get()?.popBackTo(screen, action.inclusive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.domain
|
|||
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.ThrottlerWithValues
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ import kotlin.coroutines.suspendCoroutine
|
|||
|
||||
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
|
||||
val canUseBiometry: Boolean
|
||||
get() = tangemSdk.biometricManager.canAuthenticate || canEnrollBiometrics
|
||||
get() = tangemSdk.biometricManager.canAuthenticate || needEnrollBiometrics
|
||||
|
||||
val canEnrollBiometrics: Boolean
|
||||
val needEnrollBiometrics: Boolean
|
||||
get() = tangemSdk.biometricManager.canEnrollBiometrics
|
||||
|
||||
val biometricManager: BiometricManager
|
||||
|
|
@ -61,10 +61,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
cardId: String? = null,
|
||||
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
messageRes: Int? = null,
|
||||
useBiometricsForAccessCode: Boolean = false,
|
||||
): CompletionResult<ScanResponse> {
|
||||
setAccessCodeRequestPolicy(useBiometricsForAccessCode)
|
||||
|
||||
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = ScanProductTask(
|
||||
|
|
@ -109,9 +106,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
suspend fun derivePublicKeys(
|
||||
cardId: String,
|
||||
derivations: Map<ByteArrayKey, List<DerivationPath>>,
|
||||
useBiometricsForAccessCode: Boolean = false,
|
||||
): CompletionResult<DerivationTaskResponse> {
|
||||
setAccessCodeRequestPolicy(useBiometricsForAccessCode)
|
||||
return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId)
|
||||
}
|
||||
|
||||
|
|
@ -192,9 +187,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
|
||||
suspend fun scanCard(
|
||||
cardId: String? = null,
|
||||
useBiometricsForAccessCode: Boolean = false,
|
||||
): CompletionResult<CardDTO> {
|
||||
setAccessCodeRequestPolicy(useBiometricsForAccessCode)
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = ScanTask(),
|
||||
cardId = cardId,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.domain
|
|||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.network.api.tangemTech.TangemTechError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechError
|
||||
import com.tangem.wallet.R
|
||||
|
||||
interface TapErrors
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class TapWalletManager {
|
|||
updateConfigManager(data)
|
||||
|
||||
withMainContext {
|
||||
store.dispatch(WalletAction.ResetState(data.card.cardId))
|
||||
store.dispatch(WalletAction.ResetState(data.card))
|
||||
store.dispatch(WalletConnectAction.ResetState)
|
||||
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
|
||||
store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.tap.domain.model.builders
|
||||
|
||||
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
|
|
@ -17,29 +19,27 @@ interface WalletStoreBuilder {
|
|||
fun walletManager(walletManager: WalletManager?): WalletStoreBuilder
|
||||
}
|
||||
|
||||
interface WalletMangerWalletStoreBuilder : WalletStoreBuilder {
|
||||
fun blockchainNetwork(blockchainNetwork: BlockchainNetwork?): WalletStoreBuilder
|
||||
}
|
||||
interface WalletMangerWalletStoreBuilder : WalletStoreBuilder
|
||||
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
userWalletId: UserWalletId,
|
||||
blockchainNetwork: BlockchainNetwork,
|
||||
): BlockchainNetworkWalletStoreBuilder {
|
||||
return BlockchainNetworkWalletStoreBuilderImpl(userWallet, blockchainNetwork)
|
||||
return BlockchainNetworkWalletStoreBuilderImpl(userWalletId, blockchainNetwork)
|
||||
}
|
||||
|
||||
operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
userWalletId: UserWalletId,
|
||||
walletManager: WalletManager,
|
||||
): WalletMangerWalletStoreBuilder {
|
||||
return WalletMangerWalletStoreBuilderImpl(userWallet, walletManager)
|
||||
return WalletMangerWalletStoreBuilderImpl(userWalletId, walletManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockchainNetworkWalletStoreBuilderImpl(
|
||||
private val userWallet: UserWallet,
|
||||
private val userWalletId: UserWalletId,
|
||||
private val blockchainNetwork: BlockchainNetwork,
|
||||
) : WalletStoreBuilder.BlockchainNetworkWalletStoreBuilder {
|
||||
private var walletManager: WalletManager? = null
|
||||
|
|
@ -53,7 +53,7 @@ private class BlockchainNetworkWalletStoreBuilderImpl(
|
|||
val tokensWalletsData = blockchainNetwork.getTokensWalletsData(walletManager)
|
||||
|
||||
return WalletStoreModel(
|
||||
userWalletId = userWallet.walletId,
|
||||
userWalletId = userWalletId,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
walletManager = walletManager,
|
||||
walletsData = (listOf(blockchainWalletData) + tokensWalletsData),
|
||||
|
|
@ -63,25 +63,20 @@ private class BlockchainNetworkWalletStoreBuilderImpl(
|
|||
}
|
||||
|
||||
private class WalletMangerWalletStoreBuilderImpl(
|
||||
private val userWallet: UserWallet,
|
||||
private val userWalletId: UserWalletId,
|
||||
private val walletManager: WalletManager,
|
||||
) : WalletStoreBuilder.WalletMangerWalletStoreBuilder {
|
||||
private var blockchainNetwork: BlockchainNetwork? = null
|
||||
|
||||
override fun blockchainNetwork(blockchainNetwork: BlockchainNetwork?) = this.apply {
|
||||
this.blockchainNetwork = blockchainNetwork
|
||||
}
|
||||
|
||||
override fun build(): WalletStoreModel {
|
||||
val blockchainNetwork = this.blockchainNetwork ?: BlockchainNetwork.fromWalletManager(walletManager)
|
||||
val blockchainWalletData = blockchainNetwork.getBlockchainWalletData(walletManager)
|
||||
val tokensWalletsData = blockchainNetwork.getTokensWalletsData(walletManager)
|
||||
val wallet = walletManager.wallet
|
||||
val blockchainWalletData = wallet.blockchain.toBlockchainWalletData(walletManager)
|
||||
val tokenWalletsData = wallet.getTokens().firstOrNull()?.toTokenWalletData(walletManager)
|
||||
|
||||
return WalletStoreModel(
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
userWalletId = userWalletId,
|
||||
blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager),
|
||||
walletManager = walletManager,
|
||||
walletsData = (listOf(blockchainWalletData) + tokensWalletsData),
|
||||
walletsData = (listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData)),
|
||||
walletRent = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -117,6 +112,35 @@ private fun BlockchainNetwork.getTokensWalletsData(walletManager: WalletManager?
|
|||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): WalletDataModel {
|
||||
val wallet = walletManager.wallet
|
||||
return WalletDataModel(
|
||||
currency = Currency.Blockchain(
|
||||
blockchain = this,
|
||||
derivationPath = wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
status = WalletDataModel.Loading,
|
||||
walletAddresses = wallet.createAddressesData(),
|
||||
existentialDeposit = getExistentialDeposit(walletManager),
|
||||
fiatRate = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Token.toTokenWalletData(walletManager: WalletManager): WalletDataModel {
|
||||
val wallet = walletManager.wallet
|
||||
return WalletDataModel(
|
||||
currency = Currency.Token(
|
||||
token = this,
|
||||
blockchain = wallet.blockchain,
|
||||
derivationPath = wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
status = WalletDataModel.Loading,
|
||||
walletAddresses = wallet.createAddressesData(),
|
||||
existentialDeposit = getExistentialDeposit(walletManager),
|
||||
fiatRate = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getExistentialDeposit(walletManager: WalletManager?): BigDecimal? {
|
||||
return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit()
|
||||
}
|
||||
|
|
@ -44,7 +44,6 @@ import kotlinx.coroutines.launch
|
|||
// TODO: Create repository for that
|
||||
object ScanCardProcessor {
|
||||
suspend fun scan(
|
||||
useBiometricsForAccessCode: Boolean = false,
|
||||
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
cardId: String? = null,
|
||||
onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {},
|
||||
|
|
@ -55,29 +54,37 @@ object ScanCardProcessor {
|
|||
) = withMainContext {
|
||||
onProgressStateChange(true)
|
||||
onScanStateChange(true)
|
||||
tangemSdkManager.scanProduct(
|
||||
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(null)
|
||||
|
||||
val result = tangemSdkManager.scanProduct(
|
||||
userTokensRepository = userTokensRepository,
|
||||
cardId = cardId,
|
||||
additionalBlockchainsToDerive = additionalBlockchainsToDerive,
|
||||
useBiometricsForAccessCode = useBiometricsForAccessCode,
|
||||
)
|
||||
|
||||
store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
|
||||
|
||||
result
|
||||
.doOnFailure { error ->
|
||||
onProgressStateChange(false)
|
||||
onScanStateChange(false)
|
||||
onFailure(error)
|
||||
}
|
||||
.doOnSuccess { scanResponse ->
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
|
||||
|
||||
onScanStateChange(false)
|
||||
checkForUnfinishedBackupForSaltPay(
|
||||
backupService = backupService,
|
||||
scanResponse = scanResponse,
|
||||
onProgressStateChange = { onProgressStateChange(it) },
|
||||
nextHandler = {
|
||||
nextHandler = { scanResponse1 ->
|
||||
showDisclaimerIfNeed(
|
||||
scanResponse = scanResponse,
|
||||
nextHandler = {
|
||||
scanResponse = scanResponse1,
|
||||
nextHandler = { scanResponse2 ->
|
||||
onScanSuccess(
|
||||
scanResponse = scanResponse,
|
||||
scanResponse = scanResponse2,
|
||||
onProgressStateChange = onProgressStateChange,
|
||||
onSuccess = onSuccess,
|
||||
onWalletNotCreated = onWalletNotCreated,
|
||||
|
|
@ -87,7 +94,6 @@ object ScanCardProcessor {
|
|||
)
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.domain.tokens
|
|||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
import com.tangem.datasource.api.tangemTech.CoinsResponse
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrencyFromJson(
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.getListOfCoins
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.datasource.api.tangemTech.CoinsResponse
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.tap.common.AssetReader
|
||||
|
||||
class LoadAvailableCoinsService(
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import com.tangem.blockchain.common.DerivationStyle
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.getTokens
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.tap.common.FileReader
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.TokenDao
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package com.tangem.tap.domain.tokens
|
|||
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.network.api.tangemTech.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.datasource.api.tangemTech.UserTokensResponse
|
||||
import com.tangem.tap.domain.NoDataError
|
||||
|
||||
class UserTokensNetworkService(private val tangemTechService: TangemTechService) {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ package com.tangem.tap.domain.tokens
|
|||
import android.content.Context
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.datasource.api.tangemTech.UserTokensResponse
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.util.userWalletId
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.network.api.tangemTech.UserTokensResponse
|
||||
import com.tangem.tap.common.AndroidFileReader
|
||||
import com.tangem.tap.domain.NoDataError
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ package com.tangem.tap.domain.tokens
|
|||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.Log
|
||||
import com.tangem.datasource.api.tangemTech.UserTokensResponse
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.network.api.tangemTech.UserTokensResponse
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.FileReader
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||
|
|
|
|||
|
|
@ -5,10 +5,30 @@ import com.tangem.tap.domain.model.WalletStoreModel
|
|||
import java.math.BigDecimal
|
||||
|
||||
interface TotalFiatBalanceCalculator {
|
||||
|
||||
/**
|
||||
* Calculate total fiat balance for list of [WalletStoreModel]
|
||||
* @param prevAmount Previous amount, used in [TotalFiatBalance.Refreshing]
|
||||
* @param walletStores List of [WalletStoreModel] to calculate fiat amount
|
||||
* @param initial Initial [TotalFiatBalance] state, used when list of [WalletStoreModel] is empty
|
||||
* @return [TotalFiatBalance] with state found with the [WalletStoreModel] list
|
||||
* */
|
||||
suspend fun calculate(
|
||||
prevAmount: BigDecimal,
|
||||
prevAmount: BigDecimal?,
|
||||
walletStores: List<WalletStoreModel>,
|
||||
initial: TotalFiatBalance,
|
||||
): TotalFiatBalance
|
||||
|
||||
/**
|
||||
* Same as [TotalFiatBalanceCalculator.calculate] but returns null if list of [WalletStoreModel] is empty
|
||||
* @param prevAmount Previous amount, used in [TotalFiatBalance.Refreshing]
|
||||
* @param walletStores List of [WalletStoreModel] to calculate fiat amount
|
||||
* @return [TotalFiatBalance] with state found with the [WalletStoreModel] list
|
||||
* */
|
||||
suspend fun calculateOrNull(
|
||||
prevAmount: BigDecimal?,
|
||||
walletStores: List<WalletStoreModel>,
|
||||
): TotalFiatBalance?
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -11,11 +11,19 @@ import java.math.BigDecimal
|
|||
|
||||
internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
|
||||
override suspend fun calculate(
|
||||
prevAmount: BigDecimal,
|
||||
prevAmount: BigDecimal?,
|
||||
walletStores: List<WalletStoreModel>,
|
||||
initial: TotalFiatBalance,
|
||||
): TotalFiatBalance {
|
||||
return calculateOrNull(prevAmount, walletStores) ?: initial
|
||||
}
|
||||
|
||||
override suspend fun calculateOrNull(
|
||||
prevAmount: BigDecimal?,
|
||||
walletStores: List<WalletStoreModel>,
|
||||
): TotalFiatBalance? {
|
||||
return if (walletStores.isEmpty()) {
|
||||
TotalFiatBalance.Loading
|
||||
null
|
||||
} else {
|
||||
withContext(Dispatchers.Default) {
|
||||
val walletsData = walletStores
|
||||
|
|
@ -25,7 +33,9 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
|
|||
|
||||
when (walletsData.findStatus()) {
|
||||
TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading
|
||||
TotalFiatBalanceStatus.Refreshing -> TotalFiatBalance.Refreshing(prevAmount)
|
||||
TotalFiatBalanceStatus.Refreshing -> TotalFiatBalance.Refreshing(
|
||||
amount = prevAmount ?: BigDecimal.ZERO,
|
||||
)
|
||||
TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount())
|
||||
TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(calculateAmount())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import com.tangem.common.extensions.hexToBytes
|
|||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.tap.common.AssetReader
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
|
|
|||
|
|
@ -19,8 +19,14 @@ interface UserWalletsListManager {
|
|||
|
||||
suspend fun selectWallet(walletId: UserWalletId): CompletionResult<UserWallet>
|
||||
|
||||
suspend fun save(userWallet: UserWallet): CompletionResult<Unit>
|
||||
suspend fun update(userWallet: UserWallet): CompletionResult<Unit>
|
||||
/**
|
||||
* Save user's wallet
|
||||
* @param userWallet User's wallet to save
|
||||
* @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an
|
||||
* already saved card
|
||||
* @return [CompletionResult] operation result
|
||||
* */
|
||||
suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult<Unit>
|
||||
|
||||
suspend fun delete(walletIds: List<UserWalletId>): CompletionResult<Unit>
|
||||
suspend fun clear(): CompletionResult<Unit>
|
||||
|
|
|
|||
|
|
@ -110,12 +110,36 @@ internal class BiometricUserWalletsListManager(
|
|||
findSelectedWallet()!!
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
return saveInternal(userWallet, override = false)
|
||||
}
|
||||
override suspend fun save(
|
||||
userWallet: UserWallet,
|
||||
canOverride: Boolean,
|
||||
): CompletionResult<Unit> = withUnlock {
|
||||
val isWalletSaved = state.value.wallets
|
||||
.filter { it.isSaved }
|
||||
.flatMap(UserWallet::cardsInWallet)
|
||||
.contains(userWallet.cardId)
|
||||
|
||||
override suspend fun update(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
return saveInternal(userWallet, override = true)
|
||||
if (isWalletSaved && !canOverride) {
|
||||
CompletionResult.Failure(UserWalletListError.WalletAlreadySaved)
|
||||
} else {
|
||||
keysRepository.save(
|
||||
walletId = userWallet.walletId,
|
||||
encryptionKey = userWallet.scanResponse.card.encryptionKey,
|
||||
)
|
||||
.doOnSuccess { keys ->
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
encryptionKeys = (keys + prevState.encryptionKeys).distinctBy { it.walletId },
|
||||
)
|
||||
}
|
||||
}
|
||||
.flatMap { publicInformationRepository.save(userWallet) }
|
||||
.flatMap { sensitiveInformationRepository.save(userWallet) }
|
||||
.flatMap { loadModels() }
|
||||
.doOnSuccess {
|
||||
userWallet.isSaved = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(walletIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
|
|
@ -162,38 +186,6 @@ internal class BiometricUserWalletsListManager(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun saveInternal(
|
||||
userWallet: UserWallet,
|
||||
override: Boolean,
|
||||
): CompletionResult<Unit> = withUnlock {
|
||||
val isWalletSaved = state.value.wallets
|
||||
.filter { it.isSaved }
|
||||
.flatMap(UserWallet::cardsInWallet)
|
||||
.contains(userWallet.cardId)
|
||||
|
||||
if (isWalletSaved && !override) {
|
||||
CompletionResult.Failure(UserWalletListError.WalletAlreadySaved)
|
||||
} else {
|
||||
keysRepository.save(
|
||||
walletId = userWallet.walletId,
|
||||
encryptionKey = userWallet.scanResponse.card.encryptionKey,
|
||||
)
|
||||
.doOnSuccess { keys ->
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
encryptionKeys = (keys + prevState.encryptionKeys).distinctBy { it.walletId },
|
||||
)
|
||||
}
|
||||
}
|
||||
.flatMap { publicInformationRepository.save(userWallet) }
|
||||
.flatMap { sensitiveInformationRepository.save(userWallet) }
|
||||
.flatMap { loadModels() }
|
||||
.doOnSuccess {
|
||||
userWallet.isSaved = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun <reified T> withUnlock(
|
||||
block: () -> CompletionResult<T>,
|
||||
): CompletionResult<T> {
|
||||
|
|
|
|||
|
|
@ -40,11 +40,7 @@ class DummyUserWalletsListManager : UserWalletsListManager {
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun update(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -187,8 +187,8 @@ internal class DefaultWalletCurrenciesManager(
|
|||
.flatMap { walletManager ->
|
||||
walletStoresRepository.storeOrUpdate(
|
||||
userWalletId = userWalletId,
|
||||
walletStore = WalletStoreBuilder(userWallet, walletManager)
|
||||
.blockchainNetwork(blockchainNetwork)
|
||||
walletStore = WalletStoreBuilder(userWalletId, blockchainNetwork)
|
||||
.walletManager(walletManager)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ internal class DefaultWalletStoresManager(
|
|||
{ walletManager ->
|
||||
walletStoresRepository.storeOrUpdate(
|
||||
userWalletId = userWalletId,
|
||||
walletStore = WalletStoreBuilder(userWallet, blockchainNetwork)
|
||||
walletStore = WalletStoreBuilder(userWalletId, blockchainNetwork)
|
||||
.walletManager(walletManager)
|
||||
.build(),
|
||||
)
|
||||
|
|
@ -158,9 +158,10 @@ internal class DefaultWalletStoresManager(
|
|||
refresh = refresh,
|
||||
)
|
||||
.flatMap { walletManager ->
|
||||
val userWalletId = userWallet.walletId
|
||||
walletStoresRepository.storeOrUpdate(
|
||||
userWalletId = userWallet.walletId,
|
||||
walletStore = WalletStoreBuilder(userWallet, walletManager)
|
||||
userWalletId = userWalletId,
|
||||
walletStore = WalletStoreBuilder(userWalletId, walletManager)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.domain.walletStores.repository.di
|
||||
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ import com.tangem.common.flatMap
|
|||
import com.tangem.common.flatMapOnFailure
|
||||
import com.tangem.common.map
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.replaceByOrAdd
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.squareup.moshi.JsonClass
|
|||
import com.squareup.moshi.Types
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
|
||||
import com.trustwallet.walletconnect.models.WCPeerMeta
|
||||
|
|
@ -18,7 +18,7 @@ import java.nio.charset.Charset
|
|||
class WalletConnectRepository(val context: Application) {
|
||||
private val moshi = MoshiConverter.defaultMoshi()
|
||||
private val walletConnectAdapter: JsonAdapter<List<SessionDao>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, SessionDao::class.java)
|
||||
Types.newParameterizedType(List::class.java, SessionDao::class.java),
|
||||
)
|
||||
|
||||
fun saveSession(session: WalletConnectSession) {
|
||||
|
|
@ -76,7 +76,6 @@ class WalletConnectRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SessionDao(
|
||||
val peerId: String,
|
||||
|
|
@ -91,7 +90,7 @@ data class SessionDao(
|
|||
remotePeerId = remotePeerId,
|
||||
wallet = wallet,
|
||||
session = session,
|
||||
peerMeta = peerMeta
|
||||
peerMeta = peerMeta,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +101,7 @@ data class SessionDao(
|
|||
remotePeerId = session.remotePeerId,
|
||||
wallet = session.wallet,
|
||||
session = session.session,
|
||||
peerMeta = session.peerMeta
|
||||
peerMeta = session.peerMeta,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,10 +55,14 @@ sealed class DetailsAction : Action {
|
|||
) : AppSettings()
|
||||
}
|
||||
|
||||
object EnrollBiometrics : AppSettings() {
|
||||
object Enroll : AppSettings()
|
||||
object Cancel : AppSettings()
|
||||
}
|
||||
data class CheckBiometricsStatus(
|
||||
val awaitStatusChange: Boolean,
|
||||
) : AppSettings()
|
||||
|
||||
object EnrollBiometrics : AppSettings()
|
||||
data class BiometricsStatusChanged(
|
||||
val needEnrollBiometrics: Boolean,
|
||||
) : AppSettings()
|
||||
}
|
||||
|
||||
data class ChangeAppCurrency(val fiatCurrency: FiatCurrency) : DetailsAction()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.tangem.tap.userWalletsListManager
|
|||
import com.tangem.tap.walletStoresManager
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -77,11 +78,7 @@ class DetailsMiddleware {
|
|||
}
|
||||
DetailsAction.ScanCard -> {
|
||||
scope.launch {
|
||||
tangemSdkManager.scanCard(
|
||||
cardId = state.scanResponse?.card?.cardId,
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
|
||||
state.scanResponse?.card?.isAccessCodeSet == true,
|
||||
)
|
||||
tangemSdkManager.scanCard(cardId = state.scanResponse?.card?.cardId)
|
||||
.doOnSuccess { card ->
|
||||
val currentCardId = store.state.globalState.scanResponse?.card
|
||||
?.userWalletId
|
||||
|
|
@ -121,12 +118,15 @@ class DetailsMiddleware {
|
|||
.flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) }
|
||||
.doOnSuccess {
|
||||
Analytics.send(Settings.CardSettings.FactoryResetFinished())
|
||||
val screen = if (userWalletsListManager.hasSavedUserWallets) {
|
||||
AppScreen.Welcome
|
||||
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
|
||||
if (selectedUserWallet != null) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.onUserWalletSelected(selectedUserWallet)
|
||||
} else {
|
||||
AppScreen.Home
|
||||
userWalletsListManager.lock()
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(screen))
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
(error as? TangemSdkError)?.let { sdkError ->
|
||||
|
|
@ -195,18 +195,39 @@ class DetailsMiddleware {
|
|||
fun handle(state: DetailsState, action: DetailsAction.AppSettings) {
|
||||
when (action) {
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting -> {
|
||||
if (tangemSdkManager.canEnrollBiometrics) {
|
||||
store.dispatch(DetailsAction.AppSettings.EnrollBiometrics)
|
||||
}
|
||||
when (action.setting) {
|
||||
PrivacySetting.SaveWallets -> toggleSaveWallets(state, enable = action.enable)
|
||||
PrivacySetting.SaveAccessCode -> toggleSaveAccessCodes(state, enable = action.enable)
|
||||
}
|
||||
}
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> Unit
|
||||
is DetailsAction.AppSettings.EnrollBiometrics -> Unit
|
||||
is DetailsAction.AppSettings.EnrollBiometrics.Enroll -> enrollBiometrics()
|
||||
is DetailsAction.AppSettings.EnrollBiometrics.Cancel -> Unit
|
||||
is DetailsAction.AppSettings.CheckBiometricsStatus -> {
|
||||
checkBiometricsStatus(action.awaitStatusChange, state)
|
||||
}
|
||||
is DetailsAction.AppSettings.EnrollBiometrics -> {
|
||||
enrollBiometrics()
|
||||
}
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting.Success,
|
||||
is DetailsAction.AppSettings.BiometricsStatusChanged,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param awaitStatusChange If true then start a new coroutine and check the biometric status every 100
|
||||
* milliseconds until it changes
|
||||
* */
|
||||
private fun checkBiometricsStatus(awaitStatusChange: Boolean, state: DetailsState) {
|
||||
scope.launch {
|
||||
if (awaitStatusChange) {
|
||||
while (state.needEnrollBiometrics == tangemSdkManager.needEnrollBiometrics) {
|
||||
delay(timeMillis = 100)
|
||||
}
|
||||
}
|
||||
store.dispatchOnMain(
|
||||
DetailsAction.AppSettings.BiometricsStatusChanged(
|
||||
needEnrollBiometrics = tangemSdkManager.needEnrollBiometrics,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +238,7 @@ class DetailsMiddleware {
|
|||
private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch {
|
||||
if (state.saveWallets == enable) return@launch
|
||||
if (enable) {
|
||||
saveCurrentWallet()
|
||||
saveCurrentWallet(state)
|
||||
} else {
|
||||
deleteSavedWallets()
|
||||
if (state.saveAccessCodes) {
|
||||
|
|
@ -230,16 +251,16 @@ class DetailsMiddleware {
|
|||
if (state.saveAccessCodes == enable) return@launch
|
||||
if (enable) {
|
||||
if (!state.saveWallets) {
|
||||
saveCurrentWallet()
|
||||
saveCurrentWallet(state)
|
||||
}
|
||||
saveAccessCodes()
|
||||
saveAccessCodes(state)
|
||||
} else {
|
||||
deleteSavedAccessCodes()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveCurrentWallet() {
|
||||
val scanResponse = store.state.detailsState.scanResponse ?: return
|
||||
private suspend fun saveCurrentWallet(state: DetailsState) {
|
||||
val scanResponse = state.scanResponse ?: return
|
||||
val userWallet = UserWalletBuilder(scanResponse).build()
|
||||
|
||||
userWalletsListManager.save(userWallet)
|
||||
|
|
@ -247,7 +268,7 @@ class DetailsMiddleware {
|
|||
Timber.e(error, "Wallet saving failed")
|
||||
}
|
||||
.doOnSuccess {
|
||||
preferencesStorage.shouldShowSaveWallet = false
|
||||
preferencesStorage.shouldShowSaveUserWalletScreen = false
|
||||
preferencesStorage.shouldSaveUserWallets = true
|
||||
store.dispatchOnMain(
|
||||
DetailsAction.AppSettings.SwitchPrivacySetting.Success(
|
||||
|
|
@ -274,8 +295,11 @@ class DetailsMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun saveAccessCodes() {
|
||||
private fun saveAccessCodes(state: DetailsState) {
|
||||
preferencesStorage.shouldSaveAccessCodes = true
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = state.scanResponse?.card?.isAccessCodeSet == true,
|
||||
)
|
||||
store.dispatchOnMain(
|
||||
DetailsAction.AppSettings.SwitchPrivacySetting.Success(
|
||||
setting = PrivacySetting.SaveAccessCode,
|
||||
|
|
@ -288,6 +312,9 @@ class DetailsMiddleware {
|
|||
tangemSdkManager.clearSavedUserCodes()
|
||||
.doOnSuccess {
|
||||
preferencesStorage.shouldSaveAccessCodes = false
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = false,
|
||||
)
|
||||
store.dispatchOnMain(
|
||||
DetailsAction.AppSettings.SwitchPrivacySetting.Success(
|
||||
setting = PrivacySetting.SaveAccessCode,
|
||||
|
|
|
|||
|
|
@ -157,15 +157,17 @@ private fun handlePrivacyAction(
|
|||
state: DetailsState,
|
||||
): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting -> state
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> when (action.setting) {
|
||||
PrivacySetting.SaveWallets -> state.copy(saveWallets = action.enable)
|
||||
PrivacySetting.SaveAccessCode -> state.copy(saveAccessCodes = action.enable)
|
||||
}
|
||||
is DetailsAction.AppSettings.EnrollBiometrics -> state.copy(needEnrollBiometrics = true)
|
||||
is DetailsAction.AppSettings.EnrollBiometrics.Enroll,
|
||||
is DetailsAction.AppSettings.EnrollBiometrics.Cancel,
|
||||
-> state.copy(needEnrollBiometrics = false)
|
||||
is DetailsAction.AppSettings.BiometricsStatusChanged -> state.copy(
|
||||
needEnrollBiometrics = action.needEnrollBiometrics,
|
||||
)
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting,
|
||||
is DetailsAction.AppSettings.EnrollBiometrics,
|
||||
is DetailsAction.AppSettings.CheckBiometricsStatus,
|
||||
-> state
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
class AppSettingsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
||||
|
|
@ -24,8 +25,9 @@ class AppSettingsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(android.R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(android.R.transition.fade)
|
||||
enterTransition = inflater.inflateTransition(R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
viewModel.checkBiometricsStatus()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
@ -36,7 +38,7 @@ class AppSettingsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
|||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
isTransitionGroup = true
|
||||
AppCompatTheme {
|
||||
TangemTheme {
|
||||
AppSettingsScreen(
|
||||
state = screenState.value,
|
||||
onBackPressed = {
|
||||
|
|
@ -58,6 +60,11 @@ class AppSettingsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.refreshBiometricsStatus()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
|
|
|
|||
|
|
@ -1,24 +1,31 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.components.dialogs.EnrollBiometricsDialogContent
|
||||
import com.tangem.core.ui.models.EnrollBiometricsDialog
|
||||
import com.tangem.tap.common.compose.TangemTypography
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerH32
|
||||
import com.tangem.core.ui.components.SpacerH4
|
||||
import com.tangem.core.ui.components.SpacerW32
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.details.redux.PrivacySetting
|
||||
import com.tangem.tap.features.details.ui.appsettings.components.EnrollBiometricsCard
|
||||
import com.tangem.tap.features.details.ui.appsettings.components.SettingsAlertDialog
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
import com.tangem.tap.features.details.ui.common.TangemSwitch
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -51,47 +58,43 @@ private fun AppSettings(
|
|||
element = it,
|
||||
onDialogStateChange = onDialogStateChange,
|
||||
onSettingToggled = state.onSettingToggled,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
EnrollBiometricsDialog(dialog = state.enrollBiometricsDialog)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
if (state.showEnrollBiometricsCard) {
|
||||
EnrollBiometricsCard(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing8)
|
||||
.fillMaxWidth(),
|
||||
onClick = state.onEnrollBiometrics,
|
||||
)
|
||||
SpacerH24()
|
||||
}
|
||||
|
||||
AppSettingsElement(
|
||||
state = state,
|
||||
setting = PrivacySetting.SaveWallets,
|
||||
onDialogStateChange = onDialogStateChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
Spacer(modifier = Modifier.size(32.dp))
|
||||
SpacerH32()
|
||||
AppSettingsElement(
|
||||
state = state,
|
||||
setting = PrivacySetting.SaveAccessCode,
|
||||
onDialogStateChange = onDialogStateChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EnrollBiometricsDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
dialog: EnrollBiometricsDialog?,
|
||||
) {
|
||||
if (dialog == null) return
|
||||
EnrollBiometricsDialogContent(modifier, dialog)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppSettingsElement(
|
||||
modifier: Modifier = Modifier,
|
||||
state: AppSettingsScreenState,
|
||||
setting: PrivacySetting,
|
||||
onDialogStateChange: (PrivacySetting?) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val titleRes = when (setting) {
|
||||
PrivacySetting.SaveWallets -> R.string.app_settings_saved_wallet
|
||||
|
|
@ -103,48 +106,63 @@ private fun AppSettingsElement(
|
|||
}
|
||||
val checked = state.settings[setting] ?: false
|
||||
|
||||
val titleTextColor by rememberUpdatedState(
|
||||
newValue = if (state.isTogglesEnabled) {
|
||||
TangemTheme.colors.text.primary1
|
||||
} else {
|
||||
TangemTheme.colors.text.secondary
|
||||
},
|
||||
)
|
||||
val descriptionTextColor by rememberUpdatedState(
|
||||
newValue = if (state.isTogglesEnabled) {
|
||||
TangemTheme.colors.text.secondary
|
||||
} else {
|
||||
TangemTheme.colors.text.tertiary
|
||||
},
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 20.dp),
|
||||
.padding(horizontal = TangemTheme.dimens.spacing20),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(weight = .9f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = modifier
|
||||
.weight(0.6f)
|
||||
.padding(end = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = titleRes),
|
||||
style = TangemTypography.subtitle1,
|
||||
color = colorResource(id = R.color.text_primary_1),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = titleTextColor,
|
||||
)
|
||||
Spacer(modifier = Modifier.size(4.dp))
|
||||
SpacerH4()
|
||||
Text(
|
||||
text = stringResource(id = subtitleRes),
|
||||
style = TangemTypography.body2,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = descriptionTextColor,
|
||||
)
|
||||
}
|
||||
SpacerW32()
|
||||
TangemSwitch(
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
enabled = state.isTogglesEnabled,
|
||||
onCheckedChange = { isChecked ->
|
||||
onCheckedChange(
|
||||
element = setting,
|
||||
enabled = it,
|
||||
enabled = isChecked,
|
||||
onSettingToggled = state.onSettingToggled,
|
||||
onDialogStateChange = onDialogStateChange,
|
||||
)
|
||||
},
|
||||
modifier = modifier
|
||||
.padding(20.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCheckedChange(
|
||||
element: PrivacySetting, enabled: Boolean,
|
||||
element: PrivacySetting,
|
||||
enabled: Boolean,
|
||||
onSettingToggled: (PrivacySetting, Boolean) -> Unit,
|
||||
onDialogStateChange: (PrivacySetting?) -> Unit,
|
||||
) {
|
||||
|
|
@ -152,82 +170,88 @@ private fun onCheckedChange(
|
|||
if (!enabled) {
|
||||
onDialogStateChange(element)
|
||||
} else {
|
||||
onSettingToggled(element, enabled)
|
||||
onSettingToggled(element, true)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun SettingsAlertDialog(
|
||||
element: PrivacySetting,
|
||||
onDialogStateChange: (PrivacySetting?) -> Unit,
|
||||
onSettingToggled: (PrivacySetting, Boolean) -> Unit,
|
||||
private fun AppSettingsScreenSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val text = when (element) {
|
||||
PrivacySetting.SaveWallets -> R.string.app_settings_off_saved_wallet_alert_message
|
||||
PrivacySetting.SaveAccessCode -> R.string.app_settings_off_saved_access_code_alert_message
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
AppSettingsScreen(
|
||||
state = AppSettingsScreenState(
|
||||
settings = mapOf(
|
||||
PrivacySetting.SaveWallets to true,
|
||||
PrivacySetting.SaveAccessCode to false,
|
||||
),
|
||||
showEnrollBiometricsCard = false,
|
||||
isTogglesEnabled = true,
|
||||
onSettingToggled = { _, _ -> },
|
||||
onEnrollBiometrics = {},
|
||||
),
|
||||
onBackPressed = { },
|
||||
)
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = { onDialogStateChange(null) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDialogStateChange(null)
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_cancel),
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDialogStateChange(null)
|
||||
onSettingToggled(element, false)
|
||||
},
|
||||
modifier = modifier.padding(bottom = 14.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_delete),
|
||||
color = colorResource(id = R.color.text_warning),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_attention),
|
||||
color = colorResource(id = R.color.text_primary_1),
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(id = text),
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
)
|
||||
},
|
||||
shape = MaterialTheme.shapes.medium.copy(all = CornerSize(size = 28.dp)),
|
||||
modifier = modifier.padding(vertical = 34.dp, horizontal = 24.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
fun AppSettingsScreenPreview() {
|
||||
AppSettingsScreen(
|
||||
state = AppSettingsScreenState(
|
||||
settings = mapOf(
|
||||
PrivacySetting.SaveWallets to true,
|
||||
PrivacySetting.SaveAccessCode to false,
|
||||
private fun AppSettingsScreenPreview_Light() {
|
||||
TangemTheme {
|
||||
AppSettingsScreenSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun AppSettingsScreenPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
AppSettingsScreenSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppSettingsScreen_EnrollBiometrics_Sample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
AppSettingsScreen(
|
||||
state = AppSettingsScreenState(
|
||||
settings = mapOf(
|
||||
PrivacySetting.SaveWallets to true,
|
||||
PrivacySetting.SaveAccessCode to false,
|
||||
),
|
||||
showEnrollBiometricsCard = true,
|
||||
isTogglesEnabled = false,
|
||||
onSettingToggled = { _, _ -> },
|
||||
onEnrollBiometrics = {},
|
||||
),
|
||||
onSettingToggled = { _, _ -> },
|
||||
enrollBiometricsDialog = null,
|
||||
),
|
||||
onBackPressed = { },
|
||||
)
|
||||
}
|
||||
onBackPressed = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun AppSettingsScreen_EnrollBiometrics_Preview_Light() {
|
||||
TangemTheme {
|
||||
AppSettingsScreen_EnrollBiometrics_Sample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun AppSettingsScreen_EnrollBiometrics_Preview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
AppSettingsScreen_EnrollBiometrics_Sample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings
|
||||
|
||||
import com.tangem.core.ui.models.EnrollBiometricsDialog
|
||||
import com.tangem.tap.features.details.redux.PrivacySetting
|
||||
|
||||
data class AppSettingsScreenState(
|
||||
val settings: Map<PrivacySetting, Boolean>,
|
||||
val enrollBiometricsDialog: EnrollBiometricsDialog?,
|
||||
val onSettingToggled: (PrivacySetting, Boolean) -> Unit,
|
||||
val settings: Map<PrivacySetting, Boolean> = emptyMap(),
|
||||
val showEnrollBiometricsCard: Boolean = false,
|
||||
val isTogglesEnabled: Boolean = true,
|
||||
val onSettingToggled: (PrivacySetting, Boolean) -> Unit = { _, _ -> /* no-op */ },
|
||||
val onEnrollBiometrics: () -> Unit = { /* no-op */ },
|
||||
)
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings
|
||||
|
||||
import com.tangem.core.ui.models.EnrollBiometricsDialog
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
|
|
@ -20,8 +19,14 @@ class AppSettingsViewModel(private val store: Store<AppState>) {
|
|||
PrivacySetting.SaveWallets to state.saveWallets,
|
||||
PrivacySetting.SaveAccessCode to state.saveAccessCodes,
|
||||
),
|
||||
enrollBiometricsDialog = if (state.needEnrollBiometrics) createEnrollBiometricsDialog() else null,
|
||||
onSettingToggled = { privacySetting, enabled -> onSettingsToggled(privacySetting, enabled) },
|
||||
showEnrollBiometricsCard = state.needEnrollBiometrics,
|
||||
isTogglesEnabled = !state.needEnrollBiometrics,
|
||||
onSettingToggled = { privacySetting, enabled ->
|
||||
onSettingsToggled(privacySetting, enabled)
|
||||
},
|
||||
onEnrollBiometrics = {
|
||||
store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -29,12 +34,11 @@ class AppSettingsViewModel(private val store: Store<AppState>) {
|
|||
store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting))
|
||||
}
|
||||
|
||||
private fun createEnrollBiometricsDialog() = EnrollBiometricsDialog(
|
||||
onCancel = {
|
||||
store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics.Cancel)
|
||||
},
|
||||
onEnroll = {
|
||||
store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics.Enroll)
|
||||
},
|
||||
)
|
||||
fun checkBiometricsStatus() {
|
||||
store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(awaitStatusChange = false))
|
||||
}
|
||||
|
||||
fun refreshBiometricsStatus() {
|
||||
store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(awaitStatusChange = true))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SpacerH4
|
||||
import com.tangem.core.ui.components.SpacerW16
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
internal fun EnrollBiometricsCard(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
color = TangemTheme.colors.background.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersLarge,
|
||||
onClick = onClick,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(all = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_alert_circle_24),
|
||||
tint = TangemTheme.colors.icon.attention,
|
||||
contentDescription = null,
|
||||
)
|
||||
SpacerW16()
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(id = R.string.app_settings_enable_biometrics_title),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
SpacerH4()
|
||||
Text(
|
||||
text = stringResource(id = R.string.app_settings_enable_biometrics_description),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun EnrollBiometricsCardSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
) {
|
||||
EnrollBiometricsCard(onClick = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun EnrollBiometricsCardPreview_Light() {
|
||||
TangemTheme {
|
||||
EnrollBiometricsCardSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun EnrollBiometricsCardPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
EnrollBiometricsCardSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.TextButton
|
||||
import com.tangem.core.ui.components.WarningTextButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.details.redux.PrivacySetting
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun SettingsAlertDialog(
|
||||
element: PrivacySetting,
|
||||
onDialogStateChange: (PrivacySetting?) -> Unit,
|
||||
onSettingToggled: (PrivacySetting, Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val text = when (element) {
|
||||
PrivacySetting.SaveWallets -> R.string.app_settings_off_saved_wallet_alert_message
|
||||
PrivacySetting.SaveAccessCode -> R.string.app_settings_off_saved_access_code_alert_message
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { onDialogStateChange(null) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
||||
text = stringResource(id = R.string.common_cancel),
|
||||
onClick = {
|
||||
onDialogStateChange(null)
|
||||
},
|
||||
)
|
||||
},
|
||||
dismissButton = {
|
||||
WarningTextButton(
|
||||
text = stringResource(id = R.string.common_delete),
|
||||
onClick = {
|
||||
onDialogStateChange(null)
|
||||
onSettingToggled(element, false)
|
||||
},
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_attention),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.h2,
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(id = text),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
},
|
||||
shape = TangemTheme.shapes.roundedCornersLarge,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun SettingsAlertDialogSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
SettingsAlertDialog(
|
||||
element = PrivacySetting.SaveAccessCode,
|
||||
onDialogStateChange = {},
|
||||
onSettingToggled = { _, _ -> },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun SettingsAlertDialogPreview_Light() {
|
||||
TangemTheme {
|
||||
SettingsAlertDialogSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun SettingsAlertDialogPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
SettingsAlertDialogSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -9,7 +9,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
|
|
@ -39,7 +39,7 @@ class CardSettingsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
|||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
isTransitionGroup = true
|
||||
AppCompatTheme {
|
||||
TangemTheme {
|
||||
CardSettingsScreen(
|
||||
state = screenState.value,
|
||||
onBackPressed = {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import androidx.compose.foundation.rememberScrollState
|
|||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
|
|
@ -23,6 +25,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.compose.TangemTypography
|
||||
import com.tangem.tap.features.details.ui.common.DetailsMainButton
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
|
|
@ -34,14 +37,22 @@ fun CardSettingsScreen(
|
|||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val needReadCard = state.cardDetails == null
|
||||
val backgroundColor by rememberUpdatedState(
|
||||
newValue = if (needReadCard) TangemTheme.colors.background.primary
|
||||
else TangemTheme.colors.background.secondary,
|
||||
)
|
||||
|
||||
SettingsScreensScaffold(
|
||||
content =
|
||||
if (state.cardDetails == null) {
|
||||
{ CardSettingsReadCard(state.onScanCardClick, modifier = modifier) }
|
||||
} else {
|
||||
{ CardSettings(state = state, modifier = modifier) }
|
||||
content = {
|
||||
if (needReadCard) {
|
||||
CardSettingsReadCard(state.onScanCardClick, modifier = modifier)
|
||||
} else {
|
||||
CardSettings(state = state, modifier = modifier)
|
||||
}
|
||||
},
|
||||
titleRes = R.string.card_settings_title,
|
||||
backgroundColor = backgroundColor,
|
||||
onBackClick = onBackPressed,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,18 +25,19 @@ import androidx.compose.ui.res.colorResource
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.compose.TangemTypography
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
fun SettingsScreensScaffold(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
background: @Composable (() -> Unit)? = null,
|
||||
fab: @Composable (() -> Unit)? = null,
|
||||
backgroundColor: Color = colorResource(id = R.color.background_primary),
|
||||
backgroundColor: Color = TangemTheme.colors.background.secondary,
|
||||
titleRes: Int? = null,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BackHandler(true, onBackClick)
|
||||
|
||||
|
|
@ -58,9 +59,13 @@ fun SettingsScreensScaffold(
|
|||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = stringResource(id = titleRes),
|
||||
modifier = modifier.padding(start = 20.dp, end = 20.dp, bottom = 52.dp),
|
||||
style = TangemTypography.headline1,
|
||||
color = colorResource(id = R.color.text_primary_1),
|
||||
modifier = modifier.padding(
|
||||
start = TangemTheme.dimens.spacing20,
|
||||
end = TangemTheme.dimens.spacing20,
|
||||
bottom = TangemTheme.dimens.spacing54,
|
||||
),
|
||||
style = TangemTheme.typography.h1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
content()
|
||||
}
|
||||
|
|
@ -86,18 +91,20 @@ fun ScreenTitle(
|
|||
|
||||
@Composable
|
||||
fun EmptyTopBarWithNavigation(
|
||||
onBackClick: () -> Unit,
|
||||
backgroundColor: Color = colorResource(id = R.color.background_primary),
|
||||
modifier: Modifier = Modifier,
|
||||
onBackClick: () -> Unit,
|
||||
backgroundColor: Color = TangemTheme.colors.background.primary,
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
title = { },
|
||||
navigationIcon =
|
||||
{
|
||||
IconButton(onClick = onBackClick) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_back), "",
|
||||
tint = colorResource(id = R.color.icon_primary_1),
|
||||
painter = painterResource(id = R.drawable.ic_back),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -108,10 +115,10 @@ fun EmptyTopBarWithNavigation(
|
|||
|
||||
@Composable
|
||||
fun DetailsMainButton(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
enabled: Boolean = true,
|
||||
onClick: (() -> Unit),
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
|
|
|
|||
|
|
@ -33,10 +33,11 @@ import com.tangem.wallet.R
|
|||
@Composable
|
||||
fun TangemSwitch(
|
||||
modifier: Modifier = Modifier,
|
||||
enabledColor: Color = colorResource(id = R.color.control_checked),
|
||||
disabledColor: Color = colorResource(id = R.color.icon_informative),
|
||||
checkedColor: Color = colorResource(id = R.color.control_checked),
|
||||
uncheckedColor: Color = colorResource(id = R.color.icon_informative),
|
||||
size: Dp = 48.dp,
|
||||
checked: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
) {
|
||||
val transition = updateTransition(checked, label = "SwitchState")
|
||||
|
|
@ -45,8 +46,8 @@ fun TangemSwitch(
|
|||
tween(durationMillis = 200, easing = FastOutLinearInEasing)
|
||||
},
|
||||
label = "",
|
||||
) { enabled ->
|
||||
if (enabled) enabledColor else disabledColor
|
||||
) { isChecked ->
|
||||
if (isChecked) checkedColor else uncheckedColor
|
||||
}
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
|
|
@ -55,6 +56,7 @@ fun TangemSwitch(
|
|||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
enabled = enabled,
|
||||
) {
|
||||
onCheckedChange(!checked)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
|
|
@ -40,7 +40,7 @@ class DetailsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
|||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
isTransitionGroup = true
|
||||
AppCompatTheme {
|
||||
TangemTheme {
|
||||
DetailsScreen(
|
||||
state = detailsScreenState.value,
|
||||
onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ enum class SettingsElement(
|
|||
LinkMoreCards(R.drawable.ic_more_cards, R.string.details_row_title_create_backup),
|
||||
TermsOfService(R.drawable.ic_text, R.string.disclaimer_title), // General Terms of Service of the App
|
||||
TermsOfUse(R.drawable.ic_text, R.string.details_row_title_card_tou), // Terms of Use for S2C cards only
|
||||
PrivacyPolicy(R.drawable.ic_lock, R.string.details_row_privacy_policy);
|
||||
PrivacyPolicy(R.drawable.ic_lock_24, R.string.details_row_privacy_policy);
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -38,7 +38,7 @@ class ResetCardFragment : Fragment(), StoreSubscriber<DetailsState> {
|
|||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
isTransitionGroup = true
|
||||
AppCompatTheme {
|
||||
TangemTheme {
|
||||
ResetCardScreen(
|
||||
state = screenState.value,
|
||||
onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -38,7 +38,7 @@ class SecurityModeFragment : Fragment(), StoreSubscriber<DetailsState> {
|
|||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
isTransitionGroup = true
|
||||
AppCompatTheme {
|
||||
TangemTheme {
|
||||
SecurityModeScreen(
|
||||
state = screenState.value,
|
||||
onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
|
|
@ -36,7 +36,7 @@ class WalletConnectFragment : Fragment(), StoreSubscriber<WalletConnectState> {
|
|||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
isTransitionGroup = true
|
||||
AppCompatTheme {
|
||||
TangemTheme {
|
||||
WalletConnectScreen(
|
||||
state = screenState.value,
|
||||
onBackPressed = {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -42,6 +43,10 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
|
|||
|
||||
return ComposeView(inflater.context).apply {
|
||||
setContent {
|
||||
BackHandler {
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
AppCompatTheme {
|
||||
ScreenContent()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.tap.features.send.redux.states.ButtonState
|
|||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -71,8 +72,10 @@ private fun handleHomeAction(action: Action) {
|
|||
|
||||
private fun readCard() = scope.launch {
|
||||
delay(timeMillis = 200)
|
||||
ScanCardProcessor.scan(
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
|
||||
)
|
||||
ScanCardProcessor.scan(
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (showProgress) {
|
||||
changeButtonState(ButtonState.PROGRESS)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import com.tangem.tap.preferencesStorage
|
|||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -56,8 +55,7 @@ class OnboardingHelper {
|
|||
backupCardsIds: List<String>? = null,
|
||||
) {
|
||||
when {
|
||||
userWalletsListManager.hasSavedUserWallets -> scope.launch {
|
||||
delay(timeMillis = 1_200)
|
||||
preferencesStorage.shouldSaveUserWallets -> scope.launch {
|
||||
store.dispatchOnMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
|
|
@ -68,7 +66,7 @@ class OnboardingHelper {
|
|||
store.dispatchOnMain(SaveWalletAction.Save)
|
||||
}
|
||||
tangemSdkManager.canUseBiometry &&
|
||||
preferencesStorage.shouldShowSaveWallet -> scope.launch {
|
||||
preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch {
|
||||
delay(timeMillis = 1_200)
|
||||
store.dispatchOnMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ import com.tangem.common.extensions.isZero
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.successOr
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.network.api.paymentology.AttestationResponse
|
||||
import com.tangem.network.api.paymentology.PaymentologyApiService
|
||||
import com.tangem.network.api.paymentology.RegisterKYCRequest
|
||||
import com.tangem.network.api.paymentology.RegisterWalletRequest
|
||||
import com.tangem.network.api.paymentology.RegisterWalletResponse
|
||||
import com.tangem.network.api.paymentology.RegistrationResponse
|
||||
import com.tangem.network.api.paymentology.tryExtractError
|
||||
import com.tangem.datasource.api.paymentology.AttestationResponse
|
||||
import com.tangem.datasource.api.paymentology.PaymentologyApiService
|
||||
import com.tangem.datasource.api.paymentology.RegisterKYCRequest
|
||||
import com.tangem.datasource.api.paymentology.RegisterWalletRequest
|
||||
import com.tangem.datasource.api.paymentology.RegisterWalletResponse
|
||||
import com.tangem.datasource.api.paymentology.RegistrationResponse
|
||||
import com.tangem.datasource.api.paymentology.tryExtractError
|
||||
import com.tangem.operations.attestation.AttestWalletKeyResponse
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
|
|
@ -152,12 +152,13 @@ class SaltPayActivationManager(
|
|||
)
|
||||
}
|
||||
|
||||
private fun makeRegisterKYCRequest(): RegisterKYCRequest = RegisterKYCRequest(
|
||||
cardId = cardId,
|
||||
publicKey = cardPublicKey,
|
||||
kycProvider = "UTORG",
|
||||
kycRefId = kycUrlProvider.kycRefId,
|
||||
)
|
||||
private fun makeRegisterKYCRequest(): RegisterKYCRequest =
|
||||
RegisterKYCRequest(
|
||||
cardId = cardId,
|
||||
publicKey = cardPublicKey,
|
||||
kycProvider = "UTORG",
|
||||
kycRefId = kycUrlProvider.kycRefId,
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun stub(): SaltPayActivationManager = SaltPayActivationManager(
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.successOr
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.network.api.paymentology.KYCStatus
|
||||
import com.tangem.network.api.paymentology.RegistrationResponse
|
||||
import com.tangem.datasource.api.paymentology.KYCStatus
|
||||
import com.tangem.datasource.api.paymentology.RegistrationResponse
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.blockchain.common.WalletManagerFactory
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.network.api.paymentology.PaymentologyApiService
|
||||
import com.tangem.datasource.api.paymentology.PaymentologyApiService
|
||||
import com.tangem.tap.common.toggleWidget.WidgetState
|
||||
import com.tangem.tap.domain.extensions.makeSaltPayWalletManager
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.GnosisRegistrator
|
||||
|
|
|
|||
|
|
@ -52,13 +52,23 @@ internal class SaveWalletMiddleware {
|
|||
}
|
||||
|
||||
private fun saveWalletIfBiometricsEnrolled(state: SaveWalletState) {
|
||||
if (tangemSdkManager.canEnrollBiometrics) {
|
||||
if (tangemSdkManager.needEnrollBiometrics) {
|
||||
store.dispatchOnMain(SaveWalletAction.EnrollBiometrics)
|
||||
} else {
|
||||
saveWallet(state)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* or from [SaveWalletState.backupInfo] if provided from
|
||||
* [com.tangem.tap.features.onboarding.OnboardingHelper.trySaveWalletAndNavigateToWalletScreen]
|
||||
*
|
||||
* If saved user's wallet was selected then pop back to [AppScreen.Wallet]
|
||||
* or navigate to [AppScreen.WalletSelector] otherwise
|
||||
*
|
||||
* TODO: Update that logic after onboarding and backup features refactoring
|
||||
* */
|
||||
private fun saveWallet(state: SaveWalletState) {
|
||||
val scanResponse = state.backupInfo?.scanResponse
|
||||
?: store.state.globalState.scanResponse
|
||||
|
|
@ -69,32 +79,37 @@ internal class SaveWalletMiddleware {
|
|||
.backupCardsIds(state.backupInfo?.backupCardsIds)
|
||||
.build()
|
||||
|
||||
val isFirstSavedWallet = !userWalletsListManager.hasSavedUserWallets
|
||||
|
||||
saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet)
|
||||
.flatMap { userWalletsListManager.save(userWallet) }
|
||||
.flatMap { userWalletsListManager.save(userWallet, canOverride = true) }
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(SaveWalletAction.Save.Error(error))
|
||||
}
|
||||
.doOnSuccess {
|
||||
preferencesStorage.shouldSaveUserWallets = true
|
||||
preferencesStorage.shouldSaveAccessCodes = true
|
||||
|
||||
// Enable saving access codes only if this is the first time user save the wallet
|
||||
preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet ||
|
||||
preferencesStorage.shouldSaveAccessCodes
|
||||
|
||||
val isSavedWalletSelected =
|
||||
userWalletsListManager.selectedUserWalletSync?.walletId == userWallet.walletId
|
||||
|
||||
store.dispatchOnMain(SaveWalletAction.Save.Success)
|
||||
|
||||
if (isSavedWalletSelected) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.onUserWalletSelected(userWallet)
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector))
|
||||
}
|
||||
|
||||
store.dispatchOnMain(SaveWalletAction.Save.Success)
|
||||
store.onUserWalletSelected(userWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveWalletWasShown() {
|
||||
preferencesStorage.shouldShowSaveWallet = false
|
||||
preferencesStorage.shouldShowSaveUserWalletScreen = false
|
||||
}
|
||||
|
||||
private suspend fun saveAccessCodeIfNeeded(
|
||||
|
|
|
|||
|
|
@ -6,19 +6,25 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.ui.components.dialogs.EnrollBiometricsDialogContent
|
||||
import com.tangem.core.ui.fragments.ComposeBottomSheetFragment
|
||||
import com.tangem.core.ui.models.EnrollBiometricsDialog
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.tap.features.saveWallet.ui.components.EnrollBiometricsDialogContent
|
||||
import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent
|
||||
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
|
||||
|
||||
internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment<SaveWalletScreenState>() {
|
||||
override val expandedHeightFraction: Float = .95f
|
||||
override val expandedHeightFraction: Float = .98f
|
||||
|
||||
private val viewModel by viewModels<SaveWalletViewModel>()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tap.features.saveWallet.ui
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.models.EnrollBiometricsDialog
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
|
||||
|
||||
@Immutable
|
||||
internal data class SaveWalletScreenState(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.tap.features.saveWallet.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.core.ui.models.EnrollBiometricsDialog
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletState
|
||||
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.core.ui.components.dialogs
|
||||
package com.tangem.tap.features.saveWallet.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -17,8 +17,8 @@ import com.tangem.core.ui.components.SpacerH16
|
|||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.components.TextButton
|
||||
import com.tangem.core.ui.models.EnrollBiometricsDialog
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
|
||||
|
||||
@Composable
|
||||
fun EnrollBiometricsDialogContent(
|
||||
|
|
@ -1,27 +1,32 @@
|
|||
package com.tangem.tap.features.saveWallet.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerH32
|
||||
import com.tangem.core.ui.components.SpacerH4
|
||||
import com.tangem.core.ui.components.SpacerHHalf
|
||||
import com.tangem.core.ui.components.SpacerW24
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.components.atoms.Hand
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -34,79 +39,132 @@ internal fun SaveWalletScreenContent(
|
|||
onCloseClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Hand()
|
||||
IconButton(
|
||||
Header(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onCloseClick = onCloseClick,
|
||||
)
|
||||
SpacerHHalf()
|
||||
Title(
|
||||
modifier = Modifier
|
||||
.align(Alignment.End)
|
||||
.padding(all = TangemTheme.dimens.spacing8)
|
||||
.size(TangemTheme.dimens.size32),
|
||||
onClick = onCloseClick,
|
||||
.widthIn(max = TangemTheme.dimens.size200),
|
||||
)
|
||||
SpacerH32()
|
||||
Description(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing34,
|
||||
end = TangemTheme.dimens.spacing56,
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
SpacerHHalf()
|
||||
Footer(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
showProgress = showProgress,
|
||||
onSaveWalletClick = onSaveWalletClick,
|
||||
)
|
||||
SpacerH16()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Header(
|
||||
modifier: Modifier = Modifier,
|
||||
onCloseClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
Hand()
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_close),
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
contentDescription = stringResource(id = R.string.common_cancel),
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = onCloseClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_close),
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
contentDescription = stringResource(id = R.string.common_cancel),
|
||||
)
|
||||
}
|
||||
SpacerW8()
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(.7f))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Title(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing32),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size56),
|
||||
painter = painterResource(id = R.drawable.ic_fingerprint_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
)
|
||||
SpacerH24()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.text.accent.copy(alpha = .12f),
|
||||
shape = TangemTheme.shapes.roundedCornersMedium,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing4,
|
||||
horizontal = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
text = stringResource(R.string.save_user_wallet_agreement_new_feature),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
SpacerH24()
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.onboarding_navbar_save_wallet),
|
||||
text = stringResource(id = R.string.save_user_wallet_agreement_header_biometrics),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
SpacerH12()
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(fraction = .87f),
|
||||
text = stringResource(R.string.save_user_wallet_agreement_description),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Description(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
DescriptionItem(
|
||||
iconPainter = painterResource(id = R.drawable.ic_face_recognition_24),
|
||||
title = stringResource(id = R.string.save_user_wallet_agreement_access_title),
|
||||
description = stringResource(id = R.string.save_user_wallet_agreement_access_description),
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
DescriptionItem(
|
||||
iconPainter = painterResource(id = R.drawable.ic_lock_24),
|
||||
title = stringResource(id = R.string.save_user_wallet_agreement_code_title),
|
||||
description = stringResource(id = R.string.save_user_wallet_agreement_code_description_biometrics),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Footer(
|
||||
modifier: Modifier = Modifier,
|
||||
showProgress: Boolean,
|
||||
onSaveWalletClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
showProgress = showProgress,
|
||||
text = stringResource(
|
||||
id = R.string.save_user_wallet_agreement_allow,
|
||||
stringResource(id = R.string.common_biometric_authentication),
|
||||
),
|
||||
text = stringResource(id = R.string.save_user_wallet_agreement_allow_biometrics),
|
||||
onClick = onSaveWalletClick,
|
||||
)
|
||||
SpacerH16()
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(fraction = .7f),
|
||||
text = stringResource(R.string.save_user_wallet_agreement_notice),
|
||||
|
|
@ -114,7 +172,41 @@ internal fun SaveWalletScreenContent(
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
SpacerH16()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DescriptionItem(
|
||||
modifier: Modifier = Modifier,
|
||||
iconPainter: Painter,
|
||||
title: String,
|
||||
description: String,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = iconPainter,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
)
|
||||
SpacerW24()
|
||||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
SpacerH4()
|
||||
Text(
|
||||
text = description,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.core.ui.models
|
||||
package com.tangem.tap.features.saveWallet.ui.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
|
|
@ -35,7 +35,6 @@ import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
|
|||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
|
@ -206,7 +205,6 @@ class TokensMiddleware {
|
|||
val result = tangemSdkManager.derivePublicKeys(
|
||||
cardId = card.cardId,
|
||||
derivations = derivations,
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && card.isAccessCodeSet,
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
@ -288,7 +286,7 @@ class TokensMiddleware {
|
|||
)
|
||||
|
||||
scope.launch {
|
||||
userWalletsListManager.update(updatedUserWallet)
|
||||
userWalletsListManager.save(updatedUserWallet, canOverride = true)
|
||||
.flatMap {
|
||||
walletCurrenciesManager.addCurrencies(
|
||||
userWallet = updatedUserWallet,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.tangem.domain.common.extensions.fromNetworkId
|
|||
import com.tangem.domain.common.extensions.toCoinId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.network.api.tangemTech.TokenResponse
|
||||
import com.tangem.datasource.api.tangemTech.TokenResponse
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
|
|
@ -20,7 +24,7 @@ import java.math.BigDecimal
|
|||
|
||||
sealed class WalletAction : Action {
|
||||
|
||||
data class ResetState(val newCardId: String) : WalletAction()
|
||||
data class ResetState(val newCard: CardDTO) : WalletAction()
|
||||
|
||||
data class SetIfTestnetCard(val isTestnet: Boolean) : WalletAction()
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@ import com.tangem.tap.common.toggleWidget.WidgetState
|
|||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.wallet.models.*
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.TotalBalance
|
||||
import com.tangem.tap.features.wallet.models.WalletRent
|
||||
import com.tangem.tap.features.wallet.models.WalletWarning
|
||||
import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount
|
||||
import com.tangem.tap.features.wallet.redux.reducers.findProgressState
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
|
|
@ -43,6 +47,7 @@ data class WalletState(
|
|||
val showBackupWarning: Boolean = false,
|
||||
val missingDerivations: List<BlockchainNetwork> = emptyList(),
|
||||
val loadingUserTokens: Boolean = false,
|
||||
val walletCardsCount: Int? = null,
|
||||
) : StateType {
|
||||
|
||||
// if you do not delegate - the application crashes on startup,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.datasource.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import com.tangem.tap.features.wallet.redux.WalletAction
|
|||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.features.wallet.redux.reducers.toWallet
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userTokensRepository
|
||||
|
|
@ -183,8 +182,6 @@ class MultiWalletMiddleware {
|
|||
state: WalletState?,
|
||||
) = scope.launch {
|
||||
ScanCardProcessor.scan(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
|
||||
selectedWallet.scanResponse.card.isAccessCodeSet,
|
||||
cardId = selectedWallet.cardId,
|
||||
additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain },
|
||||
) { scanResponse ->
|
||||
|
|
@ -192,7 +189,7 @@ class MultiWalletMiddleware {
|
|||
scanResponse = scanResponse,
|
||||
)
|
||||
|
||||
userWalletsListManager.update(userWallet)
|
||||
userWalletsListManager.save(userWallet, canOverride = true)
|
||||
.doOnSuccess {
|
||||
store.state.globalState.tapWalletManager.loadData(userWallet, refresh = true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -294,28 +294,29 @@ class WalletMiddleware {
|
|||
}
|
||||
is WalletAction.UserWalletChanged -> Unit
|
||||
is WalletAction.WalletStoresChanged -> {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
fetchTotalFiatBalance(action.walletStores, walletState)
|
||||
findMissedDerivations(action.walletStores)
|
||||
tryToShowAppRatingWarning(action.walletStores)
|
||||
}
|
||||
fetchTotalFiatBalance(action.walletStores, walletState)
|
||||
findMissedDerivations(action.walletStores)
|
||||
tryToShowAppRatingWarning(action.walletStores)
|
||||
}
|
||||
is WalletAction.TotalFiatBalanceChanged -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchTotalFiatBalance(walletStores: List<WalletStoreModel>, state: WalletState) {
|
||||
scope.launch {
|
||||
val totalFiatBalance = totalFiatBalanceCalculator.calculate(
|
||||
prevAmount = state.totalBalance?.fiatAmount ?: BigDecimal.ZERO,
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull(
|
||||
prevAmount = state.totalBalance?.fiatAmount,
|
||||
walletStores = walletStores,
|
||||
)
|
||||
store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance))
|
||||
|
||||
if (totalFiatBalance != null) {
|
||||
store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findMissedDerivations(wallStores: List<WalletStoreModel>) {
|
||||
scope.launch {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val missedDerivations = wallStores
|
||||
.filter { store ->
|
||||
store.walletsData.any { it.status is WalletDataModel.MissedDerivation }
|
||||
|
|
@ -327,15 +328,17 @@ class WalletMiddleware {
|
|||
}
|
||||
|
||||
private fun tryToShowAppRatingWarning(walletStores: List<WalletStoreModel>) {
|
||||
warningsMiddleware.tryToShowAppRatingWarning(
|
||||
hasNonZeroWallets = walletStores
|
||||
.flatMap { it.walletsData }
|
||||
.any { it.status.amount.isGreaterThan(BigDecimal.ZERO) },
|
||||
)
|
||||
scope.launch(Dispatchers.Default) {
|
||||
warningsMiddleware.tryToShowAppRatingWarning(
|
||||
hasNonZeroWallets = walletStores
|
||||
.flatMap { it.walletsData }
|
||||
.any { it.status.amount.isGreaterThan(BigDecimal.ZERO) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSaveWalletIfNeeded() {
|
||||
if (preferencesStorage.shouldShowSaveWallet
|
||||
if (preferencesStorage.shouldShowSaveUserWalletScreen
|
||||
&& tangemSdkManager.canUseBiometry
|
||||
&& store.state.navigationState.backStack.lastOrNull() == AppScreen.Wallet
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,12 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState)
|
||||
is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState)
|
||||
|
||||
is WalletAction.ResetState -> newState = WalletState(cardId = action.newCardId)
|
||||
is WalletAction.ResetState -> {
|
||||
newState = WalletState(
|
||||
cardId = action.newCard.cardId,
|
||||
walletCardsCount = action.newCard.findCardsCount(),
|
||||
)
|
||||
}
|
||||
is WalletAction.SetIfTestnetCard -> newState = newState.copy(isTestnet = action.isTestnet)
|
||||
is WalletAction.EmptyWallet -> {
|
||||
newState = newState.copy(
|
||||
|
|
@ -333,6 +338,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
showBackupWarning = card.isMultiwalletAllowed &&
|
||||
card.settings.isBackupAllowed &&
|
||||
card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
walletCardsCount = card.findCardsCount(),
|
||||
)
|
||||
}
|
||||
is WalletAction.WalletStoresChanged -> {
|
||||
|
|
@ -366,6 +372,11 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
return newState
|
||||
}
|
||||
|
||||
private fun CardDTO.findCardsCount(): Int? {
|
||||
return (this.backupStatus as? CardDTO.BackupStatus.Active)?.cardCount?.inc()
|
||||
?.takeIf { this.isMultiwalletAllowed }
|
||||
}
|
||||
|
||||
@JvmName("walletStoreModelToReduxModel")
|
||||
private fun List<WalletStoreModel>.mapToReduxModel(
|
||||
isMultiWalletAllowed: Boolean,
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@ package com.tangem.tap.features.wallet.ui.wallet
|
|||
import android.widget.Button
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.analytics.events.Portfolio
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.extensions.animateVisibility
|
||||
import com.tangem.tap.common.extensions.formatAmountAsSpannedString
|
||||
import com.tangem.tap.common.extensions.getQuantityString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
|
|
@ -43,21 +46,11 @@ class MultiWalletView : WalletView() {
|
|||
lAddress.root.hide()
|
||||
rowButtons.hide()
|
||||
lSingleWalletBalance.root.hide()
|
||||
lCardTotalBalance.root.show()
|
||||
rvMultiwallet.show()
|
||||
btnAddToken.show()
|
||||
setupWalletCardNumber(binding)
|
||||
}
|
||||
|
||||
private fun setupWalletCardNumber(binding: FragmentWalletBinding) = with(binding) {
|
||||
val card = store.state.globalState.scanResponse?.card
|
||||
if (card?.backupStatus is CardDTO.BackupStatus.Active) {
|
||||
val cardCount = (card.backupStatus as CardDTO.BackupStatus.Active).cardCount + 1
|
||||
tvTwinCardNumber.show()
|
||||
tvTwinCardNumber.text = tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, cardCount)
|
||||
} else {
|
||||
tvTwinCardNumber.hide()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated() {
|
||||
setupWalletsRecyclerView()
|
||||
|
|
@ -79,6 +72,7 @@ class MultiWalletView : WalletView() {
|
|||
handleTotalBalance(binding, state.totalBalance, state.state)
|
||||
handleBackupWarning(binding, state.showBackupWarning)
|
||||
handleRescanWarning(binding, state.missingDerivations.isNotEmpty())
|
||||
setupWalletCardNumber(binding, state.walletCardsCount)
|
||||
walletsAdapter.submitList(state.walletsData)
|
||||
|
||||
binding.pbLoadingUserTokens.show(state.loadingUserTokens)
|
||||
|
|
@ -107,6 +101,16 @@ class MultiWalletView : WalletView() {
|
|||
handleErrorStates(state = state, binding = binding, fragment = fragment)
|
||||
}
|
||||
|
||||
private fun setupWalletCardNumber(binding: FragmentWalletBinding, walletCardsCount: Int?) = with(binding) {
|
||||
if (walletCardsCount != null) {
|
||||
tvTwinCardNumber.show()
|
||||
tvTwinCardNumber.text =
|
||||
tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, walletCardsCount)
|
||||
} else {
|
||||
tvTwinCardNumber.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackupWarning(
|
||||
binding: FragmentWalletBinding,
|
||||
showBackupWarning: Boolean,
|
||||
|
|
@ -134,21 +138,20 @@ class MultiWalletView : WalletView() {
|
|||
totalBalance: TotalBalance?,
|
||||
progressState: ProgressState,
|
||||
) = with(binding.lCardTotalBalance) {
|
||||
root.isVisible = totalBalance != null
|
||||
if (totalBalance != null) {
|
||||
// Skip changes when on refreshing state
|
||||
if (totalBalance.state == ProgressState.Refreshing ||
|
||||
progressState == ProgressState.Refreshing
|
||||
) return@with
|
||||
if (totalBalance == null) {
|
||||
veilBalance.animateVisibility(show = true)
|
||||
root.isVisible = progressState == ProgressState.Loading
|
||||
} else {
|
||||
root.isVisible = true
|
||||
|
||||
if (totalBalance.state == ProgressState.Loading) {
|
||||
veilBalance.veil()
|
||||
} else {
|
||||
veilBalance.unVeil()
|
||||
// Skip changes when on refreshing state
|
||||
if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) {
|
||||
return@with
|
||||
}
|
||||
tvProcessing.animateVisibility(
|
||||
show = totalBalance.state == ProgressState.Error,
|
||||
)
|
||||
|
||||
veilBalance.animateVisibility(show = totalBalance.state == ProgressState.Loading)
|
||||
tvBalance.animateVisibility(show = totalBalance.state != ProgressState.Loading)
|
||||
tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error)
|
||||
|
||||
tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString(
|
||||
currencySymbol = totalBalance.fiatCurrency.symbol,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ class SingleWalletView : WalletView() {
|
|||
}
|
||||
|
||||
private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
|
||||
tvTwinCardNumber.hide()
|
||||
rvMultiwallet.hide()
|
||||
btnAddToken.hide()
|
||||
rvPendingTransaction.hide()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.tap.common.extensions.onUserWalletSelected
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.model.builders.UserWalletBuilder
|
||||
|
|
@ -19,6 +20,7 @@ import com.tangem.tap.domain.scanCard.ScanCardProcessor
|
|||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.totalFiatBalanceCalculator
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletStoresManager
|
||||
|
|
@ -26,6 +28,7 @@ import kotlinx.coroutines.flow.first
|
|||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class WalletSelectorMiddleware {
|
||||
val middleware: Middleware<AppState> = { _, appStateProvider ->
|
||||
|
|
@ -127,7 +130,8 @@ internal class WalletSelectorMiddleware {
|
|||
store.dispatchOnMain(WalletSelectorAction.AddWallet.Success)
|
||||
|
||||
if (isSavedWalletSelected) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
updateAccessCodeRequestPolicy(selectedWallet)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.onUserWalletSelected(selectedWallet)
|
||||
}
|
||||
}
|
||||
|
|
@ -141,7 +145,8 @@ internal class WalletSelectorMiddleware {
|
|||
store.dispatchOnMain(WalletSelectorAction.HandleError(error))
|
||||
}
|
||||
.doOnSuccess { selectedWallet ->
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
updateAccessCodeRequestPolicy(selectedWallet)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.onUserWalletSelected(selectedWallet)
|
||||
}
|
||||
}
|
||||
|
|
@ -163,7 +168,7 @@ internal class WalletSelectorMiddleware {
|
|||
scope.launch {
|
||||
userWalletsListManager.get(walletId = UserWalletId(walletId))
|
||||
.map { it.copy(name = newName) }
|
||||
.flatMap { userWalletsListManager.update(it) }
|
||||
.flatMap { userWalletsListManager.save(it, canOverride = true) }
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.HandleError(error))
|
||||
}
|
||||
|
|
@ -174,7 +179,6 @@ internal class WalletSelectorMiddleware {
|
|||
crossinline onCardScanned: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
ScanCardProcessor.scan(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
|
||||
onSuccess = {
|
||||
onCardScanned(it)
|
||||
},
|
||||
|
|
@ -208,11 +212,19 @@ internal class WalletSelectorMiddleware {
|
|||
val isSelectedWalletRemoved = prevSelectedWalletId != selectedWallet.walletId.stringValue
|
||||
|
||||
if (isSelectedWalletRemoved) {
|
||||
updateAccessCodeRequestPolicy(selectedWallet)
|
||||
store.onUserWalletSelected(selectedWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAccessCodeRequestPolicy(userWallet: UserWallet) {
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
|
||||
userWallet.scanResponse.card.isAccessCodeSet,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance(
|
||||
walletStores: List<WalletStoreModel>,
|
||||
): UserWalletModel {
|
||||
|
|
@ -221,7 +233,6 @@ internal class WalletSelectorMiddleware {
|
|||
is UserWalletModel.Type.MultiCurrency -> type.copy(
|
||||
tokensCount = walletStores.flatMap { it.walletsData }.size,
|
||||
)
|
||||
|
||||
is UserWalletModel.Type.SingleCurrency -> type.copy(
|
||||
blockchainName = walletStores
|
||||
.firstOrNull()
|
||||
|
|
@ -233,6 +244,7 @@ internal class WalletSelectorMiddleware {
|
|||
fiatBalance = totalFiatBalanceCalculator.calculate(
|
||||
prevAmount = fiatBalance.amount,
|
||||
walletStores = walletStores,
|
||||
initial = TotalFiatBalance.Loaded(BigDecimal.ZERO),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,23 @@
|
|||
package com.tangem.tap.features.walletSelector.ui.components
|
||||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
|
|
@ -15,7 +27,11 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconRight
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.atoms.Hand
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
|
|
@ -35,31 +51,16 @@ internal fun WalletSelectorScreenContent(
|
|||
onEditSelectedWalletClick: () -> Unit,
|
||||
onDeleteSelectedWalletsClick: () -> Unit,
|
||||
) {
|
||||
val editingWalletsSize by rememberUpdatedState(newValue = state.editingWalletsIds.size)
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Hand()
|
||||
SpacerH12()
|
||||
if (editingWalletsSize > 0) {
|
||||
EditWalletsBar(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
editingWalletsSize = editingWalletsSize,
|
||||
onClearSelectedClick = onClearSelectedClick,
|
||||
onEditSelectedWalletClick = onEditSelectedWalletClick,
|
||||
onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.user_wallet_list_title),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
SpacerH12()
|
||||
Header(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
editingWalletsIds = state.editingWalletsIds,
|
||||
onClearSelectedClick = onClearSelectedClick,
|
||||
onEditSelectedWalletClick = onEditSelectedWalletClick,
|
||||
onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(
|
||||
|
|
@ -76,28 +77,15 @@ internal fun WalletSelectorScreenContent(
|
|||
onWalletLongClick = onWalletLongClick,
|
||||
)
|
||||
SpacerH24()
|
||||
if (state.isLocked) {
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
text = stringResource(
|
||||
id = R.string.user_wallet_list_unlock_all,
|
||||
stringResource(id = R.string.common_biometrics),
|
||||
),
|
||||
showProgress = state.showUnlockProgress,
|
||||
onClick = onUnlockClick,
|
||||
)
|
||||
SpacerH12()
|
||||
}
|
||||
SecondaryButtonIconRight(
|
||||
Footer(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
text = stringResource(R.string.user_wallet_list_add_button),
|
||||
showProgress = state.showAddCardProgress,
|
||||
icon = painterResource(id = R.drawable.ic_tangem),
|
||||
onClick = onAddCardClick,
|
||||
isLocked = state.isLocked,
|
||||
showUnlockProgress = state.showUnlockProgress,
|
||||
showAddCardProgress = state.showAddCardProgress,
|
||||
onUnlockClick = onUnlockClick,
|
||||
onAddCardClick = onAddCardClick,
|
||||
)
|
||||
SpacerH16()
|
||||
}
|
||||
|
|
@ -105,62 +93,42 @@ internal fun WalletSelectorScreenContent(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun EditWalletsBar(
|
||||
private fun Header(
|
||||
modifier: Modifier = Modifier,
|
||||
editingWalletsSize: Int,
|
||||
editingWalletsIds: List<String>,
|
||||
onClearSelectedClick: () -> Unit,
|
||||
onEditSelectedWalletClick: () -> Unit,
|
||||
onDeleteSelectedWalletsClick: () -> Unit,
|
||||
) {
|
||||
val showEditAction = remember(editingWalletsSize) { editingWalletsSize in 1 until 2 }
|
||||
Row(
|
||||
modifier = modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing8)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = onClearSelectedClick,
|
||||
val editingWalletsSize by rememberUpdatedState(newValue = editingWalletsIds.size)
|
||||
val hasEditingWallets by remember {
|
||||
derivedStateOf { editingWalletsSize > 0 }
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Hand()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size44),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_close),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = "Unselect wallets",
|
||||
)
|
||||
}
|
||||
SpacerW16()
|
||||
Text(
|
||||
text = stringResource(id = R.string.user_wallet_list_editing_count, editingWalletsSize),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
SpacerWMax()
|
||||
if (showEditAction) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = onEditSelectedWalletClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_pencil_outline_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = "Edit wallet name",
|
||||
if (hasEditingWallets) {
|
||||
EditWalletsBar(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
editingWalletsSize = editingWalletsSize,
|
||||
onClearSelectedClick = onClearSelectedClick,
|
||||
onEditSelectedWalletClick = onEditSelectedWalletClick,
|
||||
onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(R.string.user_wallet_list_title),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
SpacerW8()
|
||||
}
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = onDeleteSelectedWalletsClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_trash),
|
||||
tint = TangemTheme.colors.icon.warning,
|
||||
contentDescription = "Delete selected wallets",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -208,6 +176,101 @@ private fun WalletsList(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Footer(
|
||||
modifier: Modifier = Modifier,
|
||||
isLocked: Boolean,
|
||||
showUnlockProgress: Boolean,
|
||||
showAddCardProgress: Boolean,
|
||||
onUnlockClick: () -> Unit,
|
||||
onAddCardClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
if (isLocked) {
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(
|
||||
id = R.string.user_wallet_list_unlock_all,
|
||||
stringResource(id = R.string.common_biometrics),
|
||||
),
|
||||
showProgress = showUnlockProgress,
|
||||
onClick = onUnlockClick,
|
||||
)
|
||||
}
|
||||
SecondaryButtonIconRight(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(R.string.user_wallet_list_add_button),
|
||||
showProgress = showAddCardProgress,
|
||||
icon = painterResource(id = R.drawable.ic_tangem),
|
||||
onClick = onAddCardClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EditWalletsBar(
|
||||
modifier: Modifier = Modifier,
|
||||
editingWalletsSize: Int,
|
||||
onClearSelectedClick: () -> Unit,
|
||||
onEditSelectedWalletClick: () -> Unit,
|
||||
onDeleteSelectedWalletsClick: () -> Unit,
|
||||
) {
|
||||
val showEditAction by remember(editingWalletsSize) {
|
||||
derivedStateOf { editingWalletsSize in 1 until 2 }
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = onClearSelectedClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_close),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = "Unselect wallets",
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = R.string.user_wallet_list_editing_count, editingWalletsSize),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
SpacerWMax()
|
||||
if (showEditAction) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = onEditSelectedWalletClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_pencil_outline_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = "Change wallet name",
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = onDeleteSelectedWalletsClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_trash),
|
||||
tint = TangemTheme.colors.icon.warning,
|
||||
contentDescription = "Delete selected wallets",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun WalletSelectorScreenContentSample(
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ import androidx.compose.foundation.shape.CircleShape
|
|||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -85,14 +84,15 @@ private fun WalletCardImage(
|
|||
.height(TangemTheme.dimens.size32)
|
||||
.clip(TangemTheme.shapes.roundedCornersSmall2)
|
||||
|
||||
val checkedColorFilter by rememberUpdatedState(
|
||||
newValue = if (isChecked) {
|
||||
val tintColor = TangemTheme.colors.icon.accent.copy(alpha = .6f)
|
||||
val checkedColorFilter = remember(isChecked) {
|
||||
if (isChecked) {
|
||||
ColorFilter.tint(
|
||||
color = TangemTheme.colors.icon.accent.copy(alpha = .6f),
|
||||
color = tintColor,
|
||||
blendMode = BlendMode.SrcOver,
|
||||
)
|
||||
} else null,
|
||||
)
|
||||
} else null
|
||||
}
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = cardImageModifier,
|
||||
|
|
@ -106,22 +106,15 @@ private fun WalletCardImage(
|
|||
contentDescription = null,
|
||||
)
|
||||
|
||||
if (isSelected) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.size(TangemTheme.dimens.size18)
|
||||
.background(
|
||||
color = Color.White,
|
||||
shape = CircleShape,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(all = (0.5).dp),
|
||||
painter = painterResource(id = R.drawable.ic_check_circle_18),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
when {
|
||||
isChecked -> {
|
||||
CheckedWalletMark(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
)
|
||||
}
|
||||
isSelected -> {
|
||||
SelectedWalletBadge(
|
||||
modifier = Modifier.align(Alignment.TopEnd),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -308,4 +301,42 @@ private fun CardImageShimmer(
|
|||
.background(TangemTheme.colors.button.secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SelectedWalletBadge(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size18)
|
||||
.background(
|
||||
color = Color.White,
|
||||
shape = CircleShape,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(all = (0.5).dp),
|
||||
painter = painterResource(id = R.drawable.ic_check_circle_18),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CheckedWalletMark(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier,
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_check_24),
|
||||
tint = TangemTheme.colors.icon.primary2,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.common.core.TangemError
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -14,5 +15,7 @@ internal sealed interface WelcomeAction : Action {
|
|||
data class Error(val error: TangemError) : WelcomeAction
|
||||
}
|
||||
|
||||
data class HandleDeepLink(val intent: Intent?) : WelcomeAction
|
||||
|
||||
object CloseError : WelcomeAction
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
|
|
@ -10,37 +11,47 @@ import com.tangem.tap.common.redux.navigation.AppScreen
|
|||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.model.builders.UserWalletBuilder
|
||||
import com.tangem.tap.domain.scanCard.ScanCardProcessor
|
||||
import com.tangem.tap.intentHandler
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
internal class WelcomeMiddleware {
|
||||
val middleware: Middleware<AppState> = { _, _ ->
|
||||
val middleware: Middleware<AppState> = { _, appStateProvider ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (action is WelcomeAction) {
|
||||
handleAction(action)
|
||||
val appState = appStateProvider()
|
||||
if (action is WelcomeAction && appState != null) {
|
||||
handleAction(action, appState.welcomeState)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAction(action: WelcomeAction) {
|
||||
private fun handleAction(action: WelcomeAction, state: WelcomeState) {
|
||||
when (action) {
|
||||
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometry()
|
||||
is WelcomeAction.ProceedWithCard -> proceedWithCard()
|
||||
is WelcomeAction.ProceedWithBiometrics.Error -> Unit
|
||||
is WelcomeAction.ProceedWithCard.Error -> Unit
|
||||
is WelcomeAction.ProceedWithBiometrics.Success -> Unit
|
||||
is WelcomeAction.ProceedWithCard.Success -> Unit
|
||||
is WelcomeAction.CloseError -> Unit
|
||||
is WelcomeAction.ProceedWithBiometrics -> {
|
||||
proceedWithBiometry(state)
|
||||
}
|
||||
is WelcomeAction.ProceedWithCard -> {
|
||||
proceedWithCard(state)
|
||||
}
|
||||
is WelcomeAction.ProceedWithBiometrics.Error,
|
||||
is WelcomeAction.ProceedWithCard.Error,
|
||||
is WelcomeAction.ProceedWithBiometrics.Success,
|
||||
is WelcomeAction.ProceedWithCard.Success,
|
||||
is WelcomeAction.CloseError,
|
||||
is WelcomeAction.HandleDeepLink,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithBiometry() {
|
||||
private fun proceedWithBiometry(state: WelcomeState) {
|
||||
scope.launch {
|
||||
userWalletsListManager.unlockWithBiometry()
|
||||
.doOnFailure { error ->
|
||||
|
|
@ -48,15 +59,22 @@ internal class WelcomeMiddleware {
|
|||
}
|
||||
.doOnSuccess { selectedUserWallet ->
|
||||
if (selectedUserWallet != null) {
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
|
||||
selectedUserWallet.scanResponse.card.isAccessCodeSet,
|
||||
)
|
||||
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(selectedUserWallet)
|
||||
|
||||
handleDeepLinkIfNeeded(state.deepLinkIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithCard() = scope.launch {
|
||||
private fun proceedWithCard(state: WelcomeState) = scope.launch {
|
||||
scanCardInternal { scanResponse ->
|
||||
val userWallet = UserWalletBuilder(scanResponse).build()
|
||||
|
||||
|
|
@ -65,13 +83,22 @@ internal class WelcomeMiddleware {
|
|||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error))
|
||||
}
|
||||
.doOnSuccess {
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = false,
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success)
|
||||
store.onUserWalletSelected(userWallet)
|
||||
|
||||
handleDeepLinkIfNeeded(state.deepLinkIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeepLinkIfNeeded(intent: Intent?) {
|
||||
intentHandler.handleWalletConnectLink(intent)
|
||||
}
|
||||
|
||||
private suspend inline fun scanCardInternal(
|
||||
crossinline onCardScanned: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ internal object WelcomeReducer {
|
|||
|
||||
private fun internalReduce(action: WelcomeAction, state: WelcomeState): WelcomeState {
|
||||
return when (action) {
|
||||
is WelcomeAction.HandleDeepLink -> state.copy(deepLinkIntent = action.intent)
|
||||
is WelcomeAction.ProceedWithBiometrics -> state.copy(isUnlockWithBiometricsInProgress = true)
|
||||
is WelcomeAction.ProceedWithCard -> state.copy(isUnlockWithCardInProgress = true)
|
||||
is WelcomeAction.ProceedWithBiometrics.Error -> state.copy(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.common.core.TangemError
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class WelcomeState(
|
||||
val isUnlockWithBiometricsInProgress: Boolean = false,
|
||||
val isUnlockWithCardInProgress: Boolean = false,
|
||||
val deepLinkIntent: Intent? = null,
|
||||
val error: TangemError? = null,
|
||||
) : StateType
|
||||
|
|
@ -1,12 +1,19 @@
|
|||
package com.tangem.tap.features.welcome.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
|
|
@ -39,6 +46,10 @@ internal class WelcomeFragment : ComposeFragment<WelcomeScreenState>() {
|
|||
setSystemBarsColor(color = backgroundColor)
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.systemBarsPadding(),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.common.extensions.calculateSha512
|
|||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.network.common.createRetrofitInstance
|
||||
import com.tangem.datasource.api.common.createRetrofitInstance
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.domain.common.extensions.withIOContext
|
||||
import com.tangem.network.common.createRetrofitInstance
|
||||
import com.tangem.datasource.api.common.createRetrofitInstance
|
||||
import com.tangem.tap.common.extensions.urlEncode
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.network.payid
|
|||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.network.common.createRetrofitInstance
|
||||
import com.tangem.datasource.api.common.createRetrofitInstance
|
||||
import retrofit2.Retrofit
|
||||
|
||||
class PayIdService {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.network.payid
|
|||
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.network.common.createRetrofitInstance
|
||||
import com.tangem.datasource.api.common.createRetrofitInstance
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.persistence
|
|||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.datasource.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import android.app.Application
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import java.util.*
|
||||
|
||||
class PreferencesStorage(applicationContext: Application) {
|
||||
|
|
@ -33,7 +33,7 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
get() = preferences.getLong(CHAT_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L }
|
||||
set(value) = preferences.edit { putLong(CHAT_FIRST_LAUNCH_KEY, value ?: 0) }
|
||||
|
||||
var shouldShowSaveWallet: Boolean
|
||||
var shouldShowSaveUserWalletScreen: Boolean
|
||||
get() = preferences.getBoolean(SAVE_WALLET_DIALOG_SHOWN_KEY, true)
|
||||
set(value) = preferences.edit {
|
||||
putBoolean(SAVE_WALLET_DIALOG_SHOWN_KEY, value)
|
||||
|
|
|
|||
|
|
@ -27,20 +27,13 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.skydoves.androidveil.VeilLayout
|
||||
android:id="@+id/veil_balance"
|
||||
<FrameLayout
|
||||
android:id="@+id/balance_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_processing"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_title"
|
||||
app:veilLayout_baseColor="@color/lightGray0"
|
||||
app:veilLayout_highlightColor="@color/lightGray1"
|
||||
app:veilLayout_layout="@layout/card_total_balance_shimmer"
|
||||
app:veilLayout_radius="4dp"
|
||||
app:veilLayout_shimmerEnable="true"
|
||||
app:veilLayout_veiled="true"
|
||||
tools:veilLayout_veiled="false">
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_title">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_balance"
|
||||
|
|
@ -51,9 +44,23 @@
|
|||
android:textColor="@color/darkGray6"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
tools:text="22 325.40 $" />
|
||||
|
||||
</com.skydoves.androidveil.VeilLayout>
|
||||
<com.skydoves.androidveil.VeilLayout
|
||||
android:id="@+id/veil_balance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="visible"
|
||||
app:veilLayout_baseColor="@color/lightGray0"
|
||||
app:veilLayout_highlightColor="@color/lightGray1"
|
||||
app:veilLayout_layout="@layout/card_total_balance_shimmer"
|
||||
app:veilLayout_radius="4dp"
|
||||
app:veilLayout_shimmerEnable="true"
|
||||
app:veilLayout_veiled="true" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_processing"
|
||||
|
|
@ -65,7 +72,7 @@
|
|||
android:textSize="12sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/veil_balance"
|
||||
app:layout_constraintTop_toBottomOf="@id/balance_container"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ dependencies {
|
|||
/** Project */
|
||||
implementation(project(":libs:auth"))
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(Tangem.cardCore)
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.network.common
|
||||
package com.tangem.datasource.api.common
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
|
@ -7,7 +7,7 @@ import okhttp3.Response
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
open class AddHeaderInterceptor(
|
||||
private val headers: Map<String, String>
|
||||
private val headers: Map<String, String>,
|
||||
) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
|
|
@ -21,10 +21,10 @@ open class AddHeaderInterceptor(
|
|||
}
|
||||
}
|
||||
|
||||
class CacheControlHttpInterceptor(maxAgeSeconds: Int) : AddHeaderInterceptor(mapOf(
|
||||
"Cache-Control" to "max-age=$maxAgeSeconds",
|
||||
))
|
||||
class CacheControlHttpInterceptor(maxAgeSeconds: Int) : AddHeaderInterceptor(
|
||||
mapOf("Cache-Control" to "max-age=$maxAgeSeconds"),
|
||||
)
|
||||
|
||||
class CardPublicKeyHttpInterceptor(cardPublicKeyHex: String) : AddHeaderInterceptor(mapOf(
|
||||
"card_public_key" to cardPublicKeyHex,
|
||||
))
|
||||
class CardPublicKeyHttpInterceptor(cardPublicKeyHex: String) : AddHeaderInterceptor(
|
||||
mapOf("card_public_key" to cardPublicKeyHex),
|
||||
)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.network.common
|
||||
package com.tangem.datasource.api.common
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
|
|
@ -9,8 +9,10 @@ import retrofit2.converter.moshi.MoshiConverterFactory
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
//todo needs to be refactored
|
||||
object MoshiConverter {
|
||||
|
||||
//todo refactor: provide via DI
|
||||
var INSTANCE = MoshiJsonConverter()
|
||||
private set
|
||||
|
||||
|
|
@ -23,7 +25,9 @@ object MoshiConverter {
|
|||
|
||||
fun createFactory(moshi: Moshi = INSTANCE.moshi): Converter.Factory = MoshiConverterFactory.create(moshi)
|
||||
|
||||
//todo provide via DI using quealifiers
|
||||
fun defaultMoshi(): Moshi = INSTANCE.moshi
|
||||
|
||||
//todo provide via DI using quealifiers
|
||||
fun sdkMoshi(): Moshi = MoshiJsonConverter.INSTANCE.moshi
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.network.common
|
||||
package com.tangem.datasource.api.common
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
package com.tangem.datasource.api.oneinch
|
||||
|
||||
import com.tangem.datasource.api.oneinch.models.AllowanceResponse
|
||||
import com.tangem.datasource.api.oneinch.models.ApproveCalldataResponse
|
||||
import com.tangem.datasource.api.oneinch.models.ApproveSpenderResponse
|
||||
import com.tangem.datasource.api.oneinch.models.ProtocolsResponse
|
||||
import com.tangem.datasource.api.oneinch.models.QuoteResponse
|
||||
import com.tangem.datasource.api.oneinch.models.StatusResponse
|
||||
import com.tangem.datasource.api.oneinch.models.SwapResponse
|
||||
import com.tangem.datasource.api.oneinch.models.TokensResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface OneInchApi {
|
||||
|
||||
/**
|
||||
* Healthcheck return 200 if service is available
|
||||
*
|
||||
* @return [StatusResponse]
|
||||
*/
|
||||
@GET("healthcheck")
|
||||
suspend fun healthcheck(): StatusResponse
|
||||
|
||||
//region Approve
|
||||
/**
|
||||
* Address of the 1inch router that must be trusted to spend funds for the exchange
|
||||
*
|
||||
* @return [ApproveSpenderResponse]
|
||||
*/
|
||||
@GET("approve/spender")
|
||||
suspend fun approveSpender(): ApproveSpenderResponse
|
||||
|
||||
/**
|
||||
* Generate data for calling the contract in order to allow the 1inch router to spend funds
|
||||
*
|
||||
* @param tokenAddress Token address you want to exchange
|
||||
* @param amount The number of tokens that the 1inch router is allowed to spend.
|
||||
* If not specified, it will be allowed to spend an infinite amount of tokens.
|
||||
*
|
||||
* @return [ApproveCalldataResponse] Transaction body to allow the exchange with the 1inch router
|
||||
*/
|
||||
@GET("approve/transaction")
|
||||
suspend fun approveTransaction(
|
||||
@Query("tokenAddress") tokenAddress: String,
|
||||
@Query("amount") amount: String? = null,
|
||||
): ApproveCalldataResponse
|
||||
|
||||
/**
|
||||
* Get the number of tokens that the 1inch router is allowed to spend
|
||||
*
|
||||
* @param tokenAddress Token address you want to exchange
|
||||
* @param walletAddress Wallet address for which you want to check
|
||||
*
|
||||
* @return [AllowanceResponse]
|
||||
*/
|
||||
@GET("approve/allowance")
|
||||
suspend fun approveAllowance(
|
||||
@Query("tokenAddress") tokenAddress: String,
|
||||
@Query("walletAddress") walletAddress: String,
|
||||
): AllowanceResponse
|
||||
//endregion Approve
|
||||
|
||||
//region Info
|
||||
/**
|
||||
* List of tokens that are available for swap in the 1inch Aggregation protocol
|
||||
*
|
||||
* @return [TokensResponse]
|
||||
*/
|
||||
@GET("tokens")
|
||||
suspend fun tokensAvailable(): TokensResponse
|
||||
|
||||
/**
|
||||
* List of liquidity sources that are available for routing in the 1inch Aggregation protocol
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GET("liquidity-sources")
|
||||
suspend fun liquiditySources(): ProtocolsResponse
|
||||
//endregion Info
|
||||
|
||||
//region Swap
|
||||
/**
|
||||
* Find the best quote to exchange via 1inch router
|
||||
*
|
||||
* @param fromTokenAddress Example : 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
|
||||
* @param toTokenAddress Example : 0x111111111117dc0aa78b770fa6a738034120c302
|
||||
* @param amount amount of a token to sell, set in minimal divisible units e.g.:
|
||||
* 1.00 DAI set as 1000000000000000000
|
||||
* 51.03 USDC set as 51030000
|
||||
*
|
||||
* @param protocols default: all
|
||||
* @param fee this percentage of fromTokenAddress token amount will be sent to referrerAddress,
|
||||
* the rest will be used as input for a swap
|
||||
* Min: 0; max: 3; Max: 0; max: 3; default: 0; !should be the same for quote and swap!
|
||||
*
|
||||
* @param gasLimit maximum amount of gas for a swap;
|
||||
* @param connectorTokens token-connectors can be specified via this parameter.
|
||||
* The more is set — the longer route estimation will take.
|
||||
* If not set, default token-connectors will be usedmax: 5; !should be the same for quote and swap!
|
||||
*
|
||||
* @param complexityLevel maximum number of token-connectors to be used in a transaction.
|
||||
* The more is used — the longer route estimation will take
|
||||
* min: 0; max: 3; default: 2; !should be the same for quote and swap!
|
||||
*
|
||||
* @param mainRouteParts default: 10; max: 50 !should be the same for quote and swap!
|
||||
* @param parts limit maximum number of parts each main route parts can be split into;
|
||||
* should be the same for a quote and swap
|
||||
* default: 20; max: 100
|
||||
*
|
||||
* @param gasPrice 1inch takes in account gas expenses to determine exchange route.
|
||||
* It is important to use the same gas price on the quote and swap methods.
|
||||
* Gas price set in wei: 12.5 GWEI set as 12500000000
|
||||
* default: fast from network
|
||||
*
|
||||
* @return [QuoteResponse]
|
||||
*/
|
||||
suspend fun quote(
|
||||
@Query("fromTokenAddress") fromTokenAddress: String,
|
||||
@Query("toTokenAddress") toTokenAddress: String,
|
||||
@Query("amount") amount: String,
|
||||
@Query("protocols") protocols: String? = null,
|
||||
@Query("fee") fee: String? = null,
|
||||
@Query("gasLimit") gasLimit: String? = null,
|
||||
@Query("connectorTokens") connectorTokens: String? = null,
|
||||
@Query("complexityLevel") complexityLevel: String? = null,
|
||||
@Query("mainRouteParts") mainRouteParts: String? = null,
|
||||
@Query("parts") parts: String? = null,
|
||||
@Query("gasPrice") gasPrice: String? = null,
|
||||
): QuoteResponse
|
||||
|
||||
/**
|
||||
* Generate data for calling the 1inch router for exchange
|
||||
*
|
||||
* @param fromTokenAddress Example : 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
|
||||
* @param toTokenAddress Example : 0x111111111117dc0aa78b770fa6a738034120c302
|
||||
* @param amount amount of a token to sell, set in minimal divisible units e.g.:
|
||||
* 1.00 DAI set as 1000000000000000000
|
||||
* 51.03 USDC set as 51030000
|
||||
*
|
||||
* @param fromAddress The address that calls the 1inch contract
|
||||
* @param slippage limit of price slippage you are willing to accept in percentage, may be set with decimals.
|
||||
* &slippage=0.5 means 0.5% slippage is acceptable. Low values increase chances that transaction will fail,
|
||||
* high values increase chances of front running. min: 0; max: 50;
|
||||
*
|
||||
* @param protocols default: all
|
||||
* @param destinationAddress Receiver of destination currency. default: fromAddress
|
||||
* @param fee this percentage of fromTokenAddress token amount will be sent to referrerAddress,
|
||||
* the rest will be used as input for a swap
|
||||
* Min: 0; max: 3; Max: 0; max: 3; default: 0; !should be the same for quote and swap!
|
||||
*
|
||||
* @param permit https://eips.ethereum.org/EIPS/eip-2612
|
||||
* @param compatibilityMode Allows to build calldata without optimized routers
|
||||
* @param burnChi If true, CHI will be burned from fromAddress to compensate gas.
|
||||
* Check CHI balance and allowance before turning that on. CHI should be approved for the spender address
|
||||
*
|
||||
* @param connectorTokens token-connectors can be specified via this parameter.
|
||||
* The more is set — the longer route estimation will take.
|
||||
* If not set, default token-connectors will be usedmax: 5; !should be the same for quote and swap!
|
||||
*
|
||||
* @param complexityLevel maximum number of token-connectors to be used in a transaction.
|
||||
* The more is used — the longer route estimation will take
|
||||
* min: 0; max: 3; default: 2; !should be the same for quote and swap!
|
||||
*
|
||||
* @param mainRouteParts default: 10; max: 50 !should be the same for quote and swap!
|
||||
* @param parts limit maximum number of parts each main route parts can be split into;
|
||||
* should be the same for a quote and swap
|
||||
* default: 20; max: 100
|
||||
*
|
||||
* @param gasLimit maximum amount of gas for a swap;
|
||||
* @param gasPrice 1inch takes in account gas expenses to determine exchange route.
|
||||
* It is important to use the same gas price on the quote and swap methods.
|
||||
* Gas price set in wei: 12.5 GWEI set as 12500000000
|
||||
* default: fast from network
|
||||
*
|
||||
* @return [SwapResponse]
|
||||
*/
|
||||
suspend fun swap(
|
||||
@Query("fromTokenAddress") fromTokenAddress: String,
|
||||
@Query("toTokenAddress") toTokenAddress: String,
|
||||
@Query("amount") amount: String,
|
||||
@Query("fromAddress") fromAddress: String,
|
||||
@Query("slippage") slippage: Int,
|
||||
@Query("protocols") protocols: String? = null,
|
||||
@Query("destReceiver") destinationAddress: String? = null,
|
||||
@Query("referrerAddress") referrerAddress: String? = null,
|
||||
@Query("fee") fee: String? = null,
|
||||
@Query("disableEstimate") disableEstimate: Boolean? = null,
|
||||
@Query("permit") permit: String? = null,
|
||||
@Query("compatibilityMode") compatibilityMode: Boolean? = null,
|
||||
@Query("burnChi") burnChi: Boolean? = null,
|
||||
@Query("allowPartialFill") allowPartialFill: Boolean? = null,
|
||||
@Query("parts") parts: String? = null,
|
||||
@Query("mainRouteParts") mainRouteParts: String? = null,
|
||||
@Query("connectorTokens") connectorTokens: String? = null,
|
||||
@Query("complexityLevel") complexityLevel: String? = null,
|
||||
@Query("gasLimit") gasLimit: String? = null,
|
||||
@Query("gasPrice") gasPrice: String? = null,
|
||||
): SwapResponse
|
||||
//endregion Swap
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.datasource.api.oneinch.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
data class AllowanceResponse(
|
||||
@Json(name = "allowance") val allowance: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.api.oneinch.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
* Approve calldata response
|
||||
*
|
||||
* @property data The encoded data to call the approve method on the swapped token contract
|
||||
* @property gasPrice Gas price for fast transaction processing
|
||||
* @property toAddress Token address that will be allowed to exchange through 1inch router
|
||||
* @property value Native token value in WEI (for approve is always 0)
|
||||
*/
|
||||
data class ApproveCalldataResponse(
|
||||
@Json(name = "data") val data: String,
|
||||
@Json(name = "gasPrice") val gasPrice: String,
|
||||
@Json(name = "to") val toAddress: String,
|
||||
@Json(name = "value") val value: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.api.oneinch.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
* Approve spender response
|
||||
*
|
||||
* @property address Address of the 1inch router that must be trusted to spend funds for the exchange
|
||||
*/
|
||||
data class ApproveSpenderResponse(
|
||||
@Json(name = "address") val address: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.datasource.api.oneinch.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
* Path view dto
|
||||
*/
|
||||
data class PathViewDto(
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "part") val part: Int,
|
||||
@Json(name = "fromTokenAddress") val fromTokenAddress: String,
|
||||
@Json(name = "toTokenAddress") val toTokenAddress: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.datasource.api.oneinch.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
* Protocols response
|
||||
*
|
||||
* @property protocols List of protocols that are available for routing in the 1inch Aggregation protocol
|
||||
*/
|
||||
data class ProtocolsResponse(
|
||||
@Json(name = "protocols") val protocols: List<ProtocolImageDto>,
|
||||
)
|
||||
|
||||
/**
|
||||
* Protocol image
|
||||
*
|
||||
* @property id Protocol id
|
||||
* @property title Protocol title
|
||||
* @property image Protocol logo image
|
||||
* @property imageColor Protocol logo image in color
|
||||
*/
|
||||
data class ProtocolImageDto(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "title") val title: String,
|
||||
@Json(name = "img") val image: String,
|
||||
@Json(name = "img_color") val imageColor: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.datasource.api.oneinch.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
/**
|
||||
* Quote response
|
||||
*
|
||||
* @property fromToken Source token info
|
||||
* @property toToken Destination token info
|
||||
* @property toTokenAmount Expected amount of destination token
|
||||
* @property fromTokenAmount Amount of source token
|
||||
* @property protocols Selected protocols in a path
|
||||
* @property estimatedGas gas fee
|
||||
*/
|
||||
data class QuoteResponse(
|
||||
@Json(name = "fromToken") val fromToken: TokenOneInchDto,
|
||||
@Json(name = "toToken") val toToken: TokenOneInchDto,
|
||||
@Json(name = "toTokenAmount") val toTokenAmount: String,
|
||||
@Json(name = "fromTokenAmount") val fromTokenAmount: String,
|
||||
@Json(name = "protocols") val protocols: List<PathViewDto>,
|
||||
@Json(name = "estimatedGas") val estimatedGas: Int,
|
||||
)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.datasource.api.oneinch.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
data class StatusResponse(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "provider") val provider: String,
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue