Updated on 2026-08-14
This commit is contained in:
commit
8bba8e1afa
825 changed files with 9837 additions and 22210 deletions
|
|
@ -23,7 +23,7 @@ import com.tangem.wallet.R
|
|||
*/
|
||||
abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackPressedHandler {
|
||||
|
||||
protected lateinit var mainView: View
|
||||
private lateinit var mainView: View
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.customtoken.impl.data
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
|
|
@ -39,6 +40,7 @@ class DefaultCustomTokenRepository(
|
|||
contractAddress = address,
|
||||
networkIds = networkId ?: supportedTokenNetworkIds.joinToString(separator = ","),
|
||||
)
|
||||
.getOrThrow()
|
||||
.coins.firstNotNullOfOrNull { coin ->
|
||||
val networksWithTheSameAddress = coin.networks.filter { network ->
|
||||
(network.contractAddress != null || network.decimalCount != null) &&
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tap.features.customtoken.impl.di
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.tester.api.TesterFeatureToggles
|
||||
import com.tangem.tap.features.customtoken.impl.data.DefaultCustomTokenRepository
|
||||
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
|
||||
import com.tangem.tap.features.customtoken.impl.domain.DefaultCustomTokenInteractor
|
||||
|
|
@ -25,8 +27,10 @@ internal object CustomTokenInteractorModule {
|
|||
fun provideCustomTokenInteractor(
|
||||
tangemTechApi: TangemTechApi,
|
||||
appCoroutineDispatcherProvider: AppCoroutineDispatcherProvider,
|
||||
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
reduxStateHolder: AppStateHolder,
|
||||
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
testerFeatureToggles: TesterFeatureToggles,
|
||||
): CustomTokenInteractor {
|
||||
return DefaultCustomTokenInteractor(
|
||||
featureRepository = DefaultCustomTokenRepository(
|
||||
|
|
@ -35,6 +39,8 @@ internal object CustomTokenInteractorModule {
|
|||
reduxStateHolder = reduxStateHolder,
|
||||
),
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
derivePublicKeysUseCase = derivePublicKeysUseCase,
|
||||
testerFeatureToggles = testerFeatureToggles,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,10 @@ import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
|||
*/
|
||||
interface CustomTokenRepository {
|
||||
|
||||
/** Find token by [address] and [networkId] */
|
||||
/**
|
||||
* Find token by [address] and [networkId]
|
||||
*
|
||||
* @throws com.tangem.datasource.api.common.response.ApiResponseError
|
||||
* */
|
||||
suspend fun findToken(address: String, networkId: String?): FoundToken
|
||||
}
|
||||
|
|
@ -6,11 +6,10 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.common.configs.CardConfig
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
|
|
@ -20,21 +19,22 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.tester.api.TesterFeatureToggles
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
|
|
@ -47,6 +47,8 @@ import timber.log.Timber
|
|||
class DefaultCustomTokenInteractor(
|
||||
private val featureRepository: CustomTokenRepository,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
private val testerFeatureToggles: TesterFeatureToggles,
|
||||
) : CustomTokenInteractor {
|
||||
|
||||
// TODO: Move to DI
|
||||
|
|
@ -66,19 +68,29 @@ class DefaultCustomTokenInteractor(
|
|||
|
||||
override suspend fun saveToken(customCurrency: CustomCurrency) {
|
||||
val userWallet = getSelectedWalletSyncUseCase().fold(ifLeft = { return }, ifRight = { it })
|
||||
|
||||
val currency = Currency.fromCustomCurrency(customCurrency)
|
||||
val isNeedToDerive = isNeedToDerive(userWallet, currency)
|
||||
if (isNeedToDerive) {
|
||||
deriveMissingBlockchains(
|
||||
userWallet = userWallet,
|
||||
currencyList = listOf(currency),
|
||||
onSuccess = { submitAdd(userWallet = userWallet.copy(scanResponse = it), currency = currency) },
|
||||
) {
|
||||
throw it
|
||||
}
|
||||
|
||||
if (testerFeatureToggles.isDerivePublicKeysRefactoringEnabled) {
|
||||
val currencies = listOfNotNull(element = currency.toCryptoCurrency(userWallet.scanResponse))
|
||||
derivePublicKeysUseCase(userWalletId = userWallet.walletId, currencies = currencies)
|
||||
.onRight {
|
||||
addCryptoCurrenciesUseCase(userWalletId = userWallet.walletId, currencies = currencies)
|
||||
}
|
||||
.onLeft { Timber.e("Failed to derive public keys: $it") }
|
||||
} else {
|
||||
submitAdd(userWallet, currency)
|
||||
// TODO: delete [REDACTED_JIRA]
|
||||
val isNeedToDerive = isNeedToDerive(userWallet, currency)
|
||||
if (isNeedToDerive) {
|
||||
deriveMissingBlockchains(
|
||||
userWallet = userWallet,
|
||||
currencyList = listOf(currency),
|
||||
onSuccess = { submitAdd(userWallet = userWallet.copy(scanResponse = it), currency = currency) },
|
||||
) {
|
||||
throw it
|
||||
}
|
||||
} else {
|
||||
submitAdd(userWallet, currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,69 +191,36 @@ class DefaultCustomTokenInteractor(
|
|||
|
||||
private suspend fun submitAdd(userWallet: UserWallet, currency: Currency) {
|
||||
val scanResponse = userWallet.scanResponse
|
||||
val walletFeatureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
|
||||
val userWalletId = userWallet.walletId
|
||||
|
||||
if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
val cryptoCurrencyFactory = CryptoCurrencyFactory()
|
||||
val currencyList = listOfNotNull(element = currency.toCryptoCurrency(scanResponse))
|
||||
|
||||
submitNewAdd(
|
||||
userWalletId = userWallet.walletId,
|
||||
updatedScanResponse = scanResponse,
|
||||
currencyList = listOfNotNull(
|
||||
when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = currency.blockchain,
|
||||
extraDerivationPath = currency.derivationPath,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
is Currency.Token -> {
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = currency.token,
|
||||
blockchain = currency.blockchain,
|
||||
extraDerivationPath = currency.derivationPath,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
submitLegacyAdd(scanResponse = scanResponse, currency = currency)
|
||||
userWalletsListManager.update(userWalletId) {
|
||||
it.copy(scanResponse = scanResponse)
|
||||
}
|
||||
|
||||
addCryptoCurrenciesUseCase(userWalletId, currencyList)
|
||||
}
|
||||
|
||||
private suspend fun submitLegacyAdd(scanResponse: ScanResponse, currency: Currency) {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to add currencies, no user wallet selected")
|
||||
return
|
||||
}
|
||||
private fun Currency.toCryptoCurrency(scanResponse: ScanResponse): CryptoCurrency? {
|
||||
val cryptoCurrencyFactory = CryptoCurrencyFactory()
|
||||
|
||||
userWalletsListManager.update(
|
||||
userWalletId = selectedUserWallet.walletId,
|
||||
update = { userWallet -> userWallet.copy(scanResponse = scanResponse) },
|
||||
)
|
||||
.flatMap { updatedUserWallet ->
|
||||
walletCurrenciesManager.addCurrencies(
|
||||
userWallet = updatedUserWallet,
|
||||
currenciesToAdd = listOf(currency),
|
||||
return when (this) {
|
||||
is Currency.Blockchain -> {
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = derivationPath,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
is Currency.Token -> {
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = derivationPath,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun submitNewAdd(
|
||||
userWalletId: UserWalletId,
|
||||
updatedScanResponse: ScanResponse,
|
||||
currencyList: List<CryptoCurrency>,
|
||||
) {
|
||||
scope.launch {
|
||||
userWalletsListManager.update(
|
||||
userWalletId = userWalletId,
|
||||
update = { it.copy(scanResponse = updatedScanResponse) },
|
||||
)
|
||||
|
||||
addCryptoCurrenciesUseCase(userWalletId, currencyList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,8 +25,6 @@ import com.tangem.domain.features.addCustomToken.CustomCurrency
|
|||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.*
|
||||
|
|
@ -38,8 +36,6 @@ import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTok
|
|||
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContactAddressValidator
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContractAddressValidatorResult
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
|
@ -71,7 +67,6 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
private val featureInteractor: CustomTokenInteractor,
|
||||
private val dispatchers: AppCoroutineDispatcherProvider,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
||||
private val analyticsSender = AddCustomTokenAnalyticsSender(analyticsEventHandler)
|
||||
|
|
@ -88,18 +83,16 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
private var foundToken: FoundToken? = null
|
||||
|
||||
init {
|
||||
if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
currentCryptoCurrencies = getSelectedWalletSyncUseCase().fold(
|
||||
ifLeft = { emptyList() },
|
||||
ifRight = { selectedWallet ->
|
||||
getCurrenciesUseCase(selectedWallet.walletId).fold(
|
||||
ifLeft = { emptyList() },
|
||||
ifRight = { it },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
currentCryptoCurrencies = getSelectedWalletSyncUseCase().fold(
|
||||
ifLeft = { emptyList() },
|
||||
ifRight = { selectedWallet ->
|
||||
getCurrenciesUseCase(selectedWallet.walletId).fold(
|
||||
ifLeft = { emptyList() },
|
||||
ifRight = { it },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -591,14 +584,6 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun isTokenAlreadyAdded(): Boolean {
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
isTokenAlreadyAddedNew()
|
||||
} else {
|
||||
isTokenAlreadyAddedOld()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTokenAlreadyAddedNew(): Boolean {
|
||||
return currentCryptoCurrencies
|
||||
.filterIsInstance<CryptoCurrency.Token>()
|
||||
.any { token ->
|
||||
|
|
@ -621,32 +606,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isTokenAlreadyAddedOld(): Boolean {
|
||||
return store.state.walletState.walletsStores
|
||||
.map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
|
||||
.flatten()
|
||||
.filterIsInstance<Currency.Token>()
|
||||
.any { wrappedCurrency ->
|
||||
val contractAddress = uiState.form.contractAddressInputField.value
|
||||
val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
|
||||
val sameId = foundToken?.id == wrappedCurrency.token.id
|
||||
val sameAddress = contractAddress == wrappedCurrency.token.contractAddress
|
||||
val sameBlockchain =
|
||||
Blockchain.fromNetworkId(networkSelectorValue.toNetworkId()) == wrappedCurrency.blockchain
|
||||
val isSameDerivationPath = getDerivationPath().isSameDerivationPath(wrappedCurrency.derivationPath)
|
||||
sameId && sameAddress && sameBlockchain && isSameDerivationPath
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBlockchainAlreadyAdded(): Boolean {
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
isBlockchainAlreadyAddedNew()
|
||||
} else {
|
||||
isBlockchainAlreadyAddedOld()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBlockchainAlreadyAddedNew(): Boolean {
|
||||
return currentCryptoCurrencies
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.any { coin ->
|
||||
|
|
@ -655,22 +615,6 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isBlockchainAlreadyAddedOld(): Boolean {
|
||||
return store.state.walletState.walletsStores
|
||||
.map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
|
||||
.flatten()
|
||||
.filterIsInstance<Currency.Blockchain>()
|
||||
.any {
|
||||
val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
|
||||
networkSelectorValue == it.blockchain &&
|
||||
getDerivationPath().isSameDerivationPath(it.derivationPath)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DerivationPath?.isSameDerivationPath(rawDerivationPath: String?): Boolean {
|
||||
return this == rawDerivationPath?.let { DerivationPath(it) }
|
||||
}
|
||||
|
||||
private fun handleContractAddressErrorValidation(type: AddCustomTokenError) {
|
||||
when {
|
||||
isNetworkSelected() && type == AddCustomTokenError.InvalidContractAddress -> {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@ package com.tangem.tap.features.demo
|
|||
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -22,10 +20,7 @@ object DemoHelper {
|
|||
|
||||
private val disabledActionFeatures = listOf(
|
||||
WalletConnectAction.StartWalletConnect::class.java,
|
||||
TradeCryptoAction.Buy::class.java,
|
||||
TradeCryptoAction.Sell::class.java,
|
||||
BackupAction.StartBackup::class.java,
|
||||
WalletAction.ExploreAddress::class.java,
|
||||
DetailsAction.ResetToFactory.Start::class.java,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
|||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.features.details
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
|
||||
class DarkThemeFeatureToggle(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) {
|
||||
val isDarkThemeEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "DARK_THEME_ENABLED")
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.tap.features.details.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
|
||||
internal class DefaultDetailsFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : DetailsFeatureToggles {
|
||||
|
||||
override val isRedesignedAppCurrencySelectorEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_APP_CURRENCY_SELECTOR_ENABLED")
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.features.details.featuretoggles
|
||||
|
||||
interface DetailsFeatureToggles {
|
||||
|
||||
val isRedesignedAppCurrencySelectorEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.tap.features.details.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object DetailsFeatureTogglesModule {
|
||||
|
||||
@Provides
|
||||
fun provideDetailsFeatureToggles(featureTogglesManager: FeatureTogglesManager): DetailsFeatureToggles {
|
||||
return DefaultDetailsFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,18 +2,17 @@ package com.tangem.tap.features.details.redux
|
|||
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class DetailsAction : Action {
|
||||
|
||||
data class PrepareScreen(
|
||||
val scanResponse: ScanResponse,
|
||||
val darkThemeSwitchEnabled: Boolean,
|
||||
val shouldSaveUserWallets: Boolean,
|
||||
) : DetailsAction()
|
||||
|
||||
|
|
@ -92,9 +91,9 @@ sealed class DetailsAction : Action {
|
|||
) : AppSettings()
|
||||
|
||||
data class ChangeAppCurrency(
|
||||
val fiatCurrency: FiatCurrency,
|
||||
val currency: AppCurrency,
|
||||
) : AppSettings()
|
||||
}
|
||||
|
||||
data class ChangeAppCurrency(val fiatCurrency: FiatCurrency) : DetailsAction()
|
||||
data class ChangeAppCurrency(val currency: AppCurrency) : DetailsAction()
|
||||
}
|
||||
|
|
@ -36,8 +36,6 @@ import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
|
|||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
|
|
@ -233,9 +231,8 @@ class DetailsMiddleware {
|
|||
changeBalanceHiding(action.hideBalance)
|
||||
}
|
||||
is DetailsAction.AppSettings.ChangeAppCurrency -> {
|
||||
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency))
|
||||
store.dispatch(DetailsAction.ChangeAppCurrency(action.fiatCurrency))
|
||||
store.dispatch(WalletSelectorAction.ChangeAppCurrency(action.fiatCurrency))
|
||||
store.dispatch(GlobalAction.ChangeAppCurrency(action.currency))
|
||||
store.dispatch(DetailsAction.ChangeAppCurrency(action.currency))
|
||||
}
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting.Success,
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting.Failure,
|
||||
|
|
@ -381,8 +378,6 @@ class DetailsMiddleware {
|
|||
preferencesStorage.shouldShowSaveUserWalletScreen = false
|
||||
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
|
||||
.saveShouldSaveUserWallets(item = true)
|
||||
|
||||
store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true))
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
|
|
@ -391,7 +386,6 @@ class DetailsMiddleware {
|
|||
|
||||
private suspend fun deleteSavedWalletsAndAccessCodes(): CompletionResult<Unit> {
|
||||
return userWalletsListManager.clear()
|
||||
.flatMap { walletStoresManager.clear() }
|
||||
.doOnSuccess {
|
||||
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off))
|
||||
deleteSavedAccessCodes()
|
||||
|
|
@ -399,7 +393,6 @@ class DetailsMiddleware {
|
|||
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
|
||||
.saveShouldSaveUserWallets(item = false)
|
||||
|
||||
store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true))
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
}
|
||||
is DetailsAction.ChangeAppCurrency -> detailsState.copy(
|
||||
appSettingsState = detailsState.appSettingsState.copy(
|
||||
selectedFiatCurrency = action.fiatCurrency,
|
||||
selectedAppCurrency = action.currency,
|
||||
),
|
||||
)
|
||||
is DetailsAction.AccessCodeRecovery -> handleAccessCodeRecoveryAction(action, detailsState)
|
||||
|
|
@ -74,7 +74,7 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta
|
|||
isBiometricsAvailable = tangemSdkManager.canUseBiometry,
|
||||
saveWallets = action.shouldSaveUserWallets,
|
||||
saveAccessCodes = preferencesStorage.shouldSaveAccessCodes,
|
||||
selectedFiatCurrency = store.state.globalState.appCurrency,
|
||||
selectedAppCurrency = store.state.globalState.appCurrency,
|
||||
selectedThemeMode = runBlocking {
|
||||
store.state.daggerGraphState
|
||||
.get { appThemeModeRepository }.getAppThemeMode().firstOrNull() ?: AppThemeMode.DEFAULT
|
||||
|
|
@ -83,7 +83,6 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta
|
|||
store.state.daggerGraphState
|
||||
.get { balanceHidingRepository }.getBalanceHidingSettings().isHidingEnabledInSettings
|
||||
},
|
||||
darkThemeSwitchEnabled = action.darkThemeSwitchEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -258,7 +257,7 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
|||
)
|
||||
is DetailsAction.AppSettings.ChangeAppCurrency -> state.copy(
|
||||
appSettingsState = state.appSettingsState.copy(
|
||||
selectedFiatCurrency = action.fiatCurrency,
|
||||
selectedAppCurrency = action.currency,
|
||||
),
|
||||
)
|
||||
is DetailsAction.AppSettings.ChangeBalanceHiding -> state.copy(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.entities.Button
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import org.rekotlin.StateType
|
||||
import java.util.EnumSet
|
||||
|
||||
|
|
@ -62,9 +62,8 @@ data class AppSettingsState(
|
|||
val needEnrollBiometrics: Boolean = false,
|
||||
val isHidingEnabled: Boolean = false,
|
||||
val isInProgress: Boolean = false,
|
||||
val selectedFiatCurrency: FiatCurrency = FiatCurrency.Default,
|
||||
val selectedAppCurrency: AppCurrency = AppCurrency.Default,
|
||||
val selectedThemeMode: AppThemeMode = AppThemeMode.DEFAULT,
|
||||
val darkThemeSwitchEnabled: Boolean = false,
|
||||
)
|
||||
|
||||
enum class SecurityOption { LongTap, PassCode, AccessCode }
|
||||
|
|
|
|||
|
|
@ -74,8 +74,6 @@ sealed class WalletConnectAction : Action {
|
|||
) :
|
||||
WalletConnectAction()
|
||||
|
||||
data class SetDataToSend(val transactionData: WcTransactionData) : WalletConnectAction()
|
||||
|
||||
data class HandlePersonalSignRequest(
|
||||
val message: WCEthereumSignMessage,
|
||||
val session: WalletConnectSession,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import androidx.core.os.bundleOf
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.walletconnect.WalletConnectActions
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.feature.qrscanning.SourceType
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -29,11 +33,11 @@ import com.tangem.tap.domain.walletconnect2.domain.models.Account
|
|||
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -97,7 +101,14 @@ class WalletConnectMiddleware {
|
|||
if (uri != null && isWalletConnectUri(uri)) {
|
||||
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.QrScan))
|
||||
store.dispatchOnMain(
|
||||
NavigationAction.NavigateTo(
|
||||
screen = AppScreen.QrScanning,
|
||||
bundle = bundleOf(
|
||||
QrScanningRouter.SOURCE_KEY to SourceType.WALLET_CONNECT,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.SelectNetwork -> {
|
||||
|
|
@ -115,7 +126,6 @@ class WalletConnectMiddleware {
|
|||
scope.launch {
|
||||
prepareWalletManager(
|
||||
scanResponse = data.scanResponse,
|
||||
walletState = store.state.walletState,
|
||||
blockchain = action.blockchain,
|
||||
session = data.session,
|
||||
walletConnectManager = walletConnectManager,
|
||||
|
|
@ -176,8 +186,14 @@ class WalletConnectMiddleware {
|
|||
// )
|
||||
}
|
||||
is WalletConnectAction.ScanCard -> {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
scanCard(scanResponse, action.session, action.chainId)
|
||||
val scanResponse = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.w("Unable to get selected user wallet for WC session")
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch(Dispatchers.Main) {
|
||||
scanCard(scanResponse, action.session, action.chainId)
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.ApproveSession -> {
|
||||
walletConnectManager.approve(action.session)
|
||||
|
|
@ -274,7 +290,6 @@ class WalletConnectMiddleware {
|
|||
val walletManager = getWalletManager(
|
||||
wallet = action.session.wallet,
|
||||
blockchain = blockchain,
|
||||
walletState = store.state.walletState,
|
||||
).guard {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
|
|
@ -372,19 +387,14 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
|
||||
private suspend fun getWalletManagers(): List<WalletManager> {
|
||||
val walletManagerToggles = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletFeatureToggles)
|
||||
return if (walletManagerToggles.isRedesignedScreenEnabled) {
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList()
|
||||
walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
|
||||
} else {
|
||||
store.state.walletState.walletManagers
|
||||
}
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList()
|
||||
|
||||
return walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
|
||||
}
|
||||
|
||||
private fun scanCard(scanResponse: ScanResponse, session: WalletConnectSession, chainId: Int?) {
|
||||
private suspend fun scanCard(userWallet: UserWallet, session: WalletConnectSession, chainId: Int?) {
|
||||
val blockchain = WalletConnectNetworkUtils.parseBlockchain(
|
||||
chainId = chainId,
|
||||
peer = session.peerMeta,
|
||||
|
|
@ -393,27 +403,28 @@ class WalletConnectMiddleware {
|
|||
return
|
||||
}
|
||||
|
||||
handleScanResponse(scanResponse = scanResponse, session = session, blockchain = blockchain)
|
||||
handleScanResponse(userWallet, session, blockchain)
|
||||
}
|
||||
|
||||
private fun getAvailableBlockchains(
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
walletState: WalletState,
|
||||
): List<Blockchain> {
|
||||
return walletState.currencies.filter {
|
||||
it.isBlockchain() &&
|
||||
!it.isCustomCurrency(derivationStyleProvider.getDerivationStyle()) && it.blockchain.isEvm()
|
||||
}.map { it.blockchain }
|
||||
private suspend fun getAvailableEvmBlockchains(userWalletId: UserWalletId): List<Blockchain> {
|
||||
val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
|
||||
|
||||
return currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId)
|
||||
.asSequence()
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.filterNot { it.isCustom }
|
||||
.mapNotNull { Blockchain.fromNetworkId(it.network.id.value) }
|
||||
.filter { it.isEvm() }
|
||||
.toList()
|
||||
}
|
||||
|
||||
private suspend fun prepareWalletManager(
|
||||
scanResponse: ScanResponse,
|
||||
walletState: WalletState,
|
||||
blockchain: Blockchain,
|
||||
session: WalletConnectSession,
|
||||
walletConnectManager: WalletConnectManager,
|
||||
) {
|
||||
val walletManager = getWalletManager(session.wallet, blockchain, walletState).guard {
|
||||
val walletManager = getWalletManager(session.wallet, blockchain).guard {
|
||||
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
|
|
@ -445,20 +456,25 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleScanResponse(scanResponse: ScanResponse, session: WalletConnectSession, blockchain: Blockchain) {
|
||||
private suspend fun handleScanResponse(
|
||||
userWallet: UserWallet,
|
||||
session: WalletConnectSession,
|
||||
blockchain: Blockchain,
|
||||
) {
|
||||
val scanResponse = userWallet.scanResponse
|
||||
|
||||
if (!scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
|
||||
return
|
||||
}
|
||||
val walletState = store.state.walletState
|
||||
val updatedSession = session.copy(wallet = session.wallet.copy(blockchain = blockchain))
|
||||
store.dispatch(
|
||||
WalletConnectAction.SetNewSessionData(
|
||||
NewWcSessionData(session = updatedSession, scanResponse = scanResponse, blockchain = blockchain),
|
||||
NewWcSessionData(updatedSession, scanResponse, blockchain),
|
||||
),
|
||||
)
|
||||
val blockchains = if (blockchain.isEvm()) {
|
||||
getAvailableBlockchains(scanResponse.derivationStyleProvider, walletState)
|
||||
getAvailableEvmBlockchains(userWallet.walletId)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
|
@ -469,11 +485,7 @@ class WalletConnectMiddleware {
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun getWalletManager(
|
||||
wallet: WalletForSession,
|
||||
blockchain: Blockchain,
|
||||
walletState: WalletState,
|
||||
): WalletManager? {
|
||||
private suspend fun getWalletManager(wallet: WalletForSession, blockchain: Blockchain): WalletManager? {
|
||||
val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
|
|
@ -483,25 +495,15 @@ class WalletConnectMiddleware {
|
|||
val derivation = blockchainToMake.derivationPath(
|
||||
style = userWallet.scanResponse.derivationStyleProvider.getDerivationStyle(),
|
||||
)?.rawPath
|
||||
val walletFeatureToggles = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletFeatureToggles)
|
||||
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
walletManagerFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = derivation,
|
||||
)
|
||||
} else {
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = derivation,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
walletState.getWalletManager(blockchainNetwork)
|
||||
}
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
|
||||
return walletManagerFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = derivation,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isWalletConnectUri(uri: String): Boolean {
|
||||
|
|
|
|||
|
|
@ -73,9 +73,7 @@ data class WalletForSession(
|
|||
} else if (other.derivedPublicKey != null) return false
|
||||
if (derivationPath != other.derivationPath) return false
|
||||
if (isTestNet != other.isTestNet) return false
|
||||
if (blockchain != other.blockchain) return false
|
||||
|
||||
return true
|
||||
return blockchain == other.blockchain
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.core.navigation.NavigationAction
|
|||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.tap.features.details.featuretoggles.DetailsFeatureToggles
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -21,14 +20,11 @@ internal class AppSettingsFragment : ComposeFragment(), StoreSubscriber<DetailsS
|
|||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
lateinit var detailsFeatureToggles: DetailsFeatureToggles
|
||||
|
||||
@Inject
|
||||
lateinit var appCurrencyRepository: AppCurrencyRepository
|
||||
|
||||
private val viewModel by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
AppSettingsViewModel(store, detailsFeatureToggles, appCurrencyRepository)
|
||||
AppSettingsViewModel(store, appCurrencyRepository)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -11,16 +11,13 @@ import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
|||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.featuretoggles.DetailsFeatureToggles
|
||||
import com.tangem.tap.features.details.redux.AppSetting
|
||||
import com.tangem.tap.features.details.redux.AppSettingsState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
|
|
@ -32,7 +29,6 @@ import org.rekotlin.Store
|
|||
|
||||
internal class AppSettingsViewModel(
|
||||
private val store: Store<AppState>,
|
||||
private val detailsFeatureToggles: DetailsFeatureToggles,
|
||||
private val appCurrencyRepository: AppCurrencyRepository,
|
||||
) {
|
||||
|
||||
|
|
@ -45,9 +41,7 @@ internal class AppSettingsViewModel(
|
|||
private set
|
||||
|
||||
init {
|
||||
if (detailsFeatureToggles.isRedesignedAppCurrencySelectorEnabled) {
|
||||
bootstrapAppCurrencyUpdates()
|
||||
}
|
||||
bootstrapAppCurrencyUpdates()
|
||||
}
|
||||
|
||||
fun updateState(state: DetailsState) {
|
||||
|
|
@ -64,11 +58,12 @@ internal class AppSettingsViewModel(
|
|||
private fun buildItems(state: AppSettingsState): ImmutableList<AppSettingsScreenState.Item> {
|
||||
val items = buildList {
|
||||
if (state.needEnrollBiometrics) {
|
||||
Analytics.send(Settings.AppSettings.EnableBiometrics)
|
||||
itemsFactory.createEnrollBiometricsCard(onClick = ::enrollBiometrics).let(::add)
|
||||
}
|
||||
|
||||
itemsFactory.createSelectAppCurrencyButton(
|
||||
currentAppCurrencyName = state.selectedFiatCurrency.name,
|
||||
currentAppCurrencyName = state.selectedAppCurrency.name,
|
||||
onClick = ::showAppCurrencySelector,
|
||||
).let(::add)
|
||||
|
||||
|
|
@ -94,11 +89,9 @@ internal class AppSettingsViewModel(
|
|||
onCheckedChange = ::onFlipToHideBalanceToggled,
|
||||
).let(::add)
|
||||
|
||||
if (state.darkThemeSwitchEnabled) {
|
||||
itemsFactory.createSelectThemeModeButton(state.selectedThemeMode) {
|
||||
showThemeModeSelector(state.selectedThemeMode)
|
||||
}.let(::add)
|
||||
}
|
||||
itemsFactory.createSelectThemeModeButton(state.selectedThemeMode) {
|
||||
showThemeModeSelector(state.selectedThemeMode)
|
||||
}.let(::add)
|
||||
}
|
||||
|
||||
return items.toImmutableList()
|
||||
|
|
@ -109,13 +102,7 @@ internal class AppSettingsViewModel(
|
|||
}
|
||||
|
||||
private fun showAppCurrencySelector() {
|
||||
val action = if (detailsFeatureToggles.isRedesignedAppCurrencySelectorEnabled) {
|
||||
NavigationAction.NavigateTo(AppScreen.AppCurrencySelector)
|
||||
} else {
|
||||
WalletAction.AppCurrencyAction.ChooseAppCurrency
|
||||
}
|
||||
|
||||
store.dispatchOnMain(action)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.AppCurrencySelector))
|
||||
}
|
||||
|
||||
private fun showThemeModeSelector(selectedMode: AppThemeMode) {
|
||||
|
|
@ -179,6 +166,9 @@ internal class AppSettingsViewModel(
|
|||
}
|
||||
|
||||
private fun onFlipToHideBalanceToggled(enable: Boolean) {
|
||||
val param = AnalyticsParam.OnOffState(enable)
|
||||
Analytics.send(Settings.AppSettings.HideBalanceChanged(param))
|
||||
|
||||
store.dispatch(DetailsAction.AppSettings.ChangeBalanceHiding(hideBalance = enable))
|
||||
}
|
||||
|
||||
|
|
@ -192,8 +182,7 @@ internal class AppSettingsViewModel(
|
|||
.onEach {
|
||||
if (it.code == store.state.globalState.appCurrency.code) return@onEach
|
||||
|
||||
val fiatCurrency = with(it) { FiatCurrency(code, name, symbol) }
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.ChangeAppCurrency(fiatCurrency))
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.ChangeAppCurrency(it))
|
||||
}
|
||||
.launchIn(scope)
|
||||
.saveIn(appCurrencyUpdatesJobHolder)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.annotation.StringRes
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.domain.userwallets.Artwork
|
||||
import com.tangem.tap.features.details.redux.AccessCodeRecoveryState
|
||||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
import com.tangem.tap.features.details.ui.securitymode.toTitleRes
|
||||
|
|
@ -17,7 +16,6 @@ internal data class CardSettingsScreenState(
|
|||
val accessCodeRecoveryState: AccessCodeRecoveryState? = null,
|
||||
val onScanCardClick: () -> Unit,
|
||||
val onElementClick: (CardInfo) -> Unit,
|
||||
val cardImage: Artwork? = null,
|
||||
)
|
||||
|
||||
internal sealed class CardInfo(
|
||||
|
|
|
|||
|
|
@ -12,11 +12,9 @@ import com.tangem.domain.common.getTwinCardIdForUser
|
|||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.details.redux.CardSettingsState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -37,15 +35,12 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
is Either.Left -> {
|
||||
Timber.e(selectedWalletEither.value.toString())
|
||||
}
|
||||
is Either.Right -> {
|
||||
store.dispatchOnMain(WalletAction.UpdateUserWalletArtwork(selectedWalletEither.value.walletId))
|
||||
}
|
||||
is Either.Right -> Unit
|
||||
}
|
||||
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState &&
|
||||
oldState.walletState == newState.walletState
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +62,6 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
onScanCardClick = {
|
||||
store.dispatch(DetailsAction.ScanCard)
|
||||
},
|
||||
cardImage = store.state.walletState.cardImage,
|
||||
)
|
||||
} else {
|
||||
val cardId = if (state.card.isTangemTwins) {
|
||||
|
|
@ -105,7 +99,6 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
onElementClick = {
|
||||
handleClickingItem(it)
|
||||
},
|
||||
cardImage = store.state.walletState.cardImage,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import com.tangem.core.ui.screen.ComposeFragment
|
|||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.features.details.DarkThemeFeatureToggle
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -22,9 +21,6 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState
|
|||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
@Inject
|
||||
lateinit var darkThemeFeatureToggle: DarkThemeFeatureToggle
|
||||
|
||||
@Inject
|
||||
lateinit var walletsRepository: WalletsRepository
|
||||
|
||||
|
|
@ -32,7 +28,7 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
detailsViewModel = DetailsViewModel(store, darkThemeFeatureToggle, walletsRepository)
|
||||
detailsViewModel = DetailsViewModel(store, walletsRepository)
|
||||
Analytics.send(Settings.ScreenOpened())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ internal data class SocialNetworkLink(
|
|||
|
||||
internal sealed class EventError {
|
||||
object Empty : EventError()
|
||||
data class DemoReferralNotAvailable(val onErrorShow: () -> Unit) : EventError()
|
||||
}
|
||||
|
||||
sealed class SocialNetwork(val id: String, val iconRes: Int) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.details.ui.details
|
|||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
|
|
@ -12,19 +13,18 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.DarkThemeFeatureToggle
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
|
@ -37,11 +37,11 @@ import kotlinx.coroutines.flow.flowOn
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
// TODO: change to Android ViewModel [REDACTED_JIRA]
|
||||
internal class DetailsViewModel(
|
||||
private val store: Store<AppState>,
|
||||
private val darkThemeFeatureToggle: DarkThemeFeatureToggle,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
) {
|
||||
|
||||
|
|
@ -158,7 +158,15 @@ internal class DetailsViewModel(
|
|||
|
||||
private fun linkMoreCards() {
|
||||
Analytics.send(Settings.ButtonCreateBackup())
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.BackupWallet)
|
||||
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to backup wallet, no user wallet selected")
|
||||
return
|
||||
}
|
||||
val scanResponse = selectedUserWallet.scanResponse
|
||||
Analytics.addContext(scanResponse)
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
}
|
||||
|
||||
private fun scanAndSaveUserWallet() {
|
||||
|
|
@ -192,7 +200,6 @@ internal class DetailsViewModel(
|
|||
store.dispatchWithMain(
|
||||
DetailsAction.PrepareScreen(
|
||||
scanResponse = selectedUserWallet.scanResponse,
|
||||
darkThemeSwitchEnabled = darkThemeFeatureToggle.isDarkThemeEnabled,
|
||||
shouldSaveUserWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.otaliastudios.cameraview.CameraView
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PreviewBinder
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutQrScanningBinding
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
internal class QrScanFragment : Fragment(R.layout.layout_qr_scanning) {
|
||||
|
||||
private val binding: LayoutQrScanningBinding by viewBinding(LayoutQrScanningBinding::bind)
|
||||
|
||||
private val binder = PreviewBinder()
|
||||
|
||||
private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
|
||||
private var cameraExecutor: ExecutorService? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setFitSystemWindows(fit = true)
|
||||
activity?.onBackPressedDispatcher?.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
if (!permissionIsGranted()) requestPermission()
|
||||
|
||||
cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
cameraProviderFuture?.addListener(
|
||||
{
|
||||
val cameraProvider = cameraProviderFuture?.get()
|
||||
binder.bindPreview(
|
||||
context = requireContext(),
|
||||
binding = binding,
|
||||
lifecycleOwner = this,
|
||||
cameraProvider = requireNotNull(cameraProvider),
|
||||
cameraExecutor = requireNotNull(cameraExecutor),
|
||||
onScanned = { result ->
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
setFitSystemWindows(fit = false)
|
||||
if (result.isNotBlank()) {
|
||||
store.dispatch(WalletConnectAction.OpenSession(result))
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
ContextCompat.getMainExecutor(requireContext()),
|
||||
)
|
||||
|
||||
binding.overlay.post {
|
||||
binding.overlay.setViewFinder()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
setFitSystemWindows(fit = false)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
if (requestCode != CameraView.PERMISSION_REQUEST_CODE) return
|
||||
|
||||
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
store.dispatch(WalletConnectAction.NotifyCameraPermissionIsRequired)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
}
|
||||
|
||||
private fun permissionIsGranted(): Boolean {
|
||||
val cameraPermission = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
|
||||
return cameraPermission == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun requestPermission() {
|
||||
requestPermissions(arrayOf(Manifest.permission.CAMERA), CameraView.PERMISSION_REQUEST_CODE)
|
||||
}
|
||||
|
||||
private fun setFitSystemWindows(fit: Boolean) {
|
||||
activity?.window?.let {
|
||||
WindowCompat.setDecorFitsSystemWindows(it, fit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
|
|
@ -23,22 +24,25 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber<Wallet
|
|||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
private val viewModel = WalletConnectViewModel(store)
|
||||
private var screenState: MutableState<WalletConnectScreenState> =
|
||||
mutableStateOf(viewModel.updateState(store.state.walletConnectState))
|
||||
private val viewModel: WalletConnectViewModel by viewModels()
|
||||
|
||||
private var screenState: MutableState<WalletConnectScreenState>? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Analytics.send(WalletConnect.ScreenOpened())
|
||||
lifecycle.addObserver(viewModel)
|
||||
screenState = mutableStateOf(viewModel.updateState(store.state.walletConnectState))
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val state = screenState?.value ?: return
|
||||
WalletConnectScreen(
|
||||
modifier = modifier,
|
||||
state = screenState.value,
|
||||
state = state,
|
||||
onBackClick = {
|
||||
if (screenState.value.isLoading) {
|
||||
if (state.isLoading) {
|
||||
store.dispatch(
|
||||
WalletConnectAction.FailureEstablishingSession(
|
||||
store.state.walletConnectState.newSessionData?.session?.session,
|
||||
|
|
@ -66,6 +70,6 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber<Wallet
|
|||
|
||||
override fun newState(state: WalletConnectState) {
|
||||
if (activity == null || view == null) return
|
||||
screenState.value = viewModel.updateState(state)
|
||||
screenState?.value = viewModel.updateState(state)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,34 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import androidx.lifecycle.*
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.feature.qrscanning.SourceType
|
||||
import com.tangem.feature.qrscanning.usecase.ListenToQrScanningUseCase
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.rekotlin.Store
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class WalletConnectViewModel @Inject constructor(
|
||||
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
viewModelScope.launch {
|
||||
listenToQrScanningUseCase(SourceType.WALLET_CONNECT)
|
||||
.getOrElse { emptyFlow() }
|
||||
.flowWithLifecycle(owner.lifecycle, minActiveState = Lifecycle.State.CREATED)
|
||||
.collect { store.dispatch(WalletConnectAction.OpenSession(it)) }
|
||||
}
|
||||
}
|
||||
|
||||
internal class WalletConnectViewModel(private val store: Store<AppState>) {
|
||||
fun updateState(state: WalletConnectState): WalletConnectScreenState {
|
||||
Timber.d("WC2 Sessions: ${state.wc2Sessions}")
|
||||
val sessions = state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) } + state.wc2Sessions
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
|||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.feature.qrscanning.SourceType
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -19,7 +22,14 @@ object ClipboardOrScanQrDialog {
|
|||
store.dispatch(WalletConnectAction.OpenSession(wcUri))
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ ->
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.QrScan))
|
||||
store.dispatch(
|
||||
NavigationAction.NavigateTo(
|
||||
screen = AppScreen.QrScanning,
|
||||
bundle = bundleOf(
|
||||
QrScanningRouter.SOURCE_KEY to SourceType.WALLET_CONNECT,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Size
|
||||
import android.view.OrientationEventListener
|
||||
import android.view.Surface
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.tangem.tap.common.qrCodeScan.MLKitBarcodeAnalyzer
|
||||
import com.tangem.wallet.databinding.LayoutQrScanningBinding
|
||||
import java.util.concurrent.ExecutorService
|
||||
|
||||
internal class PreviewBinder {
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
fun bindPreview(
|
||||
context: Context,
|
||||
binding: LayoutQrScanningBinding,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
cameraProvider: ProcessCameraProvider,
|
||||
cameraExecutor: ExecutorService,
|
||||
onScanned: (String) -> Unit,
|
||||
) {
|
||||
cameraProvider.unbindAll()
|
||||
|
||||
val preview: Preview = Preview.Builder()
|
||||
.build()
|
||||
|
||||
val imageAnalysis = ImageAnalysis.Builder()
|
||||
.setTargetResolution(Size(binding.cameraPreview.width, binding.cameraPreview.height))
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build()
|
||||
|
||||
val orientationEventListener = object : OrientationEventListener(context) {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
val rotation: Int = when (orientation) {
|
||||
in 45..134 -> Surface.ROTATION_270
|
||||
in 135..224 -> Surface.ROTATION_180
|
||||
in 225..314 -> Surface.ROTATION_90
|
||||
else -> Surface.ROTATION_0
|
||||
}
|
||||
|
||||
imageAnalysis.targetRotation = rotation
|
||||
}
|
||||
}
|
||||
orientationEventListener.enable()
|
||||
|
||||
val analyzer: ImageAnalysis.Analyzer = MLKitBarcodeAnalyzer {
|
||||
imageAnalysis.clearAnalyzer()
|
||||
onScanned.invoke(it)
|
||||
}
|
||||
|
||||
cameraExecutor.let {
|
||||
imageAnalysis.setAnalyzer(it, analyzer)
|
||||
}
|
||||
|
||||
preview.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
|
||||
|
||||
val cameraSelector: CameraSelector = CameraSelector.Builder()
|
||||
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
|
||||
.build()
|
||||
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageAnalysis, preview)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class DisclaimerAction : Action {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ package com.tangem.tap.features.disclaimer.redux
|
|||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.DummyDisclaimer
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class DisclaimerState(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.transition.TransitionInflater
|
|||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
|
|
@ -17,7 +18,6 @@ import com.tangem.tap.features.addBackPressHandler
|
|||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentDisclaimerBinding
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
package com.tangem.tap.features.disclaimer.ui
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.webkit.*
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
|
||||
class DisclaimerWebViewClient : WebViewClient() {
|
||||
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
|||
|
||||
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
|
||||
|
||||
// private val learn2earnViewModel by activityViewModels<Learn2earnViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
store.dispatch(HomeAction.OnCreate)
|
||||
|
|
@ -44,11 +42,6 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
|||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
// sync adding story before screen creation
|
||||
// if (learn2earnViewModel.uiState.storyScreenState.isVisible) {
|
||||
// store.dispatch(HomeAction.InsertStory(position = 0, Stories.OneInchPromo))
|
||||
// homeState.value = store.state.homeState
|
||||
// }
|
||||
BackHandler(onBack = requireActivity()::finish)
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(color = Color.Transparent, darkIcons = false)
|
||||
|
|
@ -83,7 +76,6 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
|||
private fun ScreenContent() {
|
||||
StoriesScreen(
|
||||
homeState = homeState,
|
||||
onLearn2earnClick = {}, // learn2earnViewModel.uiState.storyScreenState.onClick,
|
||||
onScanButtonClick = {
|
||||
Analytics.send(IntroductionProcess.ButtonScanCard())
|
||||
store.dispatch(action = HomeAction.ReadCard(scope = requireActivity().lifecycleScope))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import android.content.Context
|
||||
import android.telephony.TelephonyManager
|
||||
import android.telephony.TelephonyManager.PHONE_TYPE_CDMA
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -13,39 +9,8 @@ interface RegionProvider {
|
|||
fun getRegion(): String?
|
||||
}
|
||||
|
||||
class RegionService(
|
||||
private val providers: List<RegionProvider>,
|
||||
) : RegionProvider {
|
||||
override fun getRegion(): String? {
|
||||
for (provider in providers) {
|
||||
val region = provider.getRegion()
|
||||
if (region != null) return region
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class TelephonyManagerRegionProvider(context: Context) : RegionProvider {
|
||||
|
||||
private val wContext: WeakReference<Context> = WeakReference(context)
|
||||
|
||||
override fun getRegion(): String? {
|
||||
val tm = wContext.get()?.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager ?: return null
|
||||
|
||||
val region = when (tm.phoneType) {
|
||||
PHONE_TYPE_CDMA -> {
|
||||
// Result may be unreliable
|
||||
tm.networkCountryIso
|
||||
}
|
||||
else -> tm.networkCountryIso
|
||||
}
|
||||
return region.ifEmpty { return null }
|
||||
}
|
||||
}
|
||||
|
||||
class LocaleRegionProvider : RegionProvider {
|
||||
override fun getRegion(): String = Locale.current.region
|
||||
}
|
||||
|
||||
const val RUSSIA_COUNTRY_CODE = "ru"
|
||||
const val BELARUS_COUNTRY_CODE = "by"
|
||||
const val RUSSIA_COUNTRY_CODE = "ru"
|
||||
|
|
@ -20,7 +20,6 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.learn2earn.presentation.ui.Learn2earnStoriesScreen
|
||||
import com.tangem.tap.features.home.compose.content.*
|
||||
import com.tangem.tap.features.home.compose.views.HomeButtons
|
||||
import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton
|
||||
|
|
@ -33,7 +32,6 @@ import kotlin.math.max
|
|||
@Composable
|
||||
fun StoriesScreen(
|
||||
homeState: MutableState<HomeState>,
|
||||
onLearn2earnClick: () -> Unit,
|
||||
onScanButtonClick: () -> Unit,
|
||||
onShopButtonClick: () -> Unit,
|
||||
onSearchTokensClick: () -> Unit,
|
||||
|
|
@ -65,7 +63,6 @@ fun StoriesScreen(
|
|||
isScanInProgress = homeState.value.scanInProgress,
|
||||
onGoToPreviousStory = goToPreviousStory,
|
||||
onGoToNextStory = goToNextStory,
|
||||
onLearn2earnClick = onLearn2earnClick,
|
||||
onSearchTokensClick = onSearchTokensClick,
|
||||
onScanButtonClick = onScanButtonClick,
|
||||
onShopButtonClick = onShopButtonClick,
|
||||
|
|
@ -152,7 +149,6 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M
|
|||
.align(Alignment.Start),
|
||||
)
|
||||
when (config.currentStory) {
|
||||
Stories.OneInchPromo -> Learn2earnStoriesScreen(config.onLearn2earnClick)
|
||||
Stories.TangemIntro -> FirstStoriesContent(
|
||||
isPaused = isPaused,
|
||||
duration = currentStoryDuration,
|
||||
|
|
@ -187,18 +183,12 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M
|
|||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = config.currentStory != Stories.OneInchPromo,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
HomeButtons(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
btnScanStateInProgress = config.isScanInProgress,
|
||||
onScanButtonClick = config.onScanButtonClick,
|
||||
onShopButtonClick = config.onShopButtonClick,
|
||||
)
|
||||
}
|
||||
HomeButtons(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
btnScanStateInProgress = config.isScanInProgress,
|
||||
onScanButtonClick = config.onScanButtonClick,
|
||||
onShopButtonClick = config.onShopButtonClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -210,7 +200,6 @@ private data class StoriesScreenContentConfig(
|
|||
val isScanInProgress: Boolean,
|
||||
val onGoToPreviousStory: () -> Unit = {},
|
||||
val onGoToNextStory: () -> Unit = {},
|
||||
val onLearn2earnClick: () -> Unit = {},
|
||||
val onSearchTokensClick: () -> Unit = {},
|
||||
val onScanButtonClick: () -> Unit = {},
|
||||
val onShopButtonClick: () -> Unit = {},
|
||||
|
|
@ -265,12 +254,6 @@ private class StoriesScreenContentConfigProvider : CollectionPreviewParameterPro
|
|||
currentStory = Stories.WalletForEveryone,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
StoriesScreenContentConfig(
|
||||
storiesSize = 6,
|
||||
currentStoryIndex = 6,
|
||||
currentStory = Stories.OneInchPromo,
|
||||
isScanInProgress = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -37,7 +37,6 @@ import timber.log.Timber
|
|||
object HomeMiddleware {
|
||||
val handler = homeMiddleware
|
||||
|
||||
const val BUY_WALLET_URL = "https://tangem.com/ru/resellers/"
|
||||
const val NEW_BUY_WALLET_URL = "https://buy.tangem.com/"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.compose.runtime.MutableState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
import com.tangem.tap.features.send.redux.states.ButtonState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import org.rekotlin.StateType
|
||||
import java.util.Locale
|
||||
|
||||
|
|
@ -17,9 +16,6 @@ data class HomeState(
|
|||
val firstStory: Stories
|
||||
get() = stories[0]
|
||||
|
||||
val btnScanStateInProgress: Boolean
|
||||
get() = btnScanState.progressState == ProgressState.Loading
|
||||
|
||||
fun stepOf(story: Stories): Int = stories.indexOf(story)
|
||||
|
||||
fun onCountryCodeUpdate(homeState: HomeState, countryCode: String) {
|
||||
|
|
@ -52,7 +48,6 @@ sealed class Stories(
|
|||
val duration: Int,
|
||||
val isNewWalletAvailable: MutableState<Boolean> = mutableStateOf(HomeState.isNewWalletAvailableInit()),
|
||||
) {
|
||||
object OneInchPromo : Stories(duration = 8000)
|
||||
object TangemIntro : Stories(duration = 6000)
|
||||
object RevolutionaryWallet : Stories(duration = 6000)
|
||||
object UltraSecureBackup : Stories(duration = 6000)
|
||||
|
|
|
|||
|
|
@ -15,15 +15,11 @@ class IntentProcessor {
|
|||
intentHandlers.add(handler)
|
||||
}
|
||||
|
||||
fun removeIntentHandler(handler: IntentHandler) {
|
||||
intentHandlers.remove(handler)
|
||||
}
|
||||
|
||||
fun removeAll() {
|
||||
intentHandlers.clear()
|
||||
}
|
||||
|
||||
suspend fun handleIntent(intent: Intent?) {
|
||||
fun handleIntent(intent: Intent?) {
|
||||
intentHandlers.forEach {
|
||||
it.handleIntent(intent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
package com.tangem.tap.features.intentHandler.handlers
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
|
||||
import com.tangem.tap.store
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -15,16 +9,19 @@ import com.tangem.tap.store
|
|||
class BuyCurrencyIntentHandler : IntentHandler {
|
||||
|
||||
override fun handleIntent(intent: Intent?): Boolean {
|
||||
val data = intent?.data ?: return false
|
||||
val currency = store.state.walletState.selectedCurrency ?: return false
|
||||
// FIXME: [REDACTED_JIRA]
|
||||
// val data = intent?.data ?: return false
|
||||
// val currency = store.state.walletState.selectedCurrency ?: return false
|
||||
//
|
||||
// val successUri = Uri.parse(ExchangeUrlBuilder.SUCCESS_URL)
|
||||
// return if (data.host == successUri.host && data.authority == successUri.authority) {
|
||||
// val currencyType = AnalyticsParam.CurrencyType.Currency(currency)
|
||||
// Analytics.send(TokenScreenAnalyticsEvent.Bought(currencyType.value))
|
||||
// true
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
|
||||
val successUri = Uri.parse(ExchangeUrlBuilder.SUCCESS_URL)
|
||||
return if (data.host == successUri.host && data.authority == successUri.authority) {
|
||||
val currencyType = AnalyticsParam.CurrencyType.Currency(currency)
|
||||
Analytics.send(TokenScreenAnalyticsEvent.Bought(currencyType.value))
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,7 @@
|
|||
package com.tangem.tap.features.intentHandler.handlers
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -13,33 +9,36 @@ import timber.log.Timber
|
|||
class SellCurrencyIntentHandler : IntentHandler {
|
||||
|
||||
override fun handleIntent(intent: Intent?): Boolean {
|
||||
return try {
|
||||
val intentData = intent?.data ?: return false
|
||||
val transactionID = intentData.getQueryParameter(TRANSACTION_ID_PARAM) ?: return false
|
||||
val currency = intentData.getQueryParameter(CURRENCY_CODE_PARAM) ?: return false
|
||||
val amount = intentData.getQueryParameter(CURRENCY_AMOUNT_PARAM) ?: return false
|
||||
val destinationAddress = intentData.getQueryParameter(DEPOSIT_WALLET_ADDRESS_PARAM) ?: return false
|
||||
// FIXME: [REDACTED_JIRA]
|
||||
// return try {
|
||||
// val intentData = intent?.data ?: return false
|
||||
// val transactionID = intentData.getQueryParameter(TRANSACTION_ID_PARAM) ?: return false
|
||||
// val currency = intentData.getQueryParameter(CURRENCY_CODE_PARAM) ?: return false
|
||||
// val amount = intentData.getQueryParameter(CURRENCY_AMOUNT_PARAM) ?: return false
|
||||
// val destinationAddress = intentData.getQueryParameter(DEPOSIT_WALLET_ADDRESS_PARAM) ?: return false
|
||||
//
|
||||
// Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
|
||||
// store.dispatchOnMain(
|
||||
// TradeCryptoAction.SendCrypto(
|
||||
// currencyId = currency,
|
||||
// amount = amount,
|
||||
// destinationAddress = destinationAddress,
|
||||
// transactionId = transactionID,
|
||||
// ),
|
||||
// )
|
||||
// true
|
||||
// } catch (exception: Exception) {
|
||||
// Timber.d("Not MoonPay URL")
|
||||
// false
|
||||
// }
|
||||
|
||||
Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
|
||||
store.dispatchOnMain(
|
||||
TradeCryptoAction.SendCrypto(
|
||||
currencyId = currency,
|
||||
amount = amount,
|
||||
destinationAddress = destinationAddress,
|
||||
transactionId = transactionID,
|
||||
),
|
||||
)
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
Timber.d("Not MoonPay URL")
|
||||
false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TRANSACTION_ID_PARAM = "transactionId"
|
||||
private const val CURRENCY_CODE_PARAM = "baseCurrencyCode"
|
||||
private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
|
||||
private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
|
||||
}
|
||||
// private companion object {
|
||||
// private const val TRANSACTION_ID_PARAM = "transactionId"
|
||||
// private const val CURRENCY_CODE_PARAM = "baseCurrencyCode"
|
||||
// private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
|
||||
// private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
|
||||
// }
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchShare
|
||||
import com.tangem.tap.common.extensions.dispatchToastNotification
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.toQrCode
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AddressInfoBottomSheetDialog(
|
||||
private val stateDialog: AppDialog.AddressInfoDialog,
|
||||
context: Context,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
var binding: DialogOnboardingAddressInfoBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DialogOnboardingAddressInfoBinding
|
||||
.inflate(LayoutInflater.from(context))
|
||||
setContentView(binding!!.root)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
setOnCancelListener {
|
||||
store.dispatchDialogHide()
|
||||
binding = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
Analytics.send(Token.Receive.ScreenOpened())
|
||||
showData(data = stateDialog.addressData)
|
||||
}
|
||||
|
||||
private fun showData(data: WalletDataModel.AddressData) = with(binding!!) {
|
||||
pseudoToolbar.imvClose.setOnClickListener {
|
||||
dismissWithAnimation = true
|
||||
cancel()
|
||||
}
|
||||
imvQrCode.setImageBitmap(data.shareUrl.toQrCode())
|
||||
tvAddress.text = data.address
|
||||
btnFlCopyAddress.setOnClickListener {
|
||||
Analytics.send(Token.Receive.ButtonCopyAddress())
|
||||
context.copyToClipboard(data.address)
|
||||
store.dispatchToastNotification(R.string.copy_toast_msg)
|
||||
}
|
||||
btnFlShare.setOnClickListener {
|
||||
Analytics.send(Token.Receive.ButtonShareAddress())
|
||||
store.dispatchShare(data.shareUrl)
|
||||
}
|
||||
val blockchain = stateDialog.currency.blockchain
|
||||
tvReceiveMessage.text = tvReceiveMessage.getString(
|
||||
id = R.string.address_qr_code_message_format,
|
||||
blockchain.fullName,
|
||||
blockchain.currency,
|
||||
blockchain.fullName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ object OnboardingHelper {
|
|||
}
|
||||
|
||||
fun whereToNavigate(scanResponse: ScanResponse): AppScreen {
|
||||
return when (scanResponse.productType) {
|
||||
return when (val type = scanResponse.productType) {
|
||||
ProductType.Note -> AppScreen.OnboardingNote
|
||||
ProductType.Wallet,
|
||||
ProductType.Wallet2,
|
||||
|
|
@ -65,9 +65,9 @@ object OnboardingHelper {
|
|||
AppScreen.OnboardingOther
|
||||
}
|
||||
ProductType.Twins -> AppScreen.OnboardingTwins
|
||||
ProductType.Start2Coin -> throw java.lang.UnsupportedOperationException(
|
||||
"Onboarding for Start2Coin cards is not supported",
|
||||
)
|
||||
ProductType.Start2Coin,
|
||||
ProductType.Visa,
|
||||
-> throw UnsupportedOperationException("Onboarding for ${type.name} cards is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.CardVerifyAndGetInfo
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.isPositive
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.hasPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.domain.model.hasPendingTransactions
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -25,11 +25,10 @@ import java.math.BigDecimal
|
|||
*/
|
||||
class OnboardingManager(
|
||||
var scanResponse: ScanResponse,
|
||||
val usedCardsPrefStorage: UsedCardsPrefStorage,
|
||||
private val usedCardsPrefStorage: UsedCardsPrefStorage,
|
||||
) {
|
||||
|
||||
var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null
|
||||
private set
|
||||
private var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null
|
||||
|
||||
suspend fun loadArtworkUrl(): String {
|
||||
val cardInfo = cardInfo
|
||||
|
|
@ -85,10 +84,6 @@ class OnboardingManager(
|
|||
usedCardsPrefStorage.activationFinished(cardId)
|
||||
}
|
||||
|
||||
fun isActivationFinished(cardId: String): Boolean {
|
||||
return usedCardsPrefStorage.isActivationFinished(cardId)
|
||||
}
|
||||
|
||||
fun isActivationStarted(cardId: String): Boolean {
|
||||
return usedCardsPrefStorage.isActivationStarted(cardId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,20 +8,18 @@ import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
|||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
|
@ -104,7 +102,6 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
|
|||
val updatedResponse = scanResponse.copy(card = result.data.card)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.activationStarted(updatedResponse.card.cardId)
|
||||
store.state.globalState.topUpController?.registerEmptyWallet(updatedResponse)
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet))
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
|
|
@ -153,8 +150,6 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
|
|||
is OnboardingNoteAction.Balance.Set -> {
|
||||
if (action.balance.balanceIsToppedUp()) {
|
||||
OnboardingHelper.sendToppedUpEvent(scanResponse)
|
||||
|
||||
store.state.globalState.topUpController?.send(scanResponse, AnalyticsParam.CardBalanceState.Full)
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done))
|
||||
}
|
||||
}
|
||||
|
|
@ -178,8 +173,8 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
|
|||
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
|
||||
|
||||
if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) {
|
||||
val dialogData = WalletDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
|
||||
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog(dialogData))
|
||||
val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData))
|
||||
} else {
|
||||
store.dispatchOpenUrl(topUpUrl)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,16 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards.redux
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userTokensRepository
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -89,40 +83,6 @@ private fun handleOtherCardsAction(action: Action) {
|
|||
val updatedCard = updatedResponse.card
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.activationStarted(updatedCard.cardId)
|
||||
store.state.globalState.topUpController?.registerEmptyWallet(updatedResponse)
|
||||
|
||||
val primaryBlockchain = updatedResponse.cardTypesResolver.getBlockchain()
|
||||
val blockchainNetworks = if (primaryBlockchain != Blockchain.Unknown) {
|
||||
val primaryToken = updatedResponse.cardTypesResolver.getPrimaryToken()
|
||||
val blockchainNetwork =
|
||||
BlockchainNetwork(
|
||||
blockchain = primaryBlockchain,
|
||||
derivationStyleProvider = updatedResponse.derivationStyleProvider,
|
||||
)
|
||||
.updateTokens(
|
||||
listOfNotNull(primaryToken),
|
||||
)
|
||||
listOf(blockchainNetwork)
|
||||
} else {
|
||||
listOf(
|
||||
BlockchainNetwork(
|
||||
blockchain = Blockchain.Bitcoin,
|
||||
derivationStyleProvider = updatedResponse.derivationStyleProvider,
|
||||
),
|
||||
BlockchainNetwork(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
derivationStyleProvider = updatedResponse.derivationStyleProvider,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
// TODO: Use new repo [REDACTED_JIRA]
|
||||
userTokensRepository.saveUserTokens(
|
||||
card = result.data.card,
|
||||
tokens = blockchainNetworks.toCurrencies(),
|
||||
)
|
||||
}
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done))
|
||||
|
|
|
|||
|
|
@ -14,20 +14,18 @@ import com.tangem.domain.userwallets.UserWalletIdBuilder
|
|||
import com.tangem.domain.wallets.legacy.isLockedSync
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
|
|
@ -202,7 +200,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
is Result.Success -> {
|
||||
Analytics.send(Onboarding.Twins.SetupFinished())
|
||||
updateScanResponse(result.data)
|
||||
store.state.globalState.topUpController?.registerEmptyWallet(result.data)
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
withMainContext {
|
||||
|
|
@ -261,8 +258,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
is TwinCardsAction.Balance.Set -> {
|
||||
if (action.balance.balanceIsToppedUp()) {
|
||||
OnboardingHelper.sendToppedUpEvent(getScanResponse())
|
||||
|
||||
store.state.globalState.topUpController?.send(getScanResponse(), AnalyticsParam.CardBalanceState.Full)
|
||||
store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done))
|
||||
}
|
||||
}
|
||||
|
|
@ -285,8 +280,8 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
|
||||
|
||||
if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) {
|
||||
val dialogData = WalletDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
|
||||
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog(dialogData))
|
||||
val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData))
|
||||
} else {
|
||||
store.dispatchOpenUrl(topUpUrl)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
tvHeader.setText(R.string.common_warning)
|
||||
tvBody.setText(R.string.twins_recreate_warning)
|
||||
|
||||
chbUnderstand.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
chbUnderstand.setOnCheckedChangeListener { _, isChecked ->
|
||||
store.dispatch(TwinCardsAction.SetUserUnderstand(isChecked))
|
||||
}
|
||||
btnMainAction.isEnabled = state.userWasUnderstandIfWalletRecreate
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
|
|
@ -11,11 +10,9 @@ import com.tangem.common.services.Result
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.userwallets.Artwork
|
||||
|
|
@ -36,7 +33,6 @@ import com.tangem.tap.features.demo.DemoHelper
|
|||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -142,26 +138,7 @@ private fun handleWalletAction(action: Action) {
|
|||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
store.state.globalState.topUpController?.registerEmptyWallet(updatedResponse)
|
||||
|
||||
val blockchainNetworks = if (DemoHelper.isDemoCardId(result.data.card.cardId)) {
|
||||
DemoHelper.config.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}.map { blockchain ->
|
||||
BlockchainNetwork(
|
||||
blockchain = blockchain,
|
||||
derivationStyleProvider = updatedResponse.derivationStyleProvider,
|
||||
)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
// TODO: Use new repo [REDACTED_JIRA]
|
||||
userTokensRepository.saveUserTokens(
|
||||
card = result.data.card,
|
||||
tokens = blockchainNetworks.toCurrencies(),
|
||||
)
|
||||
}
|
||||
startCardActivation(updatedResponse)
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ class TestBackupAnimation(
|
|||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun setStep(step: Int, onStepUpdate: (Int) -> Unit = {}) {
|
||||
private fun setStep(step: Int, onStepUpdate: (Int) -> Unit = {}) {
|
||||
steps = step
|
||||
when (steps) {
|
||||
0 -> setupCreateWalletState()
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ class OnboardingWalletFragment :
|
|||
}
|
||||
|
||||
private fun initCardsWidget(leapfrogWidget: LeapfrogWidget, deviceScaleFactor: Float, isTest: Boolean = false) {
|
||||
cardsWidget = WalletCardsWidget(leapfrogWidget, deviceScaleFactor) { 200f * deviceScaleFactor }
|
||||
cardsWidget = WalletCardsWidget(leapfrogWidget, deviceScaleFactor)
|
||||
animator = if (isTest) {
|
||||
TestBackupAnimation(WalletBackupAnimator(cardsWidget), binding)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -7,19 +7,16 @@ import android.view.View
|
|||
import android.widget.ImageView
|
||||
import androidx.core.animation.doOnEnd
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapView
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapViewState
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
|
||||
class WalletCardsWidget(
|
||||
val leapfrogWidget: LeapfrogWidget,
|
||||
private val deviceScaleFactor: Float = 1f,
|
||||
val getTopOfAnchorViewForActivateState: () -> Float,
|
||||
) {
|
||||
|
||||
private val animDuration: Long = 400
|
||||
|
||||
var currentState: WidgetState? = null
|
||||
private set
|
||||
private var currentState: WidgetState? = null
|
||||
|
||||
fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
if (currentState == WidgetState.WELCOME) return
|
||||
|
|
@ -239,15 +236,5 @@ private data class CardProperties(
|
|||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun from(leapViewState: LeapViewState): CardProperties {
|
||||
val leapViewProperties = leapViewState.properties
|
||||
|
||||
return CardProperties(
|
||||
yTranslation = leapViewProperties.yTranslation,
|
||||
elevation = leapViewProperties.elevationEnd,
|
||||
scale = leapViewProperties.scale,
|
||||
)
|
||||
}
|
||||
}
|
||||
companion object
|
||||
}
|
||||
|
|
@ -21,7 +21,6 @@ import com.tangem.tap.common.extensions.dispatchWithMain
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -119,16 +118,8 @@ internal class SaveWalletMiddleware {
|
|||
)
|
||||
}
|
||||
|
||||
val savedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("User wallet is not saved")
|
||||
return@launch
|
||||
}
|
||||
store.dispatchWithMain(SaveWalletAction.Save.Success)
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true))
|
||||
store.dispatchWithMain(
|
||||
action = WalletAction.MultiWallet.CheckForBackupWarning(savedUserWallet.scanResponse.card),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import com.tangem.blockchain.common.*
|
|||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
|
|
@ -39,20 +38,15 @@ import com.tangem.tap.features.demo.isDemoCard
|
|||
import com.tangem.tap.features.send.redux.*
|
||||
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
|
||||
import com.tangem.tap.features.send.redux.states.*
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
import java.util.EnumSet
|
||||
|
||||
/**
|
||||
|
|
@ -277,9 +271,6 @@ private fun sendTransaction(
|
|||
Analytics.sendSelectedCurrencyEvent(mainCurrencyType)
|
||||
dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
scope.launch(Dispatchers.IO) {
|
||||
updateAfterTransaction(walletManager)
|
||||
}
|
||||
}
|
||||
is SimpleResult.Failure -> {
|
||||
updateFeedbackManagerInfo(
|
||||
|
|
@ -418,32 +409,4 @@ private fun updateWarnings(dispatch: (Action) -> Unit) {
|
|||
|
||||
val warnings = warningsManager.getWarnings(WarningMessage.Location.SendScreen, listOf(blockchain))
|
||||
dispatch(SendAction.Warnings.Set(warnings))
|
||||
}
|
||||
|
||||
private suspend fun updateAfterTransaction(walletManager: WalletManager) {
|
||||
val walletFeatureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
|
||||
if (!walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
updateWalletsLegacy(walletManager)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateWalletsLegacy(walletManager: WalletManager) {
|
||||
updateWallet(walletManager)
|
||||
delay(timeMillis = 11000) // more than 10000 to avoid throttling
|
||||
updateWallet(walletManager)
|
||||
}
|
||||
|
||||
private suspend fun updateWallet(walletManager: WalletManager) {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to update wallet, no user wallet selected")
|
||||
return
|
||||
}
|
||||
val wallet = walletManager.wallet
|
||||
walletCurrenciesManager.update(
|
||||
userWallet = selectedUserWallet,
|
||||
currency = Currency.Blockchain(
|
||||
blockchain = wallet.blockchain,
|
||||
derivationPath = wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -2,13 +2,13 @@ package com.tangem.tap.features.send.redux.reducers
|
|||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.send.redux.FeeAction
|
||||
import com.tangem.tap.features.send.redux.FeeActionUi
|
||||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.states.FeeState
|
||||
import com.tangem.tap.features.send.redux.states.FeeType
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -2,23 +2,12 @@ package com.tangem.tap.features.send.redux.reducers
|
|||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.tap.common.extensions.scaleToFiat
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt
|
||||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.states.AmountState
|
||||
import com.tangem.tap.features.send.redux.states.FeeState
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptCrypto
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptFiat
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptLayoutType
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptState
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptSymbols
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptTokenCrypto
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptTokenFiat
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
import com.tangem.tap.features.wallet.redux.utils.CAN_BE_LOWER_SIGN
|
||||
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.tap.features.send.redux.states.*
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -143,9 +132,9 @@ class ReceiptReducer : SendInternalReducer {
|
|||
)
|
||||
} else {
|
||||
ReceiptTokenFiat(
|
||||
amountFiat = UNKNOWN_AMOUNT_SIGN,
|
||||
feeFiat = UNKNOWN_AMOUNT_SIGN,
|
||||
totalFiat = UNKNOWN_AMOUNT_SIGN,
|
||||
amountFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
|
||||
feeFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
|
||||
totalFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
|
||||
willSentToken = tokensToSend.stripZeroPlainString(),
|
||||
willSentFeeCoin = feeCoin.stripZeroPlainString(),
|
||||
symbols = symbols,
|
||||
|
|
@ -175,7 +164,7 @@ class ReceiptReducer : SendInternalReducer {
|
|||
ReceiptTokenCrypto(
|
||||
amountToken = tokensToSend.stripZeroPlainString(),
|
||||
feeCoin = feeCoin.stripZeroPlainString().addPrecisionSign(),
|
||||
totalFiat = UNKNOWN_AMOUNT_SIGN,
|
||||
totalFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
|
||||
symbols = symbols,
|
||||
)
|
||||
}
|
||||
|
|
@ -216,7 +205,7 @@ class ReceiptReducer : SendInternalReducer {
|
|||
sendState.tokenConverter!!.toFiatWithPrecision(value).stripZeroPlainString()
|
||||
}
|
||||
else -> {
|
||||
UNKNOWN_AMOUNT_SIGN
|
||||
BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -225,4 +214,8 @@ class ReceiptReducer : SendInternalReducer {
|
|||
val result = if (feeState.feeIsApproximate) "$CAN_BE_LOWER_SIGN $this" else this
|
||||
return result.trim()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CAN_BE_LOWER_SIGN = "<"
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.features.send.redux.states
|
|||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import com.tangem.blockchain.common.AmountType
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.tap.common.CurrencyConverter
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
import com.tangem.tap.common.text.DecimalDigitsInputFilter
|
||||
import com.tangem.tap.domain.TapError
|
||||
|
|
@ -128,7 +128,7 @@ data class AmountState(
|
|||
val typeOfAmount: AmountType = AmountType.Coin,
|
||||
val viewAmountValue: InputViewValue = InputViewValue(BigDecimal.ZERO.toPlainString()),
|
||||
val viewBalanceValue: String = BigDecimal.ZERO.toPlainString(),
|
||||
val mainCurrency: MainCurrency = MainCurrency(MainCurrencyType.FIAT, FiatCurrency.Default.code),
|
||||
val mainCurrency: MainCurrency = MainCurrency(MainCurrencyType.FIAT, AppCurrency.Default.code),
|
||||
val amountToSendCrypto: BigDecimal = BigDecimal.ZERO,
|
||||
val balanceCrypto: BigDecimal = BigDecimal.ZERO,
|
||||
val hideBalance: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -3,30 +3,38 @@
|
|||
package com.tangem.tap.features.send.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.method.DigitsKeyListener
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import arrow.core.getOrElse
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.tangem.Message
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.feature.qrscanning.SourceType
|
||||
import com.tangem.feature.qrscanning.usecase.ListenToQrScanningUseCase
|
||||
import com.tangem.sdk.extensions.hideSoftKeyboard
|
||||
import com.tangem.tap.common.KeyboardObserver
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
import com.tangem.tap.common.extensions.setOnImeActionListener
|
||||
import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.snackBar.MaxAmountSnackbar
|
||||
import com.tangem.tap.common.text.truncateMiddleWith
|
||||
|
|
@ -40,8 +48,8 @@ import com.tangem.tap.features.send.redux.AmountActionUi.*
|
|||
import com.tangem.tap.features.send.redux.FeeActionUi.*
|
||||
import com.tangem.tap.features.send.redux.states.FeeType
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -49,8 +57,11 @@ import com.tangem.wallet.databinding.FragmentSendBinding
|
|||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.text.DecimalFormatSymbols
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val EDIT_TEXT_INPUT_DEBOUNCE = 400L
|
||||
|
||||
|
|
@ -73,11 +84,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
|
||||
val binding: FragmentSendBinding by viewBinding(FragmentSendBinding::bind)
|
||||
|
||||
@Inject
|
||||
lateinit var listenToQrScanningUseCase: ListenToQrScanningUseCase
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycle.addObserver(viewModel)
|
||||
sendSubscriber.initViewModel(viewModel)
|
||||
Analytics.send(Token.Send.ScreenOpened())
|
||||
listenToQrCode()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
|
@ -145,13 +160,34 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
}
|
||||
imvQrCode.setOnClickListener {
|
||||
Analytics.send(Token.Send.ButtonQRCode())
|
||||
startActivityForResult(
|
||||
Intent(requireContext(), ScanQrCodeActivity::class.java),
|
||||
ScanQrCodeActivity.SCAN_QR_REQUEST_CODE,
|
||||
|
||||
store.dispatchOnMain(
|
||||
NavigationAction.NavigateTo(
|
||||
screen = AppScreen.QrScanning,
|
||||
bundle = bundleOf(
|
||||
QrScanningRouter.SOURCE_KEY to SourceType.SEND,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun listenToQrCode() {
|
||||
lifecycleScope.launch {
|
||||
listenToQrScanningUseCase(SourceType.SEND)
|
||||
.getOrElse { emptyFlow() }
|
||||
.flowWithLifecycle(this@SendFragment.lifecycle, minActiveState = Lifecycle.State.CREATED)
|
||||
.collect {
|
||||
delay(200)
|
||||
|
||||
// Delayed launch is needed in order for the UI to be drawn and to process the sent events.
|
||||
// If do not use the delay, then etAmount error field is not displayed when
|
||||
// inserting an incorrect amount by shareUri
|
||||
onCodeScanned(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTransactionExtrasLayout() = with(binding.lSendAddress) {
|
||||
// TODO: [REDACTED_TASK_KEY]
|
||||
etXlmMemo.inputtedTextAsFlow()
|
||||
|
|
@ -200,27 +236,16 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
.launchIn(mainScope)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode != ScanQrCodeActivity.SCAN_QR_REQUEST_CODE) return
|
||||
|
||||
val scannedCode = data?.getStringExtra(ScanQrCodeActivity.SCAN_RESULT) ?: ""
|
||||
private fun onCodeScanned(scannedCode: String) {
|
||||
if (scannedCode.isEmpty()) return
|
||||
|
||||
// Delayed launch is needed in order for the UI to be drawn and to process the sent events.
|
||||
// If do not use the delay, then etAmount error field is not displayed when
|
||||
// inserting an incorrect amount by shareUri
|
||||
binding.lSendAddress.imvQrCode.postDelayed(
|
||||
{
|
||||
store.dispatch(
|
||||
PasteAddress(
|
||||
data = scannedCode,
|
||||
sourceType = Token.Send.AddressEntered.SourceType.QRCode,
|
||||
),
|
||||
)
|
||||
store.dispatch(TruncateOrRestore(!binding.lSendAddress.etAddress.isFocused))
|
||||
},
|
||||
200,
|
||||
store.dispatch(
|
||||
PasteAddress(
|
||||
data = scannedCode,
|
||||
sourceType = Token.Send.AddressEntered.SourceType.QRCode,
|
||||
),
|
||||
)
|
||||
store.dispatch(TruncateOrRestore(!binding.lSendAddress.etAddress.isFocused))
|
||||
}
|
||||
|
||||
private fun setupAmountLayout() {
|
||||
|
|
@ -328,7 +353,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
|
||||
private fun restoreMainCurrency(): MainCurrencyType {
|
||||
val sp = requireContext().getSharedPreferences("SendScreen", Context.MODE_PRIVATE)
|
||||
val mainCurrency = sp.getString("mainCurrency", FiatCurrency.Default.code)
|
||||
val mainCurrency = sp.getString("mainCurrency", AppCurrency.Default.code)
|
||||
return MainCurrencyType.values()
|
||||
.firstOrNull { it.name.equals(mainCurrency!!, ignoreCase = true) }
|
||||
?: MainCurrencyType.CRYPTO
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
package com.tangem.tap.features.send.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
|
@ -13,14 +13,13 @@ import com.tangem.tap.common.analytics.events.MainScreen
|
|||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutWarningCardActionBinding
|
||||
import timber.log.Timber
|
||||
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
// TODO: Delete with SendFeatureToggles
|
||||
@Deprecated(message = "Used only in old send screen")
|
||||
class WarningMessagesAdapter : ListAdapter<WarningMessage, WarningMessageVH>(DiffUtilCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WarningMessageVH {
|
||||
|
|
@ -81,27 +80,15 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
|
|||
private fun setupControlButtons(warning: WarningMessage) = when (warning.type) {
|
||||
WarningMessage.Type.Permanent, WarningMessage.Type.TestCard -> {
|
||||
binding.groupControlsTemporary.hide()
|
||||
binding.groupControlsRating.hide()
|
||||
binding.btnClose.hide()
|
||||
}
|
||||
WarningMessage.Type.Temporary -> {
|
||||
binding.groupControlsRating.hide()
|
||||
binding.groupControlsTemporary.show()
|
||||
binding.btnClose.hide()
|
||||
|
||||
val buttonAction =
|
||||
when (warning.titleResId) {
|
||||
// R.string.warning_important_security_info -> {
|
||||
// View.OnClickListener {
|
||||
// store.dispatch(WalletAction.DialogAction.SignedHashesMultiWalletDialog)
|
||||
// }
|
||||
// }
|
||||
else -> {
|
||||
View.OnClickListener {
|
||||
store.dispatch(GlobalAction.HideWarningMessage(warning))
|
||||
}
|
||||
}
|
||||
}
|
||||
val buttonAction = View.OnClickListener {
|
||||
store.dispatch(GlobalAction.HideWarningMessage(warning))
|
||||
}
|
||||
val buttonTitle = binding.root.getString(
|
||||
warning.buttonTextId ?: R.string.how_to_got_it_button,
|
||||
)
|
||||
|
|
@ -110,37 +97,19 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
|
|||
}
|
||||
WarningMessage.Type.AppRating -> {
|
||||
binding.groupControlsTemporary.hide()
|
||||
binding.groupControlsRating.show()
|
||||
binding.btnClose.show()
|
||||
binding.btnClose.setOnClickListener {
|
||||
Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Closed))
|
||||
store.dispatch(GlobalAction.HideWarningMessage(warning))
|
||||
store.dispatch(WalletAction.Warnings.AppRating.RemindLater)
|
||||
}
|
||||
// binding.btnCanBeBetter.setOnClickListener {
|
||||
// Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Disliked))
|
||||
// store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
|
||||
// store.dispatch(GlobalAction.HideWarningMessage(warning))
|
||||
// store.dispatch(GlobalAction.SendEmail(RateCanBeBetterEmail()))
|
||||
// }
|
||||
binding.btnReallyCool.setOnClickListener {
|
||||
val activity = binding.root.context.getActivity() ?: return@setOnClickListener
|
||||
|
||||
Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Liked))
|
||||
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
|
||||
val reviewManager = ReviewManagerFactory.create(activity)
|
||||
val task = reviewManager.requestReviewFlow()
|
||||
task.addOnCompleteListener {
|
||||
if (it.isSuccessful) {
|
||||
val reviewFlow = reviewManager.launchReviewFlow(activity, it.result)
|
||||
reviewFlow.addOnCompleteListener {
|
||||
if (it.isSuccessful) {
|
||||
// send review was succeed
|
||||
} else {
|
||||
// send fails
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!it.isSuccessful) {
|
||||
Timber.e(task.exception)
|
||||
}
|
||||
}.addOnFailureListener {
|
||||
|
|
@ -7,6 +7,8 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.core.text.bold
|
||||
import com.tangem.common.extensions.remove
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.getMessageString
|
||||
import com.tangem.tap.common.text.DecimalDigitsInputFilter
|
||||
|
|
@ -19,11 +21,8 @@ import com.tangem.tap.features.send.redux.states.*
|
|||
import com.tangem.tap.features.send.ui.FeeUiHelper
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.send.ui.SendViewModel
|
||||
import com.tangem.tap.features.send.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.features.send.ui.dialogs.*
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.utils.ROUGH_SIGN
|
||||
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
|
|
@ -316,7 +315,7 @@ internal class SendStateSubscriber(
|
|||
fun getString(id: Int, vararg formatStrings: String): String = mainLayout.getString(id, *formatStrings)
|
||||
|
||||
fun roughOrEmpty(value: String): String {
|
||||
return if (value == UNKNOWN_AMOUNT_SIGN) value else "$ROUGH_SIGN $value"
|
||||
return if (value == BigDecimalFormatter.EMPTY_BALANCE_SIGN) value else "$ROUGH_SIGN $value"
|
||||
}
|
||||
|
||||
when (feeProgressState) {
|
||||
|
|
@ -362,7 +361,7 @@ internal class SendStateSubscriber(
|
|||
llTotalContainer.tvTotalValue.update("${receipt.totalCrypto} ${receipt.symbols.crypto}")
|
||||
}
|
||||
|
||||
if (receipt.willSentFiat == UNKNOWN_AMOUNT_SIGN) {
|
||||
if (receipt.willSentFiat == BigDecimalFormatter.EMPTY_BALANCE_SIGN) {
|
||||
llTotalContainer.tvWillBeSentValue.hide()
|
||||
} else {
|
||||
llTotalContainer.tvWillBeSentValue.show()
|
||||
|
|
@ -414,4 +413,8 @@ internal class SendStateSubscriber(
|
|||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ROUGH_SIGN = "≈"
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.features.tokens.impl.data
|
|||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
|
|
@ -50,7 +51,7 @@ internal class TangemApiTokensPagingSource(
|
|||
searchText = searchText,
|
||||
offset = page * params.loadSize,
|
||||
limit = params.loadSize,
|
||||
)
|
||||
).getOrThrow()
|
||||
}.fold(
|
||||
onSuccess = { response ->
|
||||
LoadResult.Page(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.tokens.impl.data.converters
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.domain.tokens.getIconUrl
|
||||
import com.tangem.tap.features.tokens.impl.domain.models.Token
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
|
|
@ -14,6 +13,8 @@ import com.tangem.utils.converter.Converter
|
|||
*/
|
||||
internal object CoinsResponseConverter : Converter<CoinsResponse, List<Token>> {
|
||||
|
||||
private const val DEFAULT_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/"
|
||||
|
||||
override fun convert(value: CoinsResponse): List<Token> {
|
||||
return value.coins.map { token ->
|
||||
Token(
|
||||
|
|
@ -35,4 +36,8 @@ internal object CoinsResponseConverter : Converter<CoinsResponse, List<Token>> {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getIconUrl(id: String, imageHost: String? = null): String {
|
||||
return "${imageHost ?: DEFAULT_IMAGE_HOST}large/$id.png"
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.tokens.impl.data.converters
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.datasource.local.testnet.models.TestnetTokensConfig
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.domain.tokens.getIconUrl
|
||||
import com.tangem.tap.features.tokens.impl.domain.models.Token
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
|
|
@ -20,7 +19,7 @@ internal object TestnetTokensConfigConverter : Converter<TestnetTokensConfig, Li
|
|||
id = token.id,
|
||||
name = token.name,
|
||||
symbol = token.symbol,
|
||||
iconUrl = getIconUrl(id = token.id, imageHost = null),
|
||||
iconUrl = CoinsResponseConverter.getIconUrl(token.id),
|
||||
networks = token.networks?.mapNotNull { network ->
|
||||
val blockchain = Blockchain.fromNetworkId(network.id) ?: return@mapNotNull null
|
||||
|
||||
|
|
@ -28,7 +27,7 @@ internal object TestnetTokensConfigConverter : Converter<TestnetTokensConfig, Li
|
|||
id = network.id,
|
||||
blockchain = blockchain,
|
||||
address = network.address,
|
||||
iconUrl = getIconUrl(id = network.id, imageHost = null),
|
||||
iconUrl = CoinsResponseConverter.getIconUrl(network.id),
|
||||
decimalCount = network.decimalCount,
|
||||
)
|
||||
}.orEmpty(),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ internal interface TokensListRepository {
|
|||
* Get available tokens list
|
||||
*
|
||||
* @param searchText search text
|
||||
*
|
||||
* @throws com.tangem.datasource.api.common.response.ApiResponseError
|
||||
*/
|
||||
fun getAvailableTokens(searchText: String?): Flow<PagingData<Token>>
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import com.tangem.core.navigation.NavigationAction
|
|||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
|
|
@ -32,13 +31,13 @@ internal class DefaultTokensListRouter : TokensListRouter {
|
|||
|
||||
override fun openUnableHideMainTokenAlert(tokenName: String, tokenSymbol: String) {
|
||||
store.dispatchDialogShow(
|
||||
dialog = WalletDialog.TokensAreLinkedDialog(currencyTitle = tokenName, currencySymbol = tokenSymbol),
|
||||
dialog = AppDialog.TokensAreLinkedDialog(currencyTitle = tokenName, currencySymbol = tokenSymbol),
|
||||
)
|
||||
}
|
||||
|
||||
override fun openRemoveWalletAlert(tokenName: String, onOkClick: () -> Unit) {
|
||||
store.dispatchDialogShow(
|
||||
dialog = WalletDialog.RemoveWalletDialog(currencyTitle = tokenName, onOk = onOkClick),
|
||||
dialog = AppDialog.RemoveWalletDialog(currencyTitle = tokenName, onOk = onOkClick),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.tokens.impl.presentation.viewmodels
|
|||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
|
|
@ -12,9 +11,6 @@ import com.tangem.domain.tokens.TokensAction
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
import kotlin.properties.Delegates
|
||||
|
|
@ -22,12 +18,10 @@ import kotlin.properties.Delegates
|
|||
/**
|
||||
* Class that divide a new and legacy logic when user uses tokens list screen
|
||||
*
|
||||
* @property walletFeatureToggles wallet feature toggles
|
||||
* @property getSelectedWalletSyncUseCase use case that returns selected wallet
|
||||
* @property getCurrenciesUseCase use case that returns crypto currencies of a specified wallet
|
||||
*/
|
||||
internal class TokensListMigration(
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val getCurrenciesUseCase: GetCryptoCurrenciesUseCase,
|
||||
) {
|
||||
|
|
@ -39,14 +33,6 @@ internal class TokensListMigration(
|
|||
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
|
||||
|
||||
suspend fun getCurrentCryptoCurrencies(): TokensListCryptoCurrencies {
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
getNewCryptoCurrencies()
|
||||
} else {
|
||||
getLegacyCryptoCurrencies()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNewCryptoCurrencies(): TokensListCryptoCurrencies {
|
||||
return when (val selectedWalletEither = getSelectedWalletSyncUseCase()) {
|
||||
is Either.Left -> {
|
||||
Timber.e(selectedWalletEither.value.toString())
|
||||
|
|
@ -90,60 +76,12 @@ internal class TokensListMigration(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getLegacyCryptoCurrencies(): TokensListCryptoCurrencies {
|
||||
val wallets = store.state.walletState.walletsDataFromStores
|
||||
val derivationStyle = store.state.globalState.scanResponse?.derivationStyleProvider?.getDerivationStyle()
|
||||
|
||||
return TokensListCryptoCurrencies(
|
||||
coins = wallets.toNonCustomBlockchains(derivationStyle),
|
||||
tokens = wallets.toNonCustomTokensWithBlockchains(derivationStyle),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<WalletDataModel>.toNonCustomBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
|
||||
return this
|
||||
.mapNotNull { walletDataModel ->
|
||||
if (walletDataModel.currency.isCustomCurrency(derivationStyle)) {
|
||||
null
|
||||
} else {
|
||||
(walletDataModel.currency as? Currency.Blockchain)?.blockchain
|
||||
}
|
||||
}
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun List<WalletDataModel>.toNonCustomTokensWithBlockchains(
|
||||
derivationStyle: DerivationStyle?,
|
||||
): List<TokenWithBlockchain> {
|
||||
return this
|
||||
.mapNotNull { walletDataModel ->
|
||||
if (walletDataModel.currency !is Currency.Token) return@mapNotNull null
|
||||
if (walletDataModel.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
|
||||
|
||||
TokenWithBlockchain(walletDataModel.currency.token, walletDataModel.currency.blockchain)
|
||||
}
|
||||
.distinct()
|
||||
}
|
||||
|
||||
fun onSaveButtonClick(
|
||||
currentTokensList: List<TokenWithBlockchain>,
|
||||
currentBlockchainList: List<Blockchain>,
|
||||
changedTokensList: MutableList<TokenWithBlockchain>,
|
||||
changedBlockchainList: List<Blockchain>,
|
||||
) {
|
||||
if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
saveByNewWay(changedTokensList = changedTokensList, changedBlockchainList = changedBlockchainList)
|
||||
} else {
|
||||
saveByOldWay(currentTokensList, currentBlockchainList, changedTokensList, changedBlockchainList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveByNewWay(
|
||||
changedTokensList: MutableList<TokenWithBlockchain>,
|
||||
changedBlockchainList: List<Blockchain>,
|
||||
) {
|
||||
store.dispatch(
|
||||
action = TokensAction.NewSaveChanges(
|
||||
action = TokensAction.SaveChanges(
|
||||
currentTokens = currentNewTokens,
|
||||
currentCoins = currentNewCoins,
|
||||
changedTokens = changedTokensList.mapNotNull {
|
||||
|
|
@ -165,23 +103,4 @@ internal class TokensListMigration(
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun saveByOldWay(
|
||||
currentTokensList: List<TokenWithBlockchain>,
|
||||
currentBlockchainList: List<Blockchain>,
|
||||
changedTokensList: MutableList<TokenWithBlockchain>,
|
||||
changedBlockchainList: List<Blockchain>,
|
||||
) {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
|
||||
store.dispatch(
|
||||
action = TokensAction.LegacySaveChanges(
|
||||
currentTokens = currentTokensList,
|
||||
currentBlockchains = currentBlockchainList,
|
||||
changedTokens = changedTokensList,
|
||||
changedBlockchains = changedBlockchainList,
|
||||
scanResponse = scanResponse,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ import com.tangem.domain.common.util.cardTypesResolver
|
|||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.TokenWithBlockchain
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
|
||||
import com.tangem.tap.common.extensions.getNetworkName
|
||||
import com.tangem.tap.features.tokens.impl.domain.TokensListInteractor
|
||||
|
|
@ -70,7 +69,6 @@ internal class TokensListViewModel @Inject constructor(
|
|||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
getCurrenciesUseCase: GetCryptoCurrenciesUseCase,
|
||||
walletFeatureToggles: WalletFeatureToggles,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
||||
private val isManageAccess = store.state.tokensState.isManageAccess
|
||||
|
|
@ -88,7 +86,6 @@ internal class TokensListViewModel @Inject constructor(
|
|||
private var changedBlockchainList: MutableList<Blockchain> = mutableListOf()
|
||||
|
||||
private val tokensListMigration = TokensListMigration(
|
||||
walletFeatureToggles = walletFeatureToggles,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
getCurrenciesUseCase = getCurrenciesUseCase,
|
||||
)
|
||||
|
|
@ -307,8 +304,6 @@ internal class TokensListViewModel @Inject constructor(
|
|||
fun onSaveButtonClick() {
|
||||
analyticsSender.sendWhenSaveButtonClicked()
|
||||
tokensListMigration.onSaveButtonClick(
|
||||
currentTokensList = currentTokensList,
|
||||
currentBlockchainList = currentBlockchainList,
|
||||
changedTokensList = changedTokensList,
|
||||
changedBlockchainList = changedBlockchainList,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,14 +2,11 @@ package com.tangem.tap.features.tokens.legacy.redux
|
|||
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.configs.CardConfig
|
||||
|
|
@ -17,23 +14,22 @@ import com.tangem.domain.common.util.derivationStyleProvider
|
|||
import com.tangem.domain.common.util.supportsHdWallet
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.TokenWithBlockchain
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.walletconnect.WalletConnectActions
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.domain.card.DefaultDerivePublicKeysUseCase
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
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
|
||||
import timber.log.Timber
|
||||
|
|
@ -53,15 +49,14 @@ object TokensMiddleware {
|
|||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is TokensAction.LegacySaveChanges -> handleLegacySaveChanges(action)
|
||||
is TokensAction.NewSaveChanges -> handleNewSaveChanges(action)
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewSaveChanges(action: TokensAction.NewSaveChanges) {
|
||||
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
|
||||
scope.launch {
|
||||
val scanResponse = action.userWallet.scanResponse
|
||||
|
||||
|
|
@ -74,7 +69,7 @@ object TokensMiddleware {
|
|||
val tokensToAdd = action.changedTokens.filterNot(currentTokens::contains)
|
||||
val tokensToRemove = currentTokens.filterNot { token -> action.changedTokens.any { it == token } }
|
||||
|
||||
removeNewCurrenciesIfNeeded(
|
||||
removeCurrenciesIfNeeded(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
currencies = blockchainsToRemove + tokensToRemove,
|
||||
)
|
||||
|
|
@ -89,146 +84,40 @@ object TokensMiddleware {
|
|||
|
||||
val currencyList = blockchainsToAdd + tokensToAdd
|
||||
|
||||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingCoins(scanResponse = scanResponse, currencyList = currencyList) {
|
||||
submitNewAdd(
|
||||
userWallet = action.userWallet,
|
||||
updatedScanResponse = it,
|
||||
currencyList = currencyList,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
submitNewAdd(
|
||||
userWallet = action.userWallet,
|
||||
updatedScanResponse = scanResponse,
|
||||
currencyList = currencyList,
|
||||
val featureToggles = store.state.daggerGraphState.get(DaggerGraphState::testerFeatureToggles)
|
||||
if (featureToggles.isDerivePublicKeysRefactoringEnabled) {
|
||||
val derivePublicKeys = DefaultDerivePublicKeysUseCase(
|
||||
tangemSdkManager = tangemSdkManager,
|
||||
derivationsRepository = store.state.daggerGraphState.get(DaggerGraphState::derivationsRepository),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLegacySaveChanges(action: TokensAction.LegacySaveChanges) {
|
||||
scope.launch {
|
||||
val scanResponse = action.scanResponse
|
||||
|
||||
val currentTokens = action.currentTokens
|
||||
val currentBlockchains = action.currentBlockchains
|
||||
|
||||
val blockchainsToAdd = action.changedBlockchains.filterNot(currentBlockchains::contains)
|
||||
val blockchainsToRemove = currentBlockchains.filterNot(action.changedBlockchains::contains)
|
||||
|
||||
val tokensToAdd = action.changedTokens.filterNot(currentTokens::contains)
|
||||
val tokensToRemove =
|
||||
currentTokens.filterNot { token -> action.changedTokens.any { it.token == token.token } }
|
||||
|
||||
removeLegacyCurrenciesIfNeeded(
|
||||
currencies = convertToCurrencies(
|
||||
blockchains = blockchainsToRemove,
|
||||
tokens = tokensToRemove,
|
||||
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
|
||||
),
|
||||
)
|
||||
|
||||
val isNothingToDoWithTokens = tokensToAdd.isEmpty() && tokensToRemove.isEmpty()
|
||||
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
|
||||
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
|
||||
store.dispatchDebugErrorNotification(message = "Nothing to save")
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
return@launch
|
||||
}
|
||||
|
||||
val currencyList = convertToCurrencies(
|
||||
blockchains = blockchainsToAdd,
|
||||
tokens = tokensToAdd,
|
||||
derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle(),
|
||||
)
|
||||
|
||||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingBlockchains(scanResponse, currencyList) {
|
||||
submitLegacyAdd(it, currencyList)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
} else {
|
||||
submitLegacyAdd(scanResponse, currencyList)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertToCurrencies(
|
||||
blockchains: List<Blockchain>,
|
||||
tokens: List<TokenWithBlockchain>,
|
||||
derivationStyle: DerivationStyle?,
|
||||
): List<Currency> {
|
||||
return blockchains.map { Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath) } +
|
||||
tokens.map {
|
||||
Currency.Token(
|
||||
token = it.token,
|
||||
blockchain = it.blockchain,
|
||||
derivationPath = it.blockchain.derivationPath(derivationStyle)?.rawPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deriveMissingBlockchains(
|
||||
scanResponse: ScanResponse,
|
||||
currencyList: List<Currency>,
|
||||
onSuccess: (ScanResponse) -> Unit,
|
||||
) {
|
||||
val config = CardConfig.createConfig(scanResponse.card)
|
||||
val derivationDataList = currencyList.mapNotNull { currency ->
|
||||
val curve = config.primaryCurve(currency.blockchain)
|
||||
curve?.let { getLegacyDerivations(curve, scanResponse, currency) }
|
||||
}
|
||||
val derivations = buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
||||
derivationDataList.forEach {
|
||||
val current = this[it.derivations.first]
|
||||
if (current != null) {
|
||||
current.addAll(it.derivations.second)
|
||||
current.distinct()
|
||||
} else {
|
||||
this[it.derivations.first] = it.derivations.second.toMutableList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (derivations.isEmpty()) {
|
||||
onSuccess(scanResponse)
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.derivePublicKeys(
|
||||
cardId = null,
|
||||
derivations = derivations,
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val newDerivedKeys = result.data.entries
|
||||
val oldDerivedKeys = scanResponse.derivedKeys
|
||||
|
||||
val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet()
|
||||
|
||||
val updatedDerivedKeys = walletKeys.associateWith { walletKey ->
|
||||
val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap())
|
||||
val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
ExtendedPublicKeysMap(oldDerivations + newDerivations)
|
||||
derivePublicKeys(userWalletId = action.userWallet.walletId, currencies = currencyList)
|
||||
.onRight {
|
||||
addCryptoCurrenciesUseCase(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
currencies = currencyList,
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
val updatedScanResponse = scanResponse.copy(
|
||||
derivedKeys = updatedDerivedKeys,
|
||||
)
|
||||
store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
|
||||
onSuccess(updatedScanResponse)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens"))
|
||||
.onLeft { Timber.e("Failed to derive public keys: $it") }
|
||||
} else {
|
||||
// TODO: delete [REDACTED_JIRA]
|
||||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingCoins(scanResponse = scanResponse, currencyList = currencyList) {
|
||||
submitAdd(
|
||||
userWallet = action.userWallet,
|
||||
updatedScanResponse = it,
|
||||
currencyList = currencyList,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
submitAdd(action.userWallet, scanResponse, currencyList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(message = "Use DerivePublicKeysUseCase instead")
|
||||
private fun deriveMissingCoins(
|
||||
scanResponse: ScanResponse,
|
||||
currencyList: List<CryptoCurrency>,
|
||||
|
|
@ -237,7 +126,7 @@ object TokensMiddleware {
|
|||
val config = CardConfig.createConfig(scanResponse.card)
|
||||
val derivationDataList = currencyList.mapNotNull { currency ->
|
||||
val curve = config.primaryCurve(blockchain = Blockchain.fromId(currency.network.id.value))
|
||||
curve?.let { getNewDerivations(curve, scanResponse, currency) }
|
||||
curve?.let { getDerivations(curve, scanResponse, currency) }
|
||||
}
|
||||
val derivations = buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
||||
derivationDataList.forEach {
|
||||
|
|
@ -286,42 +175,7 @@ object TokensMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getLegacyDerivations(
|
||||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
currency: Currency,
|
||||
): DerivationData? {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||
|
||||
val supportedCurves = currency.blockchain.getSupportedCurves()
|
||||
val path = currency.blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle())
|
||||
.takeIf { supportedCurves.contains(curve) }
|
||||
|
||||
val customPath = currency.derivationPath?.let {
|
||||
DerivationPath(it)
|
||||
}.takeIf { supportedCurves.contains(curve) }
|
||||
|
||||
val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList()
|
||||
if (bothCandidates.isEmpty()) return null
|
||||
|
||||
if (currency is Currency.Blockchain && currency.blockchain == Blockchain.Cardano) {
|
||||
currency.derivationPath?.let {
|
||||
bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it)))
|
||||
}
|
||||
}
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys: ExtendedPublicKeysMap =
|
||||
scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
|
||||
val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
|
||||
|
||||
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||
if (toDerive.isEmpty()) return null
|
||||
|
||||
return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
|
||||
}
|
||||
|
||||
private fun getNewDerivations(
|
||||
private fun getDerivations(
|
||||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
currency: CryptoCurrency,
|
||||
|
|
@ -359,28 +213,7 @@ object TokensMiddleware {
|
|||
|
||||
class DerivationData(val derivations: Pair<ByteArrayKey, List<DerivationPath>>)
|
||||
|
||||
private fun submitLegacyAdd(scanResponse: ScanResponse, currencyList: List<Currency>) {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to add currencies, no user wallet selected")
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
userWalletsListManager.update(
|
||||
userWalletId = selectedUserWallet.walletId,
|
||||
update = { userWallet ->
|
||||
userWallet.copy(scanResponse = scanResponse)
|
||||
},
|
||||
)
|
||||
.flatMap { updatedUserWallet ->
|
||||
walletCurrenciesManager.addCurrencies(
|
||||
userWallet = updatedUserWallet,
|
||||
currenciesToAdd = currencyList,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun submitNewAdd(
|
||||
private fun submitAdd(
|
||||
userWallet: UserWallet,
|
||||
updatedScanResponse: ScanResponse,
|
||||
currencyList: List<CryptoCurrency>,
|
||||
|
|
@ -390,24 +223,13 @@ object TokensMiddleware {
|
|||
userWalletId = userWallet.walletId,
|
||||
update = { it.copy(scanResponse = updatedScanResponse) },
|
||||
).doOnSuccess {
|
||||
addCryptoCurrenciesUseCase(userWallet.walletId, currencyList).onRight {
|
||||
store.dispatch(action = WalletConnectActions.New.SetupUserChains(userWallet = userWallet))
|
||||
}
|
||||
addCryptoCurrenciesUseCase(userWallet.walletId, currencyList)
|
||||
}
|
||||
}
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
|
||||
private suspend fun removeLegacyCurrenciesIfNeeded(currencies: List<Currency>) {
|
||||
if (currencies.isEmpty()) return
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to remove currencies, no user wallet selected")
|
||||
return
|
||||
}
|
||||
walletCurrenciesManager.removeCurrencies(selectedUserWallet, currencies)
|
||||
}
|
||||
|
||||
private suspend fun removeNewCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
private suspend fun removeCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
if (currencies.isEmpty()) return
|
||||
val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
|
||||
val walletManagersFacade = store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade)
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.converters
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
internal class CryptoCurrencyConverter : TwoWayConverter<Currency, CryptoCurrency> {
|
||||
|
||||
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
|
||||
|
||||
override fun convert(value: Currency): CryptoCurrency {
|
||||
return when (value) {
|
||||
is Currency.Blockchain -> requireNotNull(
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = value.blockchain,
|
||||
extraDerivationPath = value.derivationPath,
|
||||
derivationStyleProvider = requireNotNull(
|
||||
store.state.globalState
|
||||
.userWalletsListManager
|
||||
?.selectedUserWalletSync
|
||||
?.scanResponse
|
||||
?.derivationStyleProvider,
|
||||
),
|
||||
),
|
||||
)
|
||||
is Currency.Token -> requireNotNull(
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = value.token,
|
||||
blockchain = value.blockchain,
|
||||
extraDerivationPath = value.derivationPath,
|
||||
derivationStyleProvider = requireNotNull(
|
||||
store.state.globalState
|
||||
.userWalletsListManager
|
||||
?.selectedUserWalletSync
|
||||
?.scanResponse
|
||||
?.derivationStyleProvider,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: CryptoCurrency): Currency {
|
||||
val blockchain = Blockchain.fromId(value.network.id.value)
|
||||
if (blockchain == Blockchain.Unknown) error("CryptoCurrencyConverter convertBack Unknown blockchain")
|
||||
return when (value) {
|
||||
is CryptoCurrency.Coin -> Currency.Blockchain(
|
||||
blockchain = blockchain,
|
||||
derivationPath = value.network.derivationPath.value,
|
||||
)
|
||||
is CryptoCurrency.Token -> Currency.Token(
|
||||
token = Token(
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
contractAddress = value.contractAddress,
|
||||
decimals = value.decimals,
|
||||
id = value.id.value,
|
||||
),
|
||||
blockchain = blockchain,
|
||||
derivationPath = value.network.derivationPath.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.data
|
||||
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
import com.tangem.tap.features.wallet.domain.WalletRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Implementation of repository for Wallet feature
|
||||
*
|
||||
* @property tangemTechApi API for server requests
|
||||
* @property dispatchers coroutine dispatcher provider
|
||||
*/
|
||||
class WalletRepositoryImpl(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : WalletRepository {
|
||||
|
||||
override suspend fun getCurrencyList(): CurrenciesResponse = withContext(dispatchers.io) {
|
||||
tangemTechApi.getCurrencyList().getOrThrow()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.domain
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
|
||||
/** Repository for Wallet feature */
|
||||
interface WalletRepository {
|
||||
|
||||
/** Get list of currency */
|
||||
suspend fun getCurrencyList(): CurrenciesResponse
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.models
|
||||
|
||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.toCoinId
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.blockchain.common.Blockchain as SdkBlockchain
|
||||
import com.tangem.blockchain.common.Token as SdkToken
|
||||
|
||||
sealed interface Currency {
|
||||
val coinId: String?
|
||||
get() = when (this) {
|
||||
is Blockchain -> blockchain.toCoinId()
|
||||
is Token -> token.id
|
||||
}
|
||||
val blockchain: SdkBlockchain
|
||||
val currencySymbol: CryptoCurrencyName
|
||||
val derivationPath: String?
|
||||
val currencyName: String
|
||||
get() = when (this) {
|
||||
is Blockchain -> blockchain.fullName
|
||||
is Token -> token.name
|
||||
}
|
||||
val decimals
|
||||
get() = when (this) {
|
||||
is Blockchain -> blockchain.decimals()
|
||||
is Token -> token.decimals
|
||||
}
|
||||
|
||||
data class Token(
|
||||
val token: SdkToken,
|
||||
override val blockchain: SdkBlockchain,
|
||||
override val derivationPath: String?,
|
||||
) : Currency {
|
||||
override val currencySymbol = token.symbol
|
||||
}
|
||||
|
||||
data class Blockchain(
|
||||
override val blockchain: SdkBlockchain,
|
||||
override val derivationPath: String?,
|
||||
) : Currency {
|
||||
override val currencySymbol: CryptoCurrencyName = blockchain.currency
|
||||
}
|
||||
|
||||
fun isCustomCurrency(derivationStyle: DerivationStyle?): Boolean {
|
||||
if (this is Token && this.token.id == null) return true
|
||||
|
||||
if (derivationPath == null || derivationStyle == null) return false
|
||||
|
||||
return derivationPath != blockchain.derivationPath(derivationStyle)?.rawPath
|
||||
}
|
||||
|
||||
fun isBlockchain(): Boolean = this is Blockchain
|
||||
fun isToken(): Boolean = this is Token
|
||||
|
||||
companion object {
|
||||
fun fromBlockchainNetwork(blockchainNetwork: BlockchainNetwork, token: SdkToken? = null): Currency {
|
||||
return if (token != null) {
|
||||
Token(
|
||||
token = token,
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationPath = blockchainNetwork.derivationPath,
|
||||
)
|
||||
} else {
|
||||
Blockchain(
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationPath = blockchainNetwork.derivationPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun fromCustomCurrency(customCurrency: CustomCurrency): Currency {
|
||||
return when (customCurrency) {
|
||||
is CustomCurrency.CustomBlockchain -> Blockchain(
|
||||
blockchain = customCurrency.network,
|
||||
derivationPath = customCurrency.derivationPath?.rawPath,
|
||||
)
|
||||
is CustomCurrency.CustomToken -> Token(
|
||||
token = customCurrency.token,
|
||||
blockchain = customCurrency.network,
|
||||
derivationPath = customCurrency.derivationPath?.rawPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun fromTokenResponse(tokenBody: UserTokensResponse.Token): Currency? {
|
||||
val blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenBody.networkId)
|
||||
?: return null
|
||||
return when {
|
||||
tokenBody.contractAddress != null -> Token(
|
||||
token = SdkToken(
|
||||
name = tokenBody.name,
|
||||
symbol = tokenBody.symbol,
|
||||
contractAddress = tokenBody.contractAddress!!,
|
||||
decimals = tokenBody.decimals,
|
||||
id = tokenBody.id,
|
||||
),
|
||||
blockchain = blockchain,
|
||||
derivationPath = tokenBody.derivationPath,
|
||||
)
|
||||
else -> Blockchain(
|
||||
blockchain = blockchain,
|
||||
derivationPath = tokenBody.derivationPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BlockchainNetwork.toCurrencies(): List<Currency> {
|
||||
val blockchain = Currency.fromBlockchainNetwork(this)
|
||||
val tokens = this.tokens.map { Currency.fromBlockchainNetwork(this, it) }
|
||||
return listOf(blockchain) + tokens
|
||||
}
|
||||
|
||||
fun List<BlockchainNetwork>.toCurrencies(): List<Currency> {
|
||||
return flatMap { it.toCurrencies() }
|
||||
}
|
||||
|
||||
fun List<Currency>.toBlockchainNetworks(): List<BlockchainNetwork> {
|
||||
return this.filter { it.isBlockchain() }.map { BlockchainNetwork(it.blockchain, it.derivationPath, getTokens(it)) }
|
||||
}
|
||||
|
||||
fun List<Currency>.getTokens(currency: Currency): List<SdkToken> {
|
||||
return this
|
||||
.filter { it.isToken() && it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath }
|
||||
.mapNotNull { if (it is Currency.Token) it.token else null }
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.models
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.extensions.isAboveZero
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class PendingTransaction(
|
||||
val transactionData: TransactionData,
|
||||
val type: PendingTransactionType,
|
||||
) {
|
||||
val address: String? = when (type) {
|
||||
PendingTransactionType.Incoming -> nullIfUnknown(transactionData.sourceAddress)
|
||||
PendingTransactionType.Outgoing -> nullIfUnknown(transactionData.destinationAddress)
|
||||
PendingTransactionType.Unknown -> null
|
||||
}
|
||||
|
||||
val amountValue: BigDecimal? = transactionData.amount.value
|
||||
|
||||
val amountValueUi: String? = amountValue?.toFormattedString(transactionData.amount.decimals)
|
||||
|
||||
val currency: String = transactionData.amount.currencySymbol
|
||||
|
||||
private fun nullIfUnknown(address: String): String? = if (address == "unknown") null else address
|
||||
}
|
||||
|
||||
enum class PendingTransactionType { Incoming, Outgoing, Unknown }
|
||||
|
||||
fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransaction? {
|
||||
if (this.status == TransactionStatus.Confirmed) return null
|
||||
|
||||
val type: PendingTransactionType = when {
|
||||
this.sourceAddress == walletAddress -> PendingTransactionType.Outgoing
|
||||
this.destinationAddress == walletAddress -> PendingTransactionType.Incoming
|
||||
else -> PendingTransactionType.Unknown
|
||||
}
|
||||
return PendingTransaction(this, type)
|
||||
}
|
||||
|
||||
fun List<TransactionData>.toPendingTransactions(walletAddress: String): List<PendingTransaction> {
|
||||
return this.mapNotNull { it.toPendingTransaction(walletAddress) }
|
||||
}
|
||||
|
||||
fun List<PendingTransaction>.filterByCoin(): List<PendingTransaction> {
|
||||
return this.filter { it.transactionData.amount.type == AmountType.Coin }
|
||||
}
|
||||
|
||||
fun TransactionData.toPendingTransactionForToken(token: Token, walletAddress: String): PendingTransaction? {
|
||||
if (this.amount.currencySymbol != token.symbol) return null
|
||||
return this.toPendingTransaction(walletAddress)
|
||||
}
|
||||
|
||||
fun Wallet.getPendingTransactions(type: PendingTransactionType? = null): List<PendingTransaction> {
|
||||
val txs = recentTransactions.toPendingTransactions(address)
|
||||
return when (type) {
|
||||
null -> txs
|
||||
else -> txs.filter { it.type == type }
|
||||
}
|
||||
}
|
||||
|
||||
fun Wallet.getPendingTransactions(token: Token): List<PendingTransaction> {
|
||||
return recentTransactions.mapNotNull { it.toPendingTransactionForToken(token, address) }
|
||||
}
|
||||
|
||||
fun Wallet.hasPendingTransactions(): Boolean {
|
||||
return getPendingTransactions().isNotEmpty()
|
||||
}
|
||||
|
||||
fun Wallet.getSendableAmounts(): List<Amount> {
|
||||
return amounts.values
|
||||
.filter { it.type != AmountType.Reserve }
|
||||
.filter { it.isAboveZero() }
|
||||
}
|
||||
|
||||
fun Wallet.hasSendableAmounts(): Boolean {
|
||||
return getSendableAmounts().isNotEmpty()
|
||||
}
|
||||
|
||||
fun Wallet.isSendableAmount(type: AmountType): Boolean {
|
||||
return amounts[type]?.isAboveZero() == true
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.models
|
||||
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
|
||||
sealed class WalletWarning(val showingPosition: Int) {
|
||||
|
||||
data class ExistentialDeposit(
|
||||
val currencyName: String,
|
||||
val edStringValueWithSymbol: String,
|
||||
) : WalletWarning(1)
|
||||
|
||||
data class TransactionInProgress(val currencyName: String) : WalletWarning(showingPosition = 10)
|
||||
|
||||
data class BalanceNotEnoughForFee(
|
||||
val currencyName: String,
|
||||
val blockchainFullName: String,
|
||||
val blockchainSymbol: String,
|
||||
) : WalletWarning(showingPosition = 30)
|
||||
|
||||
data class Rent(val walletRent: WalletStoreModel.WalletRent) : WalletWarning(showingPosition = 40)
|
||||
}
|
||||
|
||||
data class WalletWarningDescription(val title: String, val message: String)
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class WalletAction : Action {
|
||||
|
||||
object PopBackToInitialScreen : WalletAction()
|
||||
|
||||
data class UpdateCanSaveUserWallets(val canSaveUserWallets: Boolean) : WalletAction()
|
||||
|
||||
object LoadData : WalletAction() {
|
||||
object Refresh : WalletAction()
|
||||
object Success : WalletAction()
|
||||
data class Failure(val error: TapError?) : WalletAction()
|
||||
}
|
||||
|
||||
sealed class MultiWallet : WalletAction() {
|
||||
|
||||
data class SelectWallet(val currency: Currency?) : MultiWallet()
|
||||
|
||||
data class TryToRemoveWallet(val currency: Currency) : MultiWallet()
|
||||
data class RemoveWallet(val currency: Currency) : MultiWallet()
|
||||
|
||||
object BackupWallet : MultiWallet()
|
||||
data class AddMissingDerivations(val blockchains: List<BlockchainNetwork>) : MultiWallet()
|
||||
object ScanToGetDerivations : MultiWallet()
|
||||
|
||||
/**
|
||||
* Display warning if card has no backup
|
||||
*
|
||||
* @param card card to check status
|
||||
* */
|
||||
data class CheckForBackupWarning(val card: CardDTO) : MultiWallet()
|
||||
}
|
||||
|
||||
sealed class Warnings : WalletAction() {
|
||||
object CheckHashesCount : Warnings() {
|
||||
|
||||
/**
|
||||
* Start online verification of signed hashes for single currency wallets if the warning not displayed
|
||||
* */
|
||||
object VerifyOnlineIfNeeded : Warnings()
|
||||
object SaveCardId : Warnings()
|
||||
}
|
||||
|
||||
object CheckIfNeeded : Warnings()
|
||||
object Update : Warnings()
|
||||
data class Set(val warningList: List<WarningMessage>) : Warnings()
|
||||
|
||||
object AppRating : Warnings() {
|
||||
object SetNeverToShow : Warnings()
|
||||
object RemindLater : Warnings()
|
||||
}
|
||||
|
||||
class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings()
|
||||
}
|
||||
|
||||
data class Scan(
|
||||
val onScanSuccessEvent: AnalyticsEvent?,
|
||||
val scope: CoroutineScope,
|
||||
) : WalletAction()
|
||||
|
||||
data class Send(val amount: Amount? = null) : WalletAction()
|
||||
|
||||
data class CopyAddress(val address: String, val context: Context) : WalletAction() {
|
||||
object Success : WalletAction(), NotificationAction {
|
||||
override val messageResource = R.string.wallet_notification_address_copied
|
||||
}
|
||||
}
|
||||
|
||||
data class ShareAddress(val address: String, val context: Context) : WalletAction()
|
||||
|
||||
sealed class DialogAction : WalletAction() {
|
||||
data class QrCode(
|
||||
val currency: Currency,
|
||||
val selectedAddress: WalletDataModel.AddressData,
|
||||
) : DialogAction()
|
||||
|
||||
object SignedHashesMultiWalletDialog : DialogAction()
|
||||
data class ChooseTradeActionDialog(
|
||||
val buyAllowed: Boolean,
|
||||
val sellAllowed: Boolean,
|
||||
val swapAllowed: Boolean,
|
||||
) : DialogAction()
|
||||
|
||||
data class ChooseCurrency(val amounts: List<Amount>) : DialogAction()
|
||||
data class RussianCardholdersWarningDialog(
|
||||
val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null,
|
||||
) : DialogAction()
|
||||
|
||||
object Hide : DialogAction()
|
||||
}
|
||||
|
||||
data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction()
|
||||
|
||||
object CreateWallet : WalletAction()
|
||||
data class ChangeWallet(val scope: LifecycleCoroutineScope) : WalletAction()
|
||||
object ShowSaveWalletIfNeeded : WalletAction()
|
||||
|
||||
sealed class TradeCryptoAction : WalletAction() {
|
||||
object Sell : TradeCryptoAction()
|
||||
|
||||
data class Buy(val checkUserLocation: Boolean = true) : TradeCryptoAction()
|
||||
|
||||
object Swap : TradeCryptoAction()
|
||||
}
|
||||
|
||||
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
|
||||
|
||||
sealed class AppCurrencyAction : WalletAction() {
|
||||
object ChooseAppCurrency : AppCurrencyAction()
|
||||
data class SelectAppCurrency(val fiatCurrency: FiatCurrency) : AppCurrencyAction()
|
||||
}
|
||||
|
||||
data class UserWalletChanged(val userWallet: UserWallet) : WalletAction()
|
||||
data class WalletStoresChanged(val walletStores: List<WalletStoreModel>) : WalletAction()
|
||||
|
||||
data class TotalFiatBalanceChanged(val balance: TotalFiatBalance) : WalletAction()
|
||||
|
||||
data class UpdateUserWalletArtwork(val walletId: UserWalletId) : WalletAction()
|
||||
|
||||
data class SetArtworkUrl(val userWalletId: UserWalletId, val url: String) : WalletAction()
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.userwallets.Artwork
|
||||
import com.tangem.tap.common.entities.Button
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.common.toggleWidget.WidgetState
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.StateType
|
||||
import java.math.BigDecimal
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
data class WalletState(
|
||||
val state: ProgressState = ProgressState.Done,
|
||||
val error: ErrorType? = null,
|
||||
val cardImage: Artwork? = null,
|
||||
val mainWarningsList: List<WarningMessage> = mutableListOf(),
|
||||
val walletsStores: List<WalletStoreModel> = listOf(),
|
||||
val isMultiwalletAllowed: Boolean = false,
|
||||
val cardCurrency: CryptoCurrencyName? = null,
|
||||
val selectedCurrency: Currency? = null,
|
||||
val isTestnet: Boolean = false,
|
||||
val totalBalance: TotalFiatBalance? = null,
|
||||
val showBackupWarning: Boolean = false,
|
||||
val missingDerivations: List<BlockchainNetwork> = emptyList(),
|
||||
val loadingUserTokens: Boolean = false,
|
||||
val walletCardsCount: Int? = null,
|
||||
val canSaveUserWallets: Boolean = false,
|
||||
) : StateType {
|
||||
|
||||
val walletsDataFromStores: List<WalletDataModel>
|
||||
get() = walletsStores.flatMap { it.walletsData }
|
||||
|
||||
val selectedWalletData: WalletDataModel?
|
||||
get() = walletsDataFromStores.firstOrNull { it.currency == selectedCurrency }
|
||||
|
||||
// if you do not delegate - the application crashes on startup,
|
||||
// because twinCardsState has not been created yet
|
||||
val twinCardsState: TwinCardsState by ReadOnlyProperty<Any, TwinCardsState> { _, _ ->
|
||||
store.state.twinCardsState
|
||||
}
|
||||
|
||||
val isTangemTwins: Boolean
|
||||
get() = store.state.globalState.scanResponse?.cardTypesResolver?.isTangemTwins() == true
|
||||
|
||||
val isExchangeServiceFeatureOn: Boolean
|
||||
get() = store.state.globalState.exchangeManager.featureIsSwitchedOn()
|
||||
|
||||
val blockchains: List<Blockchain>
|
||||
get() = walletsStores.mapNotNull { it.walletManager?.wallet?.blockchain }
|
||||
|
||||
val currencies: List<Currency>
|
||||
get() = walletsStores.flatMap { it.walletsData }.map { it.currency }
|
||||
|
||||
val walletManagers: List<WalletManager>
|
||||
get() = walletsStores.mapNotNull { it.walletManager }
|
||||
|
||||
private val primaryWalletStore: WalletStoreModel?
|
||||
get() = if (isMultiwalletAllowed || walletsStores.isEmpty() || walletsStores.size > 1) {
|
||||
null
|
||||
} else {
|
||||
walletsStores[0]
|
||||
}
|
||||
|
||||
val primaryWalletManager: WalletManager?
|
||||
get() = primaryWalletStore?.walletManager
|
||||
|
||||
val primaryWalletData: WalletDataModel?
|
||||
get() = primaryWalletStore?.blockchainWalletData
|
||||
|
||||
val primaryTokenData: WalletDataModel?
|
||||
get() = primaryWalletStore?.walletsData
|
||||
?.firstOrNull { it.currency !is Currency.Blockchain }
|
||||
|
||||
fun getWalletManager(currency: Currency?): WalletManager? {
|
||||
if (currency?.blockchain == null) return null
|
||||
return getWalletStore(currency)?.walletManager
|
||||
}
|
||||
|
||||
fun getWalletManager(blockchain: BlockchainNetwork): WalletManager? {
|
||||
return walletsStores.firstOrNull {
|
||||
it.blockchain == blockchain.blockchain &&
|
||||
it.derivationPath?.rawPath == blockchain.derivationPath
|
||||
}?.walletManager
|
||||
}
|
||||
|
||||
fun getWalletStore(currency: Currency?): WalletStoreModel? {
|
||||
if (currency == null) return null
|
||||
return walletsStores.firstOrNull {
|
||||
it.blockchain == currency.blockchain &&
|
||||
it.derivationPath?.rawPath == currency.derivationPath
|
||||
}
|
||||
}
|
||||
|
||||
fun getBlockchainAmount(currency: Currency): BigDecimal =
|
||||
getWalletManager(currency)?.wallet?.amounts?.get(AmountType.Coin)?.value ?: BigDecimal.ZERO
|
||||
}
|
||||
|
||||
enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error }
|
||||
|
||||
enum class ErrorType {
|
||||
NoInternetConnection,
|
||||
UnknownBlockchain,
|
||||
}
|
||||
|
||||
sealed class WalletMainButton(enabled: Boolean) : Button(enabled) {
|
||||
class SendButton(enabled: Boolean) : WalletMainButton(enabled)
|
||||
}
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.data.source.preferences.model.DataSourceCurrency
|
||||
import com.tangem.data.source.preferences.model.DataSourceFiatCurrency
|
||||
import com.tangem.data.source.preferences.storage.FiatCurrenciesPrefStorage
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.wallet.domain.WalletRepository
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class AppCurrencyMiddleware(
|
||||
private val walletRepository: WalletRepository,
|
||||
private val tapWalletManager: TapWalletManager,
|
||||
private val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage,
|
||||
private val featureToggles: WalletFeatureToggles,
|
||||
private val appCurrencyRepository: AppCurrencyRepository,
|
||||
private val appCurrencyProvider: () -> FiatCurrency,
|
||||
) {
|
||||
private val showSelectorJobHolder = JobHolder()
|
||||
|
||||
fun handle(action: WalletAction.AppCurrencyAction) {
|
||||
when (action) {
|
||||
is WalletAction.AppCurrencyAction.ChooseAppCurrency -> showSelector()
|
||||
is WalletAction.AppCurrencyAction.SelectAppCurrency -> selectCurrency(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSelector() {
|
||||
if (featureToggles.isRedesignedScreenEnabled) {
|
||||
showSelectorNew()
|
||||
} else {
|
||||
showSelectorLegacy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) {
|
||||
if (featureToggles.isRedesignedScreenEnabled) {
|
||||
selectCurrencyNew(action.fiatCurrency)
|
||||
} else {
|
||||
selectCurrencyLegacy(action.fiatCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSelectorNew() {
|
||||
scope.launch {
|
||||
val currencies = appCurrencyRepository.getAvailableAppCurrencies()
|
||||
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.CurrencySelectionDialog(
|
||||
currenciesList = currencies.map { appCurrency ->
|
||||
FiatCurrency(
|
||||
code = appCurrency.code,
|
||||
name = appCurrency.name,
|
||||
symbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
currentAppCurrency = appCurrencyProvider.invoke(),
|
||||
),
|
||||
)
|
||||
}.saveIn(showSelectorJobHolder)
|
||||
}
|
||||
|
||||
private fun showSelectorLegacy() {
|
||||
val storedFiatCurrencies = fiatCurrenciesPrefStorage.restore()
|
||||
if (storedFiatCurrencies.isNotEmpty()) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.CurrencySelectionDialog(
|
||||
currenciesList = storedFiatCurrencies.mapToUiModel(),
|
||||
currentAppCurrency = appCurrencyProvider.invoke(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
runCatching { walletRepository.getCurrencyList() }
|
||||
.onSuccess { response ->
|
||||
val currenciesList = response.currencies
|
||||
.map { with(it) { DataSourceCurrency(id, code, name, rateBTC, unit, type) } }
|
||||
|
||||
if (currenciesList.isNotEmpty() && currenciesList.toSet() != storedFiatCurrencies.toSet()) {
|
||||
fiatCurrenciesPrefStorage.save(currenciesList)
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.CurrencySelectionDialog(
|
||||
currenciesList = currenciesList.mapToUiModel(),
|
||||
currentAppCurrency = appCurrencyProvider.invoke(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectCurrencyNew(fiatCurrency: FiatCurrency) {
|
||||
Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(fiatCurrency)))
|
||||
|
||||
scope.launch {
|
||||
appCurrencyRepository.changeAppCurrency(fiatCurrency.code)
|
||||
|
||||
store.dispatchWithMain(GlobalAction.ChangeAppCurrency(fiatCurrency))
|
||||
store.dispatchWithMain(DetailsAction.ChangeAppCurrency(fiatCurrency))
|
||||
store.dispatchWithMain(WalletSelectorAction.ChangeAppCurrency(fiatCurrency))
|
||||
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to select currency, no user wallet selected")
|
||||
return@launch
|
||||
}
|
||||
|
||||
tapWalletManager.loadData(selectedUserWallet, refresh = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectCurrencyLegacy(fiatCurrency: FiatCurrency) {
|
||||
Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(fiatCurrency)))
|
||||
fiatCurrenciesPrefStorage.saveAppCurrency(
|
||||
with(fiatCurrency) { DataSourceFiatCurrency(code, name, symbol) },
|
||||
)
|
||||
store.dispatch(GlobalAction.ChangeAppCurrency(fiatCurrency))
|
||||
store.dispatch(DetailsAction.ChangeAppCurrency(fiatCurrency))
|
||||
store.dispatch(WalletSelectorAction.ChangeAppCurrency(fiatCurrency))
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to select currency, no user wallet selected")
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
tapWalletManager.loadData(selectedUserWallet, refresh = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<DataSourceCurrency>.mapToUiModel(): List<FiatCurrency> {
|
||||
return this.map {
|
||||
FiatCurrency(
|
||||
code = it.code,
|
||||
name = it.name,
|
||||
symbol = it.unit,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
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.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class MultiWalletMiddleware {
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun handle(action: WalletAction.MultiWallet, walletState: WalletState?) {
|
||||
when (action) {
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
if (action.currency != null) {
|
||||
store.dispatch(NavigationAction.NavigateTo(screen = AppScreen.WalletDetails))
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.TryToRemoveWallet -> {
|
||||
val currency = action.currency
|
||||
val walletManager = walletState?.getWalletManager(currency).guard {
|
||||
store.dispatchErrorNotification(TapError.UnsupportedState("walletManager is NULL"))
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
return
|
||||
}
|
||||
|
||||
if (currency.isBlockchain() && walletManager.cardTokens.isNotEmpty()) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.TokensAreLinkedDialog(
|
||||
currencyTitle = currency.currencyName,
|
||||
currencySymbol = currency.currencySymbol,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.RemoveWalletDialog(
|
||||
currencyTitle = currency.currencyName,
|
||||
onOk = {
|
||||
Analytics.send(ButtonRemoveToken(AnalyticsParam.CurrencyType.Currency(currency)))
|
||||
store.dispatch(WalletAction.MultiWallet.RemoveWallet(currency))
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.RemoveWallet -> {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to remove wallet, no user wallet selected")
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
walletCurrenciesManager.removeCurrency(
|
||||
userWallet = selectedUserWallet,
|
||||
currencyToRemove = action.currency,
|
||||
)
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.BackupWallet -> {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to backup wallet, no user wallet selected")
|
||||
return
|
||||
}
|
||||
val scanResponse = selectedUserWallet.scanResponse
|
||||
Analytics.addContext(scanResponse)
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
}
|
||||
is WalletAction.MultiWallet.AddMissingDerivations -> {
|
||||
store.state.globalState.topUpController?.addMissingDerivations(action.blockchains)
|
||||
}
|
||||
is WalletAction.MultiWallet.ScanToGetDerivations -> {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to scan to get derivations, no user wallet selected")
|
||||
return
|
||||
}
|
||||
store.state.globalState.topUpController?.scanToGetDerivations()
|
||||
scanAndUpdateCard(selectedUserWallet)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanAndUpdateCard(selectedUserWallet: UserWallet) = scope.launch(Dispatchers.Default) {
|
||||
store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor)
|
||||
.scan(cardId = selectedUserWallet.cardId, allowsRequestAccessCodeFromRepository = true)
|
||||
.flatMap { scanResponse ->
|
||||
userWalletsListManager.update(
|
||||
userWalletId = selectedUserWallet.walletId,
|
||||
update = { userWallet ->
|
||||
userWallet.copy(
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
.doOnSuccess { updatedUserWallet ->
|
||||
store.dispatchWithMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList()))
|
||||
store.dispatchWithMain(GlobalAction.SaveScanResponse(updatedUserWallet.scanResponse))
|
||||
store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
|
|
@ -14,45 +13,45 @@ import com.tangem.domain.tokens.model.CryptoCurrency
|
|||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
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.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class TradeCryptoMiddleware {
|
||||
@Deprecated("Will be removed soon")
|
||||
object TradeCryptoMiddleware {
|
||||
|
||||
val middleware: Middleware<AppState> = { _, appState ->
|
||||
{ nextDispatch ->
|
||||
{ action ->
|
||||
if (action is TradeCryptoAction) {
|
||||
handle(appState, action)
|
||||
}
|
||||
nextDispatch(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
fun handle(state: () -> AppState?, action: TradeCryptoAction) {
|
||||
private fun handle(state: () -> AppState?, action: TradeCryptoAction) {
|
||||
if (DemoHelper.tryHandle(state, action)) return
|
||||
|
||||
when (action) {
|
||||
is TradeCryptoAction.Buy -> proceedBuyAction(state, action)
|
||||
is TradeCryptoAction.Sell -> proceedSellAction()
|
||||
is TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action)
|
||||
is TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen()
|
||||
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
||||
is TradeCryptoAction.Swap -> {
|
||||
// todo remove old flow
|
||||
}
|
||||
is TradeCryptoAction.New.Buy -> proceedNewBuyAction(state, action)
|
||||
is TradeCryptoAction.New.Sell -> proceedNewSellAction(action)
|
||||
is TradeCryptoAction.New.Swap -> openSwap(
|
||||
|
|
@ -63,54 +62,6 @@ class TradeCryptoMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use proceedNewBuyAction instead")
|
||||
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
val currency = chooseAppropriateCurrency(store.state.walletState) ?: return
|
||||
|
||||
Analytics.send(Token.ButtonBuy(AnalyticsParam.CurrencyType.Currency(currency)))
|
||||
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
|
||||
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog())
|
||||
return
|
||||
}
|
||||
|
||||
val card = store.state.globalState.scanResponse?.card ?: return
|
||||
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
|
||||
if (addresses.isEmpty()) return
|
||||
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
val appCurrency = store.state.globalState.appCurrency
|
||||
|
||||
if (currency is Currency.Token && currency.blockchain.isTestnet()) {
|
||||
val walletManager = store.state.walletState.getWalletManager(currency)
|
||||
if (walletManager !is EthereumWalletManager) {
|
||||
store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum")
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
buyErc20TestnetTokens(
|
||||
card = card,
|
||||
walletManager = walletManager,
|
||||
destinationAddress = currency.token.contractAddress,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Buy,
|
||||
blockchain = currency.blockchain,
|
||||
cryptoCurrencyName = currency.currencySymbol,
|
||||
fiatCurrencyName = appCurrency.code,
|
||||
walletAddress = addresses[0].address,
|
||||
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
|
||||
)?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Topup.ScreenOpened())
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedNewBuyAction(state: () -> AppState?, action: TradeCryptoAction.New.Buy) {
|
||||
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
|
||||
?.defaultAddress
|
||||
|
|
@ -132,15 +83,9 @@ class TradeCryptoMiddleware {
|
|||
|
||||
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
|
||||
val dialogData = topUrl?.let {
|
||||
WalletDialog.RussianCardholdersWarningDialog.Data(
|
||||
topUpUrl = it,
|
||||
)
|
||||
AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl = it)
|
||||
}
|
||||
store.dispatchOnMain(
|
||||
WalletAction.DialogAction.RussianCardholdersWarningDialog(
|
||||
dialogData = dialogData,
|
||||
),
|
||||
)
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(data = dialogData))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -174,29 +119,6 @@ class TradeCryptoMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun proceedSellAction() {
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
val currency = chooseAppropriateCurrency(store.state.walletState) ?: return
|
||||
|
||||
val appCurrency = store.state.globalState.appCurrency
|
||||
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
|
||||
if (addresses.isEmpty()) return
|
||||
|
||||
Analytics.send(Token.ButtonSell(AnalyticsParam.CurrencyType.Currency(currency)))
|
||||
|
||||
store.state.globalState.exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Sell,
|
||||
blockchain = currency.blockchain,
|
||||
cryptoCurrencyName = currency.currencySymbol,
|
||||
fiatCurrencyName = appCurrency.code,
|
||||
walletAddress = addresses[0].address,
|
||||
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
|
||||
)?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Withdraw.ScreenOpened())
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedNewSellAction(action: TradeCryptoAction.New.Sell) {
|
||||
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
|
||||
?.defaultAddress
|
||||
|
|
@ -217,41 +139,31 @@ class TradeCryptoMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun chooseAppropriateCurrency(walletState: WalletState): Currency? {
|
||||
return if (walletState.primaryTokenData == null) {
|
||||
walletState.selectedWalletData?.currency
|
||||
} else {
|
||||
walletState.primaryTokenData?.currency as? Currency.Token
|
||||
}.guard {
|
||||
store.dispatchDebugErrorNotification("Can't select an appropriate currency for a Trade action")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun preconfigureAndOpenSendScreen(action: TradeCryptoAction.SendCrypto) {
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
|
||||
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency)))
|
||||
val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency).guard {
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException("WalletManager is null"))
|
||||
return
|
||||
}
|
||||
|
||||
store.dispatchOnMain(
|
||||
PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
|
||||
coinRate = selectedWalletData.fiatRate,
|
||||
),
|
||||
)
|
||||
store.dispatchOnMain(
|
||||
SendAction.SendSpecificTransaction(
|
||||
sendAmount = action.amount,
|
||||
destinationAddress = action.destinationAddress,
|
||||
transactionId = action.transactionId,
|
||||
),
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
private fun preconfigureAndOpenSendScreen() = scope.launch {
|
||||
// FIXME: [REDACTED_JIRA]
|
||||
// val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
//
|
||||
// Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency)))
|
||||
// val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency).guard {
|
||||
// FirebaseCrashlytics.getInstance().recordException(IllegalStateException("WalletManager is null"))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// store.dispatchOnMain(
|
||||
// PrepareSendScreen(
|
||||
// walletManager = walletManager,
|
||||
// coinAmount = walletManager.wallet.amounts[AmountType.Coin],
|
||||
// coinRate = selectedWalletData.fiatRate,
|
||||
// ),
|
||||
// )
|
||||
// store.dispatchOnMain(
|
||||
// SendAction.SendSpecificTransaction(
|
||||
// sendAmount = action.amount,
|
||||
// destinationAddress = action.destinationAddress,
|
||||
// transactionId = action.transactionId,
|
||||
// ),
|
||||
// )
|
||||
// store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
}
|
||||
|
||||
private fun openReceiptUrl(transactionId: String) {
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
|
||||
class WalletDialogsMiddleware {
|
||||
fun handle(action: WalletAction.DialogAction) {
|
||||
when (action) {
|
||||
is WalletAction.DialogAction.SignedHashesMultiWalletDialog -> {
|
||||
store.dispatchDialogShow(WalletDialog.SignedHashesMultiWalletDialog)
|
||||
}
|
||||
is WalletAction.DialogAction.ChooseTradeActionDialog -> {
|
||||
store.state.walletState.selectedWalletData?.let {
|
||||
Analytics.send(Token.ButtonExchange(AnalyticsParam.CurrencyType.Currency(it.currency)))
|
||||
}
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.ChooseTradeActionDialog(
|
||||
buyAllowed = action.buyAllowed,
|
||||
sellAllowed = action.sellAllowed,
|
||||
swapAllowed = action.swapAllowed,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletAction.DialogAction.QrCode -> {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.AddressInfoDialog(
|
||||
currency = action.currency,
|
||||
addressData = action.selectedAddress,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletAction.DialogAction.ChooseCurrency -> {
|
||||
if (action.amounts.isEmpty()) return
|
||||
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.SelectAmountToSendDialog(
|
||||
amounts = action.amounts,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletAction.DialogAction.RussianCardholdersWarningDialog -> {
|
||||
store.dispatchDialogShow(WalletDialog.RussianCardholdersWarningDialog(action.dialogData))
|
||||
}
|
||||
is WalletAction.DialogAction.Hide -> {
|
||||
store.dispatchDialogHide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,412 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.userwallets.GetCardImageUseCase
|
||||
import com.tangem.domain.wallets.legacy.lockIfLockable
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.getSendableAmounts
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.ifActive
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class WalletMiddleware {
|
||||
private val tradeCryptoMiddleware = TradeCryptoMiddleware()
|
||||
private val warningsMiddleware = WarningsMiddleware()
|
||||
private val multiWalletMiddleware = MultiWalletMiddleware()
|
||||
private val walletDialogMiddleware = WalletDialogsMiddleware()
|
||||
private val appCurrencyMiddleware by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
AppCurrencyMiddleware(
|
||||
// TODO("After adding DI") get dependencies by DI
|
||||
walletRepository = store.state.featureRepositoryProvider.walletRepository,
|
||||
tapWalletManager = store.state.globalState.tapWalletManager,
|
||||
fiatCurrenciesPrefStorage = preferencesStorage.fiatCurrenciesPrefStorage,
|
||||
appCurrencyRepository = store.state.daggerGraphState.get(DaggerGraphState::appCurrencyRepository),
|
||||
featureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles),
|
||||
appCurrencyProvider = { store.state.globalState.appCurrency },
|
||||
)
|
||||
}
|
||||
|
||||
private val networkConnectionManager: NetworkConnectionManager
|
||||
get() = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||
|
||||
private var updateWalletStoresJob: Job? = null
|
||||
set(value) {
|
||||
field?.cancel()
|
||||
field = value
|
||||
}
|
||||
|
||||
val walletMiddleware: Middleware<AppState> = { _, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleAction(state, action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleAction(state: () -> AppState?, action: Action) {
|
||||
if (DemoHelper.tryHandle(state, action)) return
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val walletState = store.state.walletState
|
||||
|
||||
when (action) {
|
||||
is TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action)
|
||||
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
|
||||
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState)
|
||||
is WalletAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action)
|
||||
is WalletAction.DialogAction -> walletDialogMiddleware.handle(action)
|
||||
is WalletAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
when (val result = tangemSdkManager.createWallet(globalState.scanResponse?.card?.cardId)) {
|
||||
is CompletionResult.Success -> {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to create wallet, no user wallet selected")
|
||||
return@launch
|
||||
}
|
||||
val updatedScanResponse = selectedUserWallet.scanResponse.copy(
|
||||
card = result.data,
|
||||
)
|
||||
store.dispatchWithMain(GlobalAction.SaveScanResponse(updatedScanResponse))
|
||||
userWalletsListManager.update(selectedUserWallet.walletId) { userWallet ->
|
||||
userWallet.copy(scanResponse = updatedScanResponse)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.Scan -> {
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
action.scope.launch {
|
||||
delay(timeMillis = 700)
|
||||
store.dispatchOnMain(HomeAction.ReadCard(action.onScanSuccessEvent, action.scope))
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadData,
|
||||
is WalletAction.LoadData.Refresh,
|
||||
-> {
|
||||
val selectedWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to load/refresh wallets data, no user wallet selected")
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
globalState.tapWalletManager.loadData(
|
||||
userWallet = selectedWallet,
|
||||
refresh = action is WalletAction.LoadData.Refresh,
|
||||
)
|
||||
}
|
||||
|
||||
store.dispatchOnMain(WalletAction.UpdateUserWalletArtwork(selectedWallet.walletId))
|
||||
}
|
||||
is WalletAction.CopyAddress -> {
|
||||
Analytics.send(Token.Receive.ButtonCopyAddress())
|
||||
action.context.copyToClipboard(action.address)
|
||||
store.dispatch(WalletAction.CopyAddress.Success)
|
||||
}
|
||||
is WalletAction.ShareAddress -> {
|
||||
Analytics.send(Token.Receive.ButtonShareAddress())
|
||||
action.context.shareText(action.address)
|
||||
}
|
||||
is WalletAction.ExploreAddress -> {
|
||||
Analytics.send(Token.ButtonExplore())
|
||||
store.dispatchOpenUrl(action.exploreUrl)
|
||||
}
|
||||
is WalletAction.Send -> {
|
||||
val walletStore = walletState.getWalletStore(walletState.selectedCurrency)
|
||||
val selectedWalletData = walletState.selectedWalletData
|
||||
val walletManager = walletStore?.walletManager
|
||||
|
||||
if (walletStore == null || walletManager == null || selectedWalletData == null) {
|
||||
val error = TapError.UnsupportedState(
|
||||
"WalletAction.Send: walletStore or selectedWalletData or walletManager is null",
|
||||
)
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
|
||||
store.dispatchErrorNotification(error)
|
||||
return
|
||||
}
|
||||
if (!networkConnectionManager.isOnline) {
|
||||
store.dispatchErrorNotification(TapError.NoInternetConnection)
|
||||
return
|
||||
}
|
||||
|
||||
val currency = selectedWalletData.currency
|
||||
val initSendStateAction = if (action.amount == null) {
|
||||
val sendableAmounts = walletManager.wallet.getSendableAmounts()
|
||||
if (sendableAmounts.isEmpty()) {
|
||||
val error = TapError.UnsupportedState("WalletAction.Send: Nothing to send")
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
|
||||
store.dispatchErrorNotification(error)
|
||||
return
|
||||
}
|
||||
|
||||
if (walletState.isMultiwalletAllowed) {
|
||||
val amountToSend = findAmountToSend(currency = currency, amounts = sendableAmounts)
|
||||
if (amountToSend == null) {
|
||||
val error = TapError.UnsupportedState("WalletAction.Send: Amount to send is null")
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
|
||||
store.dispatchErrorNotification(error)
|
||||
return
|
||||
}
|
||||
|
||||
makeInitSendStateActionByCurrency(
|
||||
currency = currency,
|
||||
amount = amountToSend,
|
||||
walletStore = walletStore,
|
||||
walletManager = walletManager,
|
||||
selectedWalletData = selectedWalletData,
|
||||
)
|
||||
} else {
|
||||
val isSingleAmount = sendableAmounts.size == 1
|
||||
if (isSingleAmount) {
|
||||
makeInitSendStateActionByAmount(
|
||||
amount = sendableAmounts.first(),
|
||||
walletStore = walletStore,
|
||||
walletManager = walletManager,
|
||||
selectedWalletData = selectedWalletData,
|
||||
)
|
||||
} else {
|
||||
store.dispatch(WalletAction.DialogAction.ChooseCurrency(sendableAmounts))
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// action.amount received from the ChooseCurrency dialog
|
||||
makeInitSendStateActionByAmount(
|
||||
amount = action.amount,
|
||||
walletManager = walletManager,
|
||||
walletStore = walletStore,
|
||||
selectedWalletData = selectedWalletData,
|
||||
)
|
||||
}
|
||||
|
||||
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency)))
|
||||
store.dispatch(initSendStateAction)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
}
|
||||
is WalletAction.ShowSaveWalletIfNeeded -> {
|
||||
showSaveWalletIfNeeded()
|
||||
}
|
||||
is WalletAction.ChangeWallet -> {
|
||||
changeWallet(walletState, action.scope)
|
||||
}
|
||||
is WalletAction.UserWalletChanged -> Unit
|
||||
is WalletAction.WalletStoresChanged -> {
|
||||
// Cancel update job when new wallet stores received
|
||||
updateWalletStoresJob = scope.launch(Dispatchers.Default) {
|
||||
ifActive { fetchTotalFiatBalance(action.walletStores) }
|
||||
ifActive { findMissedDerivations(action.walletStores) }
|
||||
ifActive { tryToShowAppRatingWarning(action.walletStores) }
|
||||
ifActive { store.state.globalState.topUpController?.walletStoresChanged(action.walletStores) }
|
||||
}
|
||||
}
|
||||
is WalletAction.TotalFiatBalanceChanged -> Unit
|
||||
is WalletAction.PopBackToInitialScreen -> {
|
||||
userWalletsListManager.lockIfLockable()
|
||||
val screen = if (walletState.canSaveUserWallets) {
|
||||
AppScreen.Welcome
|
||||
} else {
|
||||
AppScreen.Home
|
||||
}
|
||||
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(screen))
|
||||
}
|
||||
is WalletAction.ChangeSelectedAddress -> {
|
||||
changeSelectedWalletAddress(action.type, walletState)
|
||||
}
|
||||
is WalletAction.UpdateUserWalletArtwork -> {
|
||||
scope.launch {
|
||||
userWalletsListManager
|
||||
.update(
|
||||
userWalletId = action.walletId,
|
||||
update = { userWallet ->
|
||||
userWallet.copy(
|
||||
artworkUrl = GetCardImageUseCase().invoke(
|
||||
cardId = userWallet.cardId,
|
||||
cardPublicKey = userWallet.scanResponse.card.cardPublicKey,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.doOnSuccess {
|
||||
store.dispatch(
|
||||
WalletAction.SetArtworkUrl(userWalletId = action.walletId, url = it.artworkUrl),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findAmountToSend(currency: Currency, amounts: List<Amount>): Amount? {
|
||||
return amounts.find { amount ->
|
||||
val amountType = amount.type
|
||||
if (amountType is AmountType.Token && currency is Currency.Token) {
|
||||
val token = amountType.token
|
||||
token.symbol == currency.currencySymbol && token.contractAddress == currency.token.contractAddress
|
||||
} else {
|
||||
amount.currencySymbol == currency.currencySymbol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeInitSendStateActionByAmount(
|
||||
amount: Amount,
|
||||
walletStore: WalletStoreModel,
|
||||
walletManager: WalletManager,
|
||||
selectedWalletData: WalletDataModel,
|
||||
): PrepareSendScreen = when (amount.type) {
|
||||
AmountType.Coin ->
|
||||
PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = amount,
|
||||
coinRate = selectedWalletData.fiatRate,
|
||||
)
|
||||
is AmountType.Token -> {
|
||||
PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
|
||||
coinRate = walletStore.blockchainWalletData.fiatRate,
|
||||
tokenAmount = amount,
|
||||
tokenRate = selectedWalletData.fiatRate,
|
||||
)
|
||||
}
|
||||
AmountType.Reserve -> {
|
||||
val exception = IllegalStateException("WalletAction.Send: Reserve can't be sent")
|
||||
FirebaseCrashlytics.getInstance().recordException(exception)
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeInitSendStateActionByCurrency(
|
||||
currency: Currency,
|
||||
amount: Amount,
|
||||
walletStore: WalletStoreModel,
|
||||
walletManager: WalletManager,
|
||||
selectedWalletData: WalletDataModel,
|
||||
): PrepareSendScreen = when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = amount,
|
||||
coinRate = selectedWalletData.fiatRate,
|
||||
)
|
||||
}
|
||||
is Currency.Token -> {
|
||||
PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
|
||||
coinRate = walletStore.blockchainWalletData.fiatRate,
|
||||
tokenAmount = amount,
|
||||
tokenRate = selectedWalletData.fiatRate,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeSelectedWalletAddress(type: AddressType, state: WalletState) {
|
||||
val selectedUserWalletId = userWalletsListManager.selectedUserWalletSync?.walletId.guard {
|
||||
Timber.e("Unable to change selected wallet address, no user wallet selected")
|
||||
return
|
||||
}
|
||||
val selectedCurrency = state.selectedCurrency.guard {
|
||||
Timber.e("Unable to change selected wallet address, no currency selected")
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch(Dispatchers.Default) {
|
||||
walletStoresManager.updateSelectedAddress(selectedUserWalletId, selectedCurrency, type)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchTotalFiatBalance(walletStores: List<WalletStoreModel>) {
|
||||
val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull(walletStores)
|
||||
|
||||
if (totalFiatBalance != null) {
|
||||
store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance))
|
||||
}
|
||||
}
|
||||
|
||||
private fun findMissedDerivations(wallStores: List<WalletStoreModel>) {
|
||||
val missedDerivations = wallStores
|
||||
.filter { store ->
|
||||
store.walletsData.any { it.status is WalletDataModel.MissedDerivation }
|
||||
}
|
||||
.map(WalletStoreModel::blockchainNetwork)
|
||||
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(missedDerivations))
|
||||
}
|
||||
|
||||
private fun tryToShowAppRatingWarning(walletStores: List<WalletStoreModel>) {
|
||||
warningsMiddleware.tryToShowAppRatingWarning(
|
||||
hasNonZeroWallets = walletStores
|
||||
.flatMap { it.walletsData }
|
||||
.any { it.status.amount.isGreaterThan(BigDecimal.ZERO) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun showSaveWalletIfNeeded() {
|
||||
if (preferencesStorage.shouldShowSaveUserWalletScreen &&
|
||||
tangemSdkManager.canUseBiometry &&
|
||||
store.state.navigationState.backStack.lastOrNull() == AppScreen.Wallet
|
||||
) {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet))
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeWallet(state: WalletState, lifecycleScope: LifecycleCoroutineScope) {
|
||||
when {
|
||||
state.canSaveUserWallets -> {
|
||||
Analytics.send(MainScreen.ButtonMyWallets())
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector))
|
||||
}
|
||||
else -> {
|
||||
Analytics.send(MainScreen.ButtonScanCard())
|
||||
store.dispatch(
|
||||
WalletAction.Scan(
|
||||
onScanSuccessEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main),
|
||||
scope = lifecycleScope,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.SignatureCountValidator
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.extensions.hasSignedHashes
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
class WarningsMiddleware {
|
||||
fun handle(action: WalletAction.Warnings, globalState: GlobalState?) {
|
||||
when (action) {
|
||||
is WalletAction.Warnings.Update -> setWarningMessages()
|
||||
is WalletAction.Warnings.CheckIfNeeded -> {
|
||||
showCardWarningsIfNeeded(globalState)
|
||||
val readyToShow = preferencesStorage.appRatingLaunchObserver.isReadyToShow()
|
||||
if (readyToShow) addWarningMessage(warning = WarningMessagesManager.appRatingWarning, autoUpdate = true)
|
||||
}
|
||||
|
||||
is WalletAction.Warnings.CheckHashesCount.VerifyOnlineIfNeeded -> checkHashesCountOnlineIfNeeded()
|
||||
is WalletAction.Warnings.CheckHashesCount.SaveCardId -> {
|
||||
val cardId = globalState?.scanResponse?.card?.cardId
|
||||
cardId?.let { preferencesStorage.usedCardsPrefStorage.scanned(it) }
|
||||
}
|
||||
is WalletAction.Warnings.AppRating.RemindLater -> {
|
||||
preferencesStorage.appRatingLaunchObserver.applyDelayedShowing()
|
||||
}
|
||||
is WalletAction.Warnings.AppRating.SetNeverToShow -> {
|
||||
preferencesStorage.appRatingLaunchObserver.setNeverToShow()
|
||||
}
|
||||
is WalletAction.Warnings.CheckRemainingSignatures -> {
|
||||
if (action.remainingSignatures != null &&
|
||||
action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING
|
||||
) {
|
||||
// store.state.globalState.warningManager?.removeWarnings(R.string.warning_low_signatures_format)
|
||||
addWarningMessage(
|
||||
warning = WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures),
|
||||
autoUpdate = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
is WalletAction.Warnings.AppRating,
|
||||
is WalletAction.Warnings.CheckHashesCount,
|
||||
is WalletAction.Warnings.Set,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
fun tryToShowAppRatingWarning(hasNonZeroWallets: Boolean) {
|
||||
if (hasNonZeroWallets) {
|
||||
preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds()
|
||||
}
|
||||
if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) {
|
||||
addWarningMessage(WarningMessagesManager.appRatingWarning, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCardWarningsIfNeeded(globalState: GlobalState?) {
|
||||
globalState?.scanResponse?.let { scanResponse ->
|
||||
val card = scanResponse.card
|
||||
globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
|
||||
if (card.isTestCard) {
|
||||
addWarningMessage(WarningMessagesManager.testCardWarning, autoUpdate = true)
|
||||
return@let
|
||||
}
|
||||
|
||||
showWarningLowRemainingSignaturesIfNeeded(card)
|
||||
if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) {
|
||||
addWarningMessage(WarningMessagesManager.devCardWarning)
|
||||
} else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) {
|
||||
checkIfWarningNeeded(scanResponse)?.let { warning -> addWarningMessage(warning) }
|
||||
}
|
||||
if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release && !globalState.cardVerifiedOnline) {
|
||||
addWarningMessage(WarningMessagesManager.onlineVerificationFailed)
|
||||
}
|
||||
if (scanResponse.isDemoCard()) {
|
||||
addWarningMessage(WarningMessagesManager.demoCardWarning)
|
||||
}
|
||||
setWarningMessages()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showWarningLowRemainingSignaturesIfNeeded(card: CardDTO) {
|
||||
val remainingSignatures = card.wallets.firstOrNull()?.remainingSignatures
|
||||
if (remainingSignatures != null &&
|
||||
remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING
|
||||
) {
|
||||
addWarningMessage(WarningMessagesManager.remainingSignaturesNotEnough(remainingSignatures))
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfWarningNeeded(scanResponse: ScanResponse): WarningMessage? {
|
||||
if (scanResponse.cardTypesResolver.isTangemTwins() || scanResponse.isDemoCard()) return null
|
||||
|
||||
if (scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
val isBackupForbidden = with(scanResponse.card.settings) { !(isBackupAllowed || isHDWalletAllowed) }
|
||||
return if (scanResponse.card.hasSignedHashes() && isBackupForbidden) {
|
||||
WarningMessagesManager.signedHashesMultiWalletWarning
|
||||
} else {
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return if (scanResponse.card.hasSignedHashes()) {
|
||||
WarningMessagesManager.alreadySignedHashesWarning
|
||||
} else {
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkHashesCountOnlineIfNeeded() {
|
||||
val alreadySignedHashesWarning = WarningMessagesManager.alreadySignedHashesWarning
|
||||
val manager = store.state.globalState.warningManager ?: return
|
||||
if (manager.containsWarning(alreadySignedHashesWarning)) return
|
||||
|
||||
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||
if (!networkConnectionManager.isOnline) return
|
||||
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
val card = scanResponse?.card
|
||||
if (card == null || preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) return
|
||||
|
||||
if (scanResponse.cardTypesResolver.isTangemTwins() || scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
return
|
||||
}
|
||||
|
||||
val validator = store.state.walletState.walletManagers.firstOrNull()
|
||||
as? SignatureCountValidator
|
||||
scope.launch {
|
||||
val signedHashes = card.wallets.firstOrNull()?.totalSignedHashes ?: 0
|
||||
val result = validator?.validateSignatureCount(signedHashes)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
SimpleResult.Success -> {
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
}
|
||||
is SimpleResult.Failure ->
|
||||
if (signedHashes > 0 || result.error is BlockchainSdkError.SignatureCountNotMatched) {
|
||||
alreadySignedHashesWarning.isHidden = false
|
||||
addWarningMessage(alreadySignedHashesWarning, true)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addWarningMessage(warning: WarningMessage, autoUpdate: Boolean = false) {
|
||||
store.state.globalState.warningManager?.addWarning(warning)
|
||||
if (autoUpdate) setWarningMessages()
|
||||
}
|
||||
|
||||
private fun setWarningMessages() {
|
||||
store.dispatchOnMain(WalletAction.Warnings.Set(getWarnings()))
|
||||
}
|
||||
|
||||
private fun getWarnings(): List<WarningMessage> {
|
||||
val warningManager = store.state.globalState.warningManager ?: return emptyList()
|
||||
return warningManager.getWarnings(
|
||||
WarningMessage.Location.MainScreen,
|
||||
store.state.walletState.blockchains,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.models
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.wallet.R
|
||||
|
||||
sealed interface WalletDialog : StateDialog {
|
||||
data class SelectAmountToSendDialog(val amounts: List<Amount>) : WalletDialog
|
||||
object SignedHashesMultiWalletDialog : WalletDialog
|
||||
data class ChooseTradeActionDialog(
|
||||
val buyAllowed: Boolean,
|
||||
val sellAllowed: Boolean,
|
||||
val swapAllowed: Boolean,
|
||||
) : WalletDialog
|
||||
|
||||
data class CurrencySelectionDialog(
|
||||
val currenciesList: List<FiatCurrency>,
|
||||
val currentAppCurrency: FiatCurrency,
|
||||
) : WalletDialog
|
||||
|
||||
data class RemoveWalletDialog(
|
||||
val currencyTitle: String,
|
||||
val onOk: () -> Unit,
|
||||
) : WalletDialog {
|
||||
val messageRes: Int = R.string.token_details_hide_alert_message
|
||||
val titleRes: Int = R.string.token_details_hide_alert_title
|
||||
val primaryButtonRes: Int = R.string.token_details_hide_alert_hide
|
||||
}
|
||||
|
||||
data class TokensAreLinkedDialog(
|
||||
val currencyTitle: String,
|
||||
val currencySymbol: String,
|
||||
) : WalletDialog {
|
||||
val messageRes: Int = R.string.token_details_unable_hide_alert_message
|
||||
val titleRes: Int = R.string.token_details_unable_hide_alert_title
|
||||
}
|
||||
|
||||
data class RussianCardholdersWarningDialog(val data: Data?) : WalletDialog {
|
||||
data class Data(val topUpUrl: String)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
||||
class AppCurrencyReducer {
|
||||
fun reduce(action: WalletAction.AppCurrencyAction, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.AppCurrencyAction.SelectAppCurrency,
|
||||
is WalletAction.AppCurrencyAction.ChooseAppCurrency,
|
||||
-> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
||||
class MultiWalletReducer {
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
state.copy(selectedCurrency = action.currency)
|
||||
}
|
||||
|
||||
is WalletAction.MultiWallet.TryToRemoveWallet -> state
|
||||
is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(
|
||||
missingDerivations = action.blockchains,
|
||||
)
|
||||
|
||||
is WalletAction.MultiWallet.BackupWallet -> state
|
||||
is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy(state = ProgressState.Loading)
|
||||
is WalletAction.MultiWallet.CheckForBackupWarning -> state.copy(
|
||||
showBackupWarning = action.card.settings.isBackupAllowed &&
|
||||
action.card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
)
|
||||
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.userwallets.Artwork
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import org.rekotlin.Action
|
||||
|
||||
object WalletReducer {
|
||||
fun reduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState =
|
||||
internalReduce(action, state, appStateHolder)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun internalReduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState {
|
||||
val multiWalletReducer = MultiWalletReducer()
|
||||
val appCurrencyReducer = AppCurrencyReducer()
|
||||
|
||||
if (action !is WalletAction) return state.walletState
|
||||
|
||||
var newState = state.walletState
|
||||
|
||||
when (action) {
|
||||
is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState)
|
||||
is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState)
|
||||
is WalletAction.LoadData.Failure -> {
|
||||
when (action.error) {
|
||||
is TapError.NoInternetConnection -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Error,
|
||||
error = ErrorType.NoInternetConnection,
|
||||
)
|
||||
}
|
||||
is TapError.UnknownBlockchain -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Error,
|
||||
error = ErrorType.UnknownBlockchain,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Error,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadData -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadData.Refresh -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Refreshing,
|
||||
error = null,
|
||||
)
|
||||
}
|
||||
is WalletAction.AppCurrencyAction -> {
|
||||
newState = appCurrencyReducer.reduce(action, newState)
|
||||
}
|
||||
is WalletAction.UserWalletChanged -> with(action.userWallet) {
|
||||
val card = scanResponse.card
|
||||
newState = WalletState(
|
||||
isMultiwalletAllowed = isMultiCurrency,
|
||||
cardImage = Artwork(
|
||||
artworkId = artworkUrl,
|
||||
),
|
||||
isTestnet = card.isTestCard,
|
||||
state = ProgressState.Loading,
|
||||
showBackupWarning = isMultiCurrency &&
|
||||
card.settings.isBackupAllowed &&
|
||||
card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
walletCardsCount = card.findCardsCount(),
|
||||
walletsStores = newState.walletsStores,
|
||||
totalBalance = if (isMultiCurrency) {
|
||||
newState.totalBalance
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
is WalletAction.WalletStoresChanged -> {
|
||||
newState = newState.copy(
|
||||
walletsStores = action.walletStores,
|
||||
selectedCurrency = findSelectedCurrency(
|
||||
walletsStores = action.walletStores,
|
||||
currentSelectedCurrency = newState.selectedCurrency,
|
||||
isMultiWalletAllowed = newState.isMultiwalletAllowed,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletAction.TotalFiatBalanceChanged -> {
|
||||
newState = newState.copy(
|
||||
totalBalance = action.balance,
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadData.Success -> {
|
||||
newState = newState.copy(state = ProgressState.Done)
|
||||
}
|
||||
is WalletAction.UpdateCanSaveUserWallets -> {
|
||||
newState = newState.copy(canSaveUserWallets = action.canSaveUserWallets)
|
||||
}
|
||||
is WalletAction.SetArtworkUrl -> {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync?.walletId
|
||||
|
||||
if (selectedUserWallet == action.userWalletId) {
|
||||
newState = newState.copy(
|
||||
cardImage = Artwork(artworkId = action.url),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
appStateHolder.walletState = newState
|
||||
return newState
|
||||
}
|
||||
|
||||
fun findSelectedCurrency(
|
||||
walletsStores: List<WalletStoreModel>,
|
||||
currentSelectedCurrency: Currency?,
|
||||
isMultiWalletAllowed: Boolean,
|
||||
): Currency? = if (isMultiWalletAllowed) {
|
||||
currentSelectedCurrency
|
||||
} else {
|
||||
walletsStores.firstOrNull()
|
||||
?.walletsData
|
||||
?.firstOrNull()
|
||||
?.currency
|
||||
}
|
||||
|
||||
private fun CardDTO.findCardsCount(): Int? {
|
||||
return (this.backupStatus as? CardDTO.BackupStatus.Active)?.cardCount?.inc()
|
||||
}
|
||||
|
||||
fun Wallet.createAddressesData(): List<WalletDataModel.AddressData> {
|
||||
val listOfAddressData = mutableListOf<WalletDataModel.AddressData>()
|
||||
// put a defaultAddress at the first place
|
||||
addresses.forEach {
|
||||
val addressData = WalletDataModel.AddressData(
|
||||
it.value,
|
||||
it.type,
|
||||
getShareUri(it.value),
|
||||
getExploreUrl(it.value),
|
||||
)
|
||||
if (it.type == AddressType.Default) {
|
||||
listOfAddressData.add(0, addressData)
|
||||
} else {
|
||||
listOfAddressData.add(addressData)
|
||||
}
|
||||
}
|
||||
return listOfAddressData
|
||||
}
|
||||
|
||||
private fun handleCheckSignedHashesActions(action: WalletAction.Warnings, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.Warnings.Set -> state.copy(mainWarningsList = action.warningList)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.utils
|
||||
|
||||
const val UNKNOWN_AMOUNT_SIGN = "—"
|
||||
const val ROUGH_SIGN = "≈"
|
||||
const val CAN_BE_LOWER_SIGN = "<"
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import androidx.annotation.IdRes
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.wallet.ui.utils.getFormattedCryptoAmount
|
||||
import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.CardBalanceBinding
|
||||
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
class BalanceWidget(
|
||||
private val binding: CardBalanceBinding,
|
||||
private val fragment: WalletFragment,
|
||||
private val blockchainWalletData: WalletDataModel,
|
||||
private val tokenWalletData: WalletDataModel?,
|
||||
) {
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun setup() {
|
||||
when (blockchainWalletData.status) {
|
||||
is WalletDataModel.Loading -> {
|
||||
with(binding) {
|
||||
lBalance.root.show()
|
||||
lBalanceError.root.hide()
|
||||
lBalance.tvFiatAmount.hide()
|
||||
|
||||
lBalance.tvCurrency.text = blockchainWalletData.currency.currencyName
|
||||
lBalance.tvAmount.text = ""
|
||||
}
|
||||
|
||||
showStatus(R.id.tv_status_loading)
|
||||
|
||||
if (tokenWalletData != null) {
|
||||
showBalanceWithToken(blockchainWalletData, false)
|
||||
} else {
|
||||
showBalanceWithoutToken(blockchainWalletData, false)
|
||||
}
|
||||
}
|
||||
is WalletDataModel.VerifiedOnline,
|
||||
is WalletDataModel.TransactionInProgress,
|
||||
-> with(binding.lBalance) {
|
||||
root.show()
|
||||
binding.lBalanceError.root.hide()
|
||||
val statusView = if (blockchainWalletData.status is WalletDataModel.VerifiedOnline) {
|
||||
R.id.tv_status_verified
|
||||
} else {
|
||||
// tvStatusError.text = fragment.getText(R.string.wallet_balance_tx_in_progress)
|
||||
R.id.group_error
|
||||
}
|
||||
showStatus(statusView)
|
||||
// tvStatusErrorMessage.hide()
|
||||
|
||||
if (tokenWalletData != null) {
|
||||
showBalanceWithToken(blockchainWalletData, true)
|
||||
} else {
|
||||
showBalanceWithoutToken(blockchainWalletData, true)
|
||||
}
|
||||
}
|
||||
is WalletDataModel.Unreachable -> with(binding.lBalance) {
|
||||
root.show()
|
||||
binding.lBalanceError.root.hide()
|
||||
tvFiatAmount.hide()
|
||||
groupBaseCurrency.hide()
|
||||
|
||||
val currency = tokenWalletData?.currency?.currencySymbol
|
||||
?: blockchainWalletData.currency.currencyName
|
||||
tvCurrency.text = currency
|
||||
tvAmount.text = ""
|
||||
|
||||
// tvStatusErrorMessage.text = blockchainWalletData.status.errorMessage
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
// tvStatusError.text = fragment.getString(R.string.wallet_balance_blockchain_unreachable)
|
||||
|
||||
showStatus(R.id.group_error)
|
||||
// tvStatusErrorMessage.show(!blockchainWalletData.status.errorMessage.isNullOrBlank())
|
||||
}
|
||||
is WalletDataModel.NoAccount -> with(binding.lBalanceError) {
|
||||
binding.lBalance.root.hide()
|
||||
binding.lBalanceError.root.show()
|
||||
tvErrorTitle.text = fragment.getText(R.string.wallet_error_no_account)
|
||||
tvErrorDescriptions.text =
|
||||
fragment.getString(
|
||||
R.string.no_account_generic,
|
||||
blockchainWalletData.status.amountToCreateAccount,
|
||||
blockchainWalletData.currency.currencySymbol,
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showStatus(@IdRes viewRes: Int) = with(binding.lBalance) {
|
||||
// groupError.show(viewRes == R.id.group_error)
|
||||
tvStatusLoading.show(viewRes == R.id.tv_status_loading)
|
||||
tvStatusVerified.show(viewRes == R.id.tv_status_verified)
|
||||
}
|
||||
|
||||
private fun showBalanceWithToken(data: WalletDataModel, showAmount: Boolean) = with(binding.lBalance) {
|
||||
groupBaseCurrency.show()
|
||||
tvCurrency.text = tokenWalletData?.currency?.currencySymbol
|
||||
tvBaseCurrency.text = data.currency.currencyName
|
||||
tvAmount.text = if (showAmount) tokenWalletData?.getFormattedCryptoAmount() else ""
|
||||
tvBaseAmount.text = if (showAmount) data.getFormattedCryptoAmount() else ""
|
||||
if (showAmount) {
|
||||
tvFiatAmount.show()
|
||||
tvFiatAmount.text = tokenWalletData?.getFormattedFiatAmount(store.state.globalState.appCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBalanceWithoutToken(data: WalletDataModel, showAmount: Boolean) = with(binding.lBalance) {
|
||||
groupBaseCurrency.hide()
|
||||
tvCurrency.text = data.currency.currencyName
|
||||
tvAmount.text = if (showAmount) data.getFormattedCryptoAmount() else ""
|
||||
if (showAmount) {
|
||||
tvFiatAmount.show()
|
||||
tvFiatAmount.text = data.getFormattedFiatAmount(store.state.globalState.appCurrency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.view.View
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object MultipleAddressUiHelper {
|
||||
|
||||
private val blockchainsSupportingSplit = listOf(
|
||||
Blockchain.Bitcoin,
|
||||
Blockchain.BitcoinTestnet,
|
||||
Blockchain.Litecoin,
|
||||
Blockchain.BitcoinCash,
|
||||
Blockchain.Cardano,
|
||||
)
|
||||
|
||||
fun typeToId(type: AddressType, blockchain: Blockchain): Int {
|
||||
return if (blockchain in blockchainsSupportingSplit) {
|
||||
if (type == AddressType.Legacy) {
|
||||
R.id.chip_legacy
|
||||
} else {
|
||||
R.id.chip_default
|
||||
}
|
||||
} else {
|
||||
View.NO_ID
|
||||
}
|
||||
}
|
||||
|
||||
fun idToType(id: Int, blockchain: Blockchain): AddressType? {
|
||||
return when (blockchain) {
|
||||
in blockchainsSupportingSplit -> {
|
||||
when (id) {
|
||||
R.id.chip_default -> AddressType.Default
|
||||
R.id.chip_legacy -> AddressType.Legacy
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,509 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.badoo.mvicore.DiffStrategy
|
||||
import com.badoo.mvicore.ModelWatcher
|
||||
import com.badoo.mvicore.modelWatcher
|
||||
import com.tangem.common.doOnResult
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.sdk.extensions.dpToPx
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.common.analytics.events.DetailsScreen
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.utils.SafeStoreSubscriber
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.models.WalletWarning
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter
|
||||
import com.tangem.tap.features.wallet.ui.images.load
|
||||
import com.tangem.tap.features.wallet.ui.test.TestWallet
|
||||
import com.tangem.tap.features.wallet.ui.utils.*
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManagerSafe
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentWalletDetailsBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Wallet details fragment - use only for MultiWallet
|
||||
*/
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Suppress("LargeClass", "MagicNumber")
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
@AndroidEntryPoint
|
||||
class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), SafeStoreSubscriber<WalletState> {
|
||||
|
||||
@Inject
|
||||
lateinit var swapInteractor: SwapInteractor
|
||||
|
||||
@Inject
|
||||
lateinit var swapFeatureToggleManager: SwapFeatureToggleManager
|
||||
|
||||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||
private lateinit var warningMessagesAdapter: WalletDetailWarningMessagesAdapter
|
||||
|
||||
private val binding: FragmentWalletDetailsBinding by viewBinding(FragmentWalletDetailsBinding::bind)
|
||||
|
||||
private val walletDataWatcher: ModelWatcher<WalletDataModel> = modelWatcher {
|
||||
val addressCardStrategy: DiffStrategy<WalletDataModel> = { old, new ->
|
||||
old.currency != new.currency || old.walletAddresses != new.walletAddresses
|
||||
}
|
||||
|
||||
WalletDataModel::currency {
|
||||
handleCurrencyIcon(it)
|
||||
}
|
||||
WalletDataModel::walletAddresses { walletAddresses ->
|
||||
setupCopyAndShareButtons(walletAddresses?.selectedAddress?.address)
|
||||
}
|
||||
WalletDataModel::currency { currency ->
|
||||
setupCurrency(currency)
|
||||
}
|
||||
watch({ it }, addressCardStrategy) { walletData ->
|
||||
setupAddressCard(
|
||||
shouldShowMultipleAddress = walletData.shouldShowMultipleAddress(),
|
||||
selectedAddress = walletData.walletAddresses?.selectedAddress,
|
||||
currency = walletData.currency,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val walletStateWatcher: ModelWatcher<WalletState> = modelWatcher {
|
||||
val walletDataStrategy: DiffStrategy<WalletState> = { old, new ->
|
||||
new.walletsStores.isNotEmpty() &&
|
||||
new.selectedCurrency != null &&
|
||||
(old.selectedCurrency != new.selectedCurrency || old.walletsStores != new.walletsStores)
|
||||
}
|
||||
|
||||
watch({ it }, walletDataStrategy) { state ->
|
||||
val selectedWallet = state.selectedWalletData
|
||||
if (selectedWallet != null) {
|
||||
setupBalanceData(selectedWallet)
|
||||
setupSwipeRefresh(selectedWallet)
|
||||
walletDataWatcher.invoke(selectedWallet)
|
||||
|
||||
val walletStore = state.getWalletStore(state.selectedCurrency)
|
||||
if (walletStore != null) {
|
||||
handleWarnings(
|
||||
selectedWallet.assembleWarnings(
|
||||
blockchainAmount = walletStore.blockchainWalletData.status.amount,
|
||||
blockchainWalletRent = walletStore.walletRent,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
(WalletState::selectedWalletData or WalletState::isExchangeServiceFeatureOn) { state ->
|
||||
val selectedWallet = state.selectedWalletData
|
||||
if (selectedWallet != null) {
|
||||
val blockchainAmount: BigDecimal = state.getBlockchainAmount(selectedWallet.currency)
|
||||
setupButtonsRow(selectedWallet, state.isExchangeServiceFeatureOn, blockchainAmount)
|
||||
}
|
||||
}
|
||||
(WalletState::state or WalletState::error) { state ->
|
||||
setupNoInternetHandling(state.state, state.error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
Analytics.send(DetailsScreen.ScreenOpened())
|
||||
activity?.onBackPressedDispatcher?.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
|
||||
setupTransactionsRecyclerView()
|
||||
setupButtons()
|
||||
setupWarningsRecyclerView()
|
||||
setupTestActionButton()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state -> state.select(AppState::walletState) }
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
clearWatchers()
|
||||
}
|
||||
|
||||
private fun setupTransactionsRecyclerView() = with(binding) {
|
||||
pendingTransactionAdapter = PendingTransactionsAdapter()
|
||||
rvPendingTransaction.layoutManager = LinearLayoutManager(requireContext())
|
||||
rvPendingTransaction.adapter = pendingTransactionAdapter
|
||||
}
|
||||
|
||||
private fun setupWarningsRecyclerView() = with(binding) {
|
||||
warningMessagesAdapter = WalletDetailWarningMessagesAdapter()
|
||||
rvWarningMessages.layoutManager = LinearLayoutManager(requireContext())
|
||||
rvWarningMessages.adapter = warningMessagesAdapter
|
||||
rvWarningMessages.addItemDecoration(SpaceItemDecoration.vertical(8f))
|
||||
}
|
||||
|
||||
private fun setupButtons() {
|
||||
binding.rowButtons.onSendClick = { store.dispatch(WalletAction.Send()) }
|
||||
}
|
||||
|
||||
private fun setupTestActionButton() {
|
||||
view?.findViewById<View>(R.id.l_balance)?.let { view ->
|
||||
TestActions.initFor(view = view, actions = TestWallet.solanaRentExemptWarning())
|
||||
}
|
||||
}
|
||||
|
||||
override fun newStateOnMain(state: WalletState) {
|
||||
if (activity == null || view == null) return
|
||||
if (state.selectedWalletData == null) return
|
||||
walletStateWatcher.invoke(state)
|
||||
|
||||
updateViewMeasurements()
|
||||
}
|
||||
|
||||
private fun updateViewMeasurements() {
|
||||
val tvFiatAmount = binding.lWalletDetails.lBalance.tvFiatAmount
|
||||
val paddingStart = if (tvFiatAmount.text == UNKNOWN_AMOUNT_SIGN) 16f else 12f
|
||||
|
||||
tvFiatAmount.setPadding(
|
||||
tvFiatAmount.dpToPx(paddingStart).toInt(),
|
||||
tvFiatAmount.paddingTop,
|
||||
tvFiatAmount.paddingEnd,
|
||||
tvFiatAmount.paddingBottom,
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupCurrency(currency: Currency) = with(binding) {
|
||||
tvCurrencyTitle.text = currency.currencyName
|
||||
|
||||
if (currency is Currency.Token) {
|
||||
tvCurrencySubtitle.text = tvCurrencySubtitle.getString(
|
||||
R.string.wallet_currency_subtitle,
|
||||
currency.blockchain.fullName,
|
||||
)
|
||||
tvCurrencySubtitle.show()
|
||||
} else {
|
||||
tvCurrencySubtitle.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSwipeRefresh(walletData: WalletDataModel) {
|
||||
binding.srlWalletDetails.setOnRefreshListener {
|
||||
if (walletData.status !is WalletDataModel.Loading) {
|
||||
Analytics.send(Token.Refreshed())
|
||||
val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to refresh wallet details screen, no user wallet selected")
|
||||
return@setOnRefreshListener
|
||||
}
|
||||
binding.srlWalletDetails.isRefreshing = true
|
||||
lifecycleScope.launch(Dispatchers.Default) {
|
||||
walletCurrenciesManager.update(selectedUserWallet, walletData.currency).doOnResult {
|
||||
withMainContext {
|
||||
binding.srlWalletDetails.isRefreshing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCopyAndShareButtons(walletAddress: String?) {
|
||||
binding.lWalletDetails.btnCopy.setOnClickListener {
|
||||
if (walletAddress != null) store.dispatch(WalletAction.CopyAddress(walletAddress, requireContext()))
|
||||
}
|
||||
|
||||
binding.lWalletDetails.btnShare.setOnClickListener {
|
||||
if (walletAddress != null) store.dispatch(WalletAction.ShareAddress(walletAddress, requireContext()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtonsRow(
|
||||
selectedWallet: WalletDataModel,
|
||||
isExchangeServiceFeatureOn: Boolean,
|
||||
blockchainAmount: BigDecimal,
|
||||
) {
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
binding.rowButtons.apply {
|
||||
onBuyClick = { store.dispatch(TradeCryptoAction.Buy()) }
|
||||
onSellClick = { store.dispatch(TradeCryptoAction.Sell) }
|
||||
onSwapClick = { store.dispatch(TradeCryptoAction.Swap) }
|
||||
onTradeClick = {
|
||||
store.dispatch(
|
||||
WalletAction.DialogAction.ChooseTradeActionDialog(
|
||||
buyAllowed = selectedWallet.isAvailableToBuy(exchangeManager),
|
||||
sellAllowed = selectedWallet.isAvailableToSell(exchangeManager),
|
||||
swapAllowed = selectedWallet.isAvailableToSwap(
|
||||
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||
swapInteractor = swapInteractor,
|
||||
isSingleWallet = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
val actions = selectedWallet.getAvailableActions(
|
||||
swapInteractor = swapInteractor,
|
||||
exchangeManager = exchangeManager,
|
||||
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||
isSingleWallet = false,
|
||||
)
|
||||
binding.rowButtons.updateButtonsVisibility(
|
||||
actions = actions,
|
||||
exchangeServiceFeatureOn = isExchangeServiceFeatureOn,
|
||||
sendAllowed = selectedWallet.mainButton(blockchainAmount).enabled,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleWarnings(warnings: List<WalletWarning>) = with(binding) {
|
||||
val converter = WalletWarningConverter(requireContext())
|
||||
val warningDetails = warnings.map { converter.convert(it) }
|
||||
|
||||
warningMessagesAdapter.submitList(warningDetails)
|
||||
rvWarningMessages.show(warningDetails.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun handleCurrencyIcon(currency: Currency) = with(binding.lWalletDetails.lBalance) {
|
||||
ivCurrency.load(
|
||||
currency = currency,
|
||||
derivationStyle = store.state.globalState.scanResponse
|
||||
?.derivationStyleProvider?.getDerivationStyle(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
|
||||
val knownTransactions = pendingTransactions.filterNot { it.type == PendingTransactionType.Unknown }
|
||||
pendingTransactionAdapter.submitList(knownTransactions)
|
||||
binding.rvPendingTransaction.show(knownTransactions.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun setupAddressCard(
|
||||
shouldShowMultipleAddress: Boolean,
|
||||
selectedAddress: WalletDataModel.AddressData?,
|
||||
currency: Currency,
|
||||
) = with(binding.lWalletDetails) {
|
||||
if (selectedAddress == null) return@with
|
||||
|
||||
setupAddressTypeChips(shouldShowMultipleAddress, selectedAddress, currency)
|
||||
|
||||
tvAddress.text = selectedAddress.address
|
||||
tvExplore.setOnClickListener {
|
||||
store.dispatch(WalletAction.ExploreAddress(selectedAddress.exploreUrl, requireContext()))
|
||||
}
|
||||
ivQrCode.setImageBitmap(selectedAddress.shareUrl.toQrCode())
|
||||
|
||||
tvReceiveMessage.text = when (currency) {
|
||||
is Currency.Blockchain -> tvReceiveMessage.getString(
|
||||
id = R.string.address_qr_code_message_format,
|
||||
currency.blockchain.fullName,
|
||||
currency.currencySymbol,
|
||||
currency.blockchain.fullName,
|
||||
)
|
||||
is Currency.Token -> tvReceiveMessage.getString(
|
||||
id = R.string.address_qr_code_message_format,
|
||||
currency.token.name,
|
||||
currency.currencySymbol,
|
||||
currency.blockchain.fullName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAddressTypeChips(
|
||||
shouldShowMultipleAddress: Boolean,
|
||||
selectedAddress: WalletDataModel.AddressData,
|
||||
currency: Currency,
|
||||
) = with(binding.lWalletDetails) {
|
||||
if (shouldShowMultipleAddress && currency is Currency.Blockchain) {
|
||||
(cardBalance as? ViewGroup)?.beginDelayedTransition()
|
||||
chipGroupAddressType.show()
|
||||
chipGroupAddressType.fitChipsByGroupWidth()
|
||||
|
||||
val checkedId = MultipleAddressUiHelper.typeToId(selectedAddress.type, currency.blockchain)
|
||||
if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
|
||||
|
||||
chipGroupAddressType.setOnCheckedChangeListener { _, checkedId ->
|
||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||
val type =
|
||||
MultipleAddressUiHelper.idToType(checkedId, currency.blockchain)
|
||||
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
|
||||
}
|
||||
} else {
|
||||
chipGroupAddressType.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNoInternetHandling(progressState: ProgressState, errorType: ErrorType?) {
|
||||
if (progressState == ProgressState.Error) {
|
||||
if (errorType == ErrorType.NoInternetConnection) {
|
||||
binding.srlWalletDetails.isRefreshing = false
|
||||
(activity as? SnackbarHandler)?.showSnackbar(
|
||||
text = R.string.wallet_notification_no_internet,
|
||||
buttonTitle = R.string.common_retry,
|
||||
) { store.dispatch(WalletAction.LoadData) }
|
||||
}
|
||||
} else {
|
||||
(activity as? SnackbarHandler)?.dismissSnackbar()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupBalanceData(walletData: WalletDataModel) = with(binding.lWalletDetails) {
|
||||
when (val status = walletData.status) {
|
||||
is WalletDataModel.Loading -> {
|
||||
lBalanceError.root.hide()
|
||||
lBalance.root.show()
|
||||
lBalance.groupBalance.show()
|
||||
lBalance.tvError.hide()
|
||||
lBalance.tvAmount.text = walletData.getFormattedCryptoAmount()
|
||||
lBalance.tvFiatAmount.text = walletData.getFormattedFiatAmount(store.state.globalState.appCurrency)
|
||||
lBalance.tvStatus.setLoadingStatus(R.string.wallet_balance_loading)
|
||||
}
|
||||
is WalletDataModel.VerifiedOnline,
|
||||
is WalletDataModel.SameCurrencyTransactionInProgress,
|
||||
is WalletDataModel.TransactionInProgress,
|
||||
-> {
|
||||
lBalanceError.root.hide()
|
||||
lBalance.root.show()
|
||||
lBalance.groupBalance.show()
|
||||
lBalance.tvError.hide()
|
||||
lBalance.tvAmount.text = walletData.getFormattedCryptoAmount()
|
||||
lBalance.tvFiatAmount.text = walletData.getFormattedFiatAmount(store.state.globalState.appCurrency)
|
||||
when (status) {
|
||||
is WalletDataModel.VerifiedOnline,
|
||||
is WalletDataModel.SameCurrencyTransactionInProgress,
|
||||
-> {
|
||||
lBalance.tvStatus.setVerifiedBalanceStatus(R.string.wallet_balance_verified)
|
||||
showPendingTransactionsIfPresent(status.pendingTransactions)
|
||||
}
|
||||
|
||||
is WalletDataModel.TransactionInProgress -> {
|
||||
lBalance.tvStatus.setWarningStatus(R.string.wallet_balance_tx_in_progress)
|
||||
showPendingTransactionsIfPresent(status.pendingTransactions)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
is WalletDataModel.Unreachable -> {
|
||||
lBalanceError.root.hide()
|
||||
lBalance.root.show()
|
||||
lBalance.groupBalance.hide()
|
||||
lBalance.tvError.show()
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
// lBalance.tvError.setWarningStatus(
|
||||
// R.string.wallet_balance_blockchain_unreachable,
|
||||
// status.errorMessage,
|
||||
// )
|
||||
}
|
||||
is WalletDataModel.NoAccount -> {
|
||||
lBalance.root.hide()
|
||||
lBalanceError.root.show()
|
||||
lBalanceError.tvErrorTitle.text = getText(R.string.wallet_error_no_account)
|
||||
lBalanceError.tvErrorDescriptions.text =
|
||||
getString(
|
||||
R.string.no_account_generic,
|
||||
status.amountToCreateAccount,
|
||||
walletData.currency.currencySymbol,
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_remove -> {
|
||||
store.state.walletState.selectedWalletData?.let { walletData ->
|
||||
store.dispatch(WalletAction.MultiWallet.TryToRemoveWallet(walletData.currency))
|
||||
true
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Deprecated in Java",
|
||||
replaceWith = ReplaceWith("inflater.inflate(R.menu.menu_wallet_details, menu)", "com.tangem.wallet.R"),
|
||||
)
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_wallet_details, menu)
|
||||
}
|
||||
|
||||
private fun clearWatchers() {
|
||||
walletDataWatcher.clear()
|
||||
walletStateWatcher.clear()
|
||||
}
|
||||
|
||||
private fun TextView.setWarningStatus(mainMessage: Int, error: String? = null) {
|
||||
val text = getString(mainMessage).appendIfNotNull(error, "\nError: ")
|
||||
setStatus(text, R.color.warning, R.drawable.ic_warning_small)
|
||||
}
|
||||
|
||||
private fun TextView.setVerifiedBalanceStatus(mainMessage: Int) {
|
||||
setStatus(getString(mainMessage), R.color.accent, R.drawable.ic_ok)
|
||||
}
|
||||
|
||||
private fun TextView.setLoadingStatus(mainMessage: Int) {
|
||||
setStatus(getString(mainMessage), R.color.darkGray4, null)
|
||||
}
|
||||
|
||||
private fun TextView.setStatus(text: String, @ColorRes color: Int, @DrawableRes drawable: Int?) {
|
||||
this.text = text
|
||||
setTextColor(getColor(color))
|
||||
setCompoundDrawablesWithIntrinsicBounds(drawable ?: 0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,326 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import coil.load
|
||||
import coil.size.Scale
|
||||
import com.badoo.mvicore.modelWatcher
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.core.ui.utils.OneTouchClickListener
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.tap.MainActivity
|
||||
import com.tangem.tap.common.analytics.events.Portfolio
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.utils.SafeStoreSubscriber
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.statePrinter.printScanResponseState
|
||||
import com.tangem.tap.domain.statePrinter.printWalletState
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView
|
||||
import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView
|
||||
import com.tangem.tap.features.wallet.ui.wallet.WalletView
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentWalletBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<WalletState> {
|
||||
|
||||
@Inject
|
||||
lateinit var swapInteractor: SwapInteractor
|
||||
|
||||
@Inject
|
||||
lateinit var swapFeatureToggleManager: SwapFeatureToggleManager
|
||||
|
||||
@Inject
|
||||
lateinit var networkConnectionManager: NetworkConnectionManager
|
||||
|
||||
private lateinit var warningsAdapter: WarningMessagesAdapter
|
||||
|
||||
private val binding: FragmentWalletBinding by viewBinding(FragmentWalletBinding::bind)
|
||||
|
||||
private var walletView: WalletView = MultiWalletView()
|
||||
|
||||
private val viewModel by viewModels<WalletViewModel>()
|
||||
|
||||
private val totalBalanceWatcher = modelWatcher {
|
||||
(WalletState::totalBalance) { totalBalance ->
|
||||
totalBalance?.let {
|
||||
viewModel.onBalanceLoaded(totalBalance)
|
||||
store.state.globalState.topUpController?.totalBalanceStateChanged(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val isNetworkConnectionError = mutableStateOf(false)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
activity?.lifecycle?.addObserver(viewModel)
|
||||
|
||||
activity?.onBackPressedDispatcher?.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(WalletAction.PopBackToInitialScreen)
|
||||
}
|
||||
},
|
||||
)
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
setStatusBarColor(R.color.background_secondary)
|
||||
|
||||
subscribeOnNetworkStateChanging()
|
||||
|
||||
store.subscribe(this) { state ->
|
||||
state.select { it.walletState }
|
||||
}
|
||||
walletView.setFragment(this, binding)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
walletView.removeFragment()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
walletView.onDestroyFragment()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
|
||||
binding.toolbar.setNavigationOnClickListener(
|
||||
OneTouchClickListener {
|
||||
store.dispatch(WalletAction.ChangeWallet(scope = requireActivity().lifecycleScope))
|
||||
},
|
||||
)
|
||||
setupWarningsRecyclerView()
|
||||
walletView.changeWalletView(this, binding)
|
||||
addCustomActionOnCard()
|
||||
}
|
||||
|
||||
private fun addCustomActionOnCard() {
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
|
||||
binding.ivCard.setOnClickListener {
|
||||
printScanResponseState()
|
||||
printWalletState()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun setupWarningsRecyclerView() {
|
||||
warningsAdapter = WarningMessagesAdapter()
|
||||
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||
with(binding) {
|
||||
rvWarningMessages.layoutManager = layoutManager
|
||||
rvWarningMessages.addItemDecoration(SpaceItemDecoration.all(16f))
|
||||
rvWarningMessages.adapter = warningsAdapter
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
override fun newStateOnMain(state: WalletState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
when {
|
||||
state.isMultiwalletAllowed && walletView !is MultiWalletView -> {
|
||||
walletView.onViewDestroy()
|
||||
walletView = MultiWalletView()
|
||||
walletView.changeWalletView(this, binding)
|
||||
}
|
||||
!state.isMultiwalletAllowed && walletView !is SingleWalletView -> {
|
||||
walletView.onViewDestroy()
|
||||
walletView = SingleWalletView()
|
||||
walletView.changeWalletView(this, binding)
|
||||
}
|
||||
else -> {} // we keep the same view unless we scan a card that requires a different view
|
||||
}
|
||||
totalBalanceWatcher.invoke(state)
|
||||
|
||||
walletView.swapInteractor = swapInteractor
|
||||
walletView.swapFeatureToggleManager = swapFeatureToggleManager
|
||||
|
||||
walletView.onNewState(state)
|
||||
|
||||
if (binding.toolbar.menu.findItem(R.id.details_menu) == null) {
|
||||
binding.toolbar.inflateMenu(R.menu.menu_wallet)
|
||||
}
|
||||
|
||||
setupCardImage(state)
|
||||
|
||||
showWarningsIfPresent(state.mainWarningsList)
|
||||
|
||||
setupPullToRefreshLayout(state)
|
||||
|
||||
binding.toolbar.setNavigationIcon(
|
||||
if (state.canSaveUserWallets) R.drawable.ic_wallet_24 else R.drawable.ic_tap_card_24,
|
||||
)
|
||||
|
||||
// showLearn2earnView()
|
||||
}
|
||||
|
||||
// private fun showLearn2earnView() {
|
||||
// val isShowing = learn2earnViewModel.uiState.mainScreenState.isVisible
|
||||
// if (!isShowing) return
|
||||
//
|
||||
// binding.composeLearnToEarnContainer.show(true) { binding.llWarnings.beginDelayedTransition() }
|
||||
// binding.composeLearnToEarnContainer.apply {
|
||||
// setViewCompositionStrategy(
|
||||
// strategy = ViewCompositionStrategy.DisposeOnLifecycleDestroyed(
|
||||
// lifecycle = [REDACTED_EMAIL],
|
||||
// ),
|
||||
// )
|
||||
// setContent {
|
||||
// TangemTheme {
|
||||
// Learn2earnMainPageScreen(learn2earnViewModel.uiState)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun setupPullToRefreshLayout(state: WalletState) {
|
||||
setupErrorPullToRefreshState(state)
|
||||
|
||||
binding.pullToRefreshLayout.isRefreshing = state.state == ProgressState.Refreshing
|
||||
|
||||
binding.pullToRefreshLayout.setOnRefreshListener {
|
||||
if (state.state != ProgressState.Loading && state.state != ProgressState.Refreshing) {
|
||||
refreshWalletData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupErrorPullToRefreshState(state: WalletState) {
|
||||
if (state.state == ProgressState.Error) {
|
||||
when (state.error) {
|
||||
ErrorType.NoInternetConnection -> {
|
||||
isNetworkConnectionError.value = true
|
||||
binding.pullToRefreshLayout.isRefreshing = false
|
||||
|
||||
(activity as? MainActivity)?.showSnackbar(
|
||||
text = R.string.wallet_notification_no_internet,
|
||||
buttonTitle = R.string.common_retry,
|
||||
)
|
||||
// because was added logic of autoupdate mainscreen data, remove retry
|
||||
// TODO("remove comment after release 4.6")
|
||||
// { store.dispatch(WalletAction.LoadData) }
|
||||
}
|
||||
else -> isNetworkConnectionError.value = false
|
||||
}
|
||||
} else {
|
||||
isNetworkConnectionError.value = false
|
||||
(activity as? MainActivity)?.dismissSnackbar()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshWalletData() {
|
||||
Analytics.send(Portfolio.Refreshed())
|
||||
store.dispatch(WalletAction.LoadData.Refresh)
|
||||
// learn2earnViewModel.onMainScreenRefreshed()
|
||||
}
|
||||
|
||||
private fun showWarningsIfPresent(warnings: List<WarningMessage>) {
|
||||
warningsAdapter.submitList(warnings)
|
||||
binding.rvWarningMessages.show(warnings.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun setupCardImage(state: WalletState) {
|
||||
binding.ivCard.load(state.cardImage?.artworkId) {
|
||||
scale(Scale.FIT)
|
||||
crossfade(enable = true)
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnNetworkStateChanging() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
networkConnectionManager.isOnlineFlow
|
||||
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
|
||||
.distinctUntilChanged()
|
||||
.collect { isOnline ->
|
||||
if (isOnline) {
|
||||
(activity as? MainActivity)?.dismissSnackbar()
|
||||
} else {
|
||||
isNetworkConnectionError.value = true
|
||||
binding.pullToRefreshLayout.isRefreshing = false
|
||||
(activity as? MainActivity)?.showSnackbar(
|
||||
text = R.string.wallet_notification_no_internet,
|
||||
buttonTitle = R.string.common_retry,
|
||||
)
|
||||
}
|
||||
if (isOnline && isNetworkConnectionError.value) {
|
||||
refreshWalletData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.details_menu -> {
|
||||
store.dispatch(GlobalAction.UpdateFeedbackInfo(store.state.walletState.walletManagers))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Details))
|
||||
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Deprecated in Java",
|
||||
replaceWith = ReplaceWith(
|
||||
"if (store.state.walletState.shouldShowDetails) inflater.inflate(R.menu.menu_wallet, menu)",
|
||||
"com.tangem.tap.store",
|
||||
"com.tangem.wallet.R",
|
||||
),
|
||||
)
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_wallet, menu)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.ui.analytics.WalletAnalyticsEventsMapper
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Kill me, please
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltViewModel
|
||||
internal class WalletViewModel @Inject constructor(
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : ViewModel(), StoreSubscriber<UserWalletsListManager?>, DefaultLifecycleObserver {
|
||||
private var observeWalletStoresUpdatesJob: Job? = null
|
||||
set(value) {
|
||||
field?.cancel()
|
||||
field = value
|
||||
}
|
||||
|
||||
private val walletAnalyticsEventsMapper = WalletAnalyticsEventsMapper()
|
||||
|
||||
init {
|
||||
subscribeToUserWalletsListManagerUpdates()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: UserWalletsListManager?) {
|
||||
// Restarting observing of wallet store updates when the manager changes
|
||||
if (state != null) {
|
||||
bootstrapSelectedWalletStoresChanges(state)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
launch()
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
if (scanResponse != null) {
|
||||
val currency = ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)
|
||||
val signInType = store.state.signInState.type
|
||||
if (currency != null && signInType != null) {
|
||||
analyticsEventHandler.send(
|
||||
Basic.SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = signInType,
|
||||
walletsCount = store.state.globalState.userWalletsListManager?.walletsCount.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
analyticsEventHandler.send(MainScreen.ScreenOpened())
|
||||
}
|
||||
|
||||
fun onBalanceLoaded(totalBalance: TotalFiatBalance?) {
|
||||
if (totalBalance != null) {
|
||||
walletAnalyticsEventsMapper.convert(totalBalance)?.let { balanceParam ->
|
||||
analyticsEventHandler.send(
|
||||
Basic.BalanceLoaded(
|
||||
balance = balanceParam,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launch() {
|
||||
val manager = store.state.globalState.userWalletsListManager
|
||||
if (manager != null) {
|
||||
bootstrapSelectedWalletStoresChanges(manager)
|
||||
}
|
||||
bootstrapShowSaveWalletIfNeeded()
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) {
|
||||
observeWalletStoresUpdatesJob = manager.selectedUserWallet
|
||||
.map { it.walletId }
|
||||
.flatMapLatest(walletStoresManager::get)
|
||||
.debounce { walletStores ->
|
||||
if (walletStores.isNotEmpty()) WALLET_STORES_DEBOUNCE_TIMEOUT else 0
|
||||
}
|
||||
.onEach { walletStores ->
|
||||
store.dispatchOnMain(WalletAction.WalletStoresChanged(walletStores))
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun bootstrapShowSaveWalletIfNeeded() {
|
||||
viewModelScope.launch {
|
||||
delay(timeMillis = 1_800)
|
||||
store.dispatchOnMain(WalletAction.ShowSaveWalletIfNeeded)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToUserWalletsListManagerUpdates() {
|
||||
store.subscribe(this) { appState ->
|
||||
appState
|
||||
.skip { old, new ->
|
||||
old.globalState.userWalletsListManager == new.globalState.userWalletsListManager
|
||||
}
|
||||
.select { it.globalState.userWalletsListManager }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val WALLET_STORES_DEBOUNCE_TIMEOUT = 100L
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.common.module.ModuleMessageConverter
|
||||
import com.tangem.tap.features.wallet.models.WalletWarning
|
||||
import com.tangem.tap.features.wallet.models.WalletWarningDescription
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
class WalletWarningConverter(
|
||||
private val context: Context,
|
||||
) : ModuleMessageConverter<WalletWarning, WalletWarningDescription> {
|
||||
|
||||
override fun convert(message: WalletWarning): WalletWarningDescription {
|
||||
// val warningMessage = when (message) {
|
||||
// is WalletWarning.ExistentialDeposit -> {
|
||||
// context.getString(
|
||||
// R.string.warning_existential_deposit_message,
|
||||
// message.currencyName,
|
||||
// message.edStringValueWithSymbol,
|
||||
// )
|
||||
// }
|
||||
// is WalletWarning.BalanceNotEnoughForFee -> {
|
||||
// context.getString(
|
||||
// R.string.token_details_send_blocked_fee_format,
|
||||
// message.currencyName,
|
||||
// message.blockchainFullName,
|
||||
// message.currencyName,
|
||||
// message.blockchainFullName,
|
||||
// message.blockchainSymbol,
|
||||
// )
|
||||
// }
|
||||
// is WalletWarning.TransactionInProgress -> {
|
||||
// context.getString(
|
||||
// R.string.token_details_send_blocked_tx_format,
|
||||
// message.currencyName,
|
||||
// )
|
||||
// }
|
||||
// is WalletWarning.Rent -> {
|
||||
// context.getString(
|
||||
// R.string.solana_rent_warning,
|
||||
// message.walletRent.rent,
|
||||
// message.walletRent.exemptionAmount,
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
return WalletWarningDescription(context.getString(R.string.common_warning), "")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ItemPendingTransactionBinding
|
||||
|
||||
class PendingTransactionsAdapter :
|
||||
ListAdapter<PendingTransaction, PendingTransactionsAdapter.TransactionsViewHolder>(DiffUtilCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionsViewHolder {
|
||||
val binding = ItemPendingTransactionBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false,
|
||||
)
|
||||
return TransactionsViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TransactionsViewHolder, position: Int) {
|
||||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<PendingTransaction>() {
|
||||
override fun areContentsTheSame(oldItem: PendingTransaction, newItem: PendingTransaction) = oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(oldItem: PendingTransaction, newItem: PendingTransaction) = oldItem == newItem
|
||||
}
|
||||
|
||||
class TransactionsViewHolder(val binding: ItemPendingTransactionBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(transaction: PendingTransaction) {
|
||||
if (transaction.type == PendingTransactionType.Unknown) {
|
||||
binding.root.hide()
|
||||
}
|
||||
|
||||
val transactionDescriptionRes = when (transaction.type) {
|
||||
PendingTransactionType.Incoming -> R.string.wallet_pending_tx_receiving
|
||||
PendingTransactionType.Outgoing -> R.string.wallet_pending_tx_sending
|
||||
PendingTransactionType.Unknown -> return
|
||||
}
|
||||
val transactionAddressRes = when (transaction.type) {
|
||||
PendingTransactionType.Incoming -> R.string.wallet_pending_tx_receiving_address_format
|
||||
PendingTransactionType.Outgoing -> R.string.wallet_pending_tx_sending_address_format
|
||||
PendingTransactionType.Unknown -> return
|
||||
}
|
||||
val image = when (transaction.type) {
|
||||
PendingTransactionType.Incoming -> R.drawable.ic_arrow_left
|
||||
PendingTransactionType.Outgoing -> R.drawable.ic_arrow_right_20
|
||||
PendingTransactionType.Unknown -> return
|
||||
}
|
||||
binding.tvPendingTransaction.text =
|
||||
binding.root.getString(transactionDescriptionRes).let { "$it " }
|
||||
|
||||
transaction.amountValueUi?.let { binding.tvPendingTransactionAmount.text = "$it " }
|
||||
binding.tvPendingTransactionCurrency.text = transaction.currency
|
||||
|
||||
if (transaction.address != null) {
|
||||
binding.tvPendingTransactionAddress.text =
|
||||
binding.root.getString(transactionAddressRes, transaction.address)
|
||||
}
|
||||
binding.ivPendingTransaction.setImageDrawable(binding.root.context.getDrawableCompat(image))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.tap.common.analytics.events.Portfolio
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.ui.images.load
|
||||
import com.tangem.tap.features.wallet.ui.utils.getFormattedCryptoAmount
|
||||
import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount
|
||||
import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatRate
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ItemCurrencyWalletBinding
|
||||
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
class WalletAdapter : ListAdapter<WalletDataModel, WalletAdapter.WalletsViewHolder>(DiffUtilCallback) {
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return currentList[position].currency.currencySymbol.hashCode().toLong()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder {
|
||||
val layout = ItemCurrencyWalletBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false,
|
||||
)
|
||||
return WalletsViewHolder(layout)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: WalletsViewHolder, position: Int) {
|
||||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<WalletDataModel>() {
|
||||
override fun areContentsTheSame(oldItem: WalletDataModel, newItem: WalletDataModel) = oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(oldItem: WalletDataModel, newItem: WalletDataModel) = oldItem == newItem
|
||||
}
|
||||
|
||||
class WalletsViewHolder(val binding: ItemCurrencyWalletBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(wallet: WalletDataModel) = with(binding) {
|
||||
val status = wallet.status
|
||||
val fiatCurrency = store.state.globalState.appCurrency
|
||||
|
||||
val statusMessage = when (status) {
|
||||
is WalletDataModel.TransactionInProgress -> {
|
||||
root.getString(R.string.wallet_balance_tx_in_progress)
|
||||
}
|
||||
is WalletDataModel.Unreachable -> {
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
// root.getString(R.string.wallet_balance_blockchain_unreachable)
|
||||
}
|
||||
is WalletDataModel.MissedDerivation -> {
|
||||
root.getString(R.string.wallet_balance_missing_derivation)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (status is WalletDataModel.Loading) {
|
||||
lContent.root.hide()
|
||||
lShimmer.root.veil()
|
||||
} else {
|
||||
lShimmer.root.unVeil()
|
||||
lContent.root.show()
|
||||
}
|
||||
|
||||
ivCurrency.load(
|
||||
currency = wallet.currency,
|
||||
derivationStyle = store.state.globalState.scanResponse
|
||||
?.derivationStyleProvider?.getDerivationStyle(),
|
||||
)
|
||||
|
||||
lContent.tvCurrency.text = wallet.currency.currencyName
|
||||
lContent.tvAmountFiat.text = wallet.getFormattedFiatAmount(fiatCurrency)
|
||||
lContent.tvAmount.text = wallet.getFormattedCryptoAmount()
|
||||
|
||||
lContent.tvStatus.isVisible = statusMessage != null
|
||||
// lContent.tvStatus.text = statusMessage
|
||||
|
||||
lContent.tvExchangeRate.isVisible = statusMessage == null
|
||||
lContent.tvExchangeRate.text = wallet.getFormattedFiatRate(
|
||||
fiatCurrency = fiatCurrency,
|
||||
noRateValue = root.getString(id = R.string.token_item_no_rate),
|
||||
)
|
||||
|
||||
if (wallet.walletAddresses != null) {
|
||||
cardWallet.setOnClickListener {
|
||||
Analytics.send(Portfolio.TokenTapped())
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet.currency))
|
||||
}
|
||||
} else {
|
||||
cardWallet.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tap.common.extensions.getColor
|
||||
import com.tangem.tap.features.wallet.models.WalletWarningDescription
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutWarningCardBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WalletDetailWarningMessagesAdapter :
|
||||
ListAdapter<WalletWarningDescription, WalletDetailsWarningMessageVH>(DiffUtilCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletDetailsWarningMessageVH {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = LayoutWarningCardBinding.inflate(inflater, parent, false)
|
||||
|
||||
return WalletDetailsWarningMessageVH(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: WalletDetailsWarningMessageVH, position: Int) {
|
||||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
private class DiffUtilCallback : DiffUtil.ItemCallback<WalletWarningDescription>() {
|
||||
override fun areContentsTheSame(oldItem: WalletWarningDescription, newItem: WalletWarningDescription) =
|
||||
oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(oldItem: WalletWarningDescription, newItem: WalletWarningDescription) =
|
||||
oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
class WalletDetailsWarningMessageVH(
|
||||
val binding: LayoutWarningCardBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(warning: WalletWarningDescription) {
|
||||
binding.warningCard.setCardBackgroundColor(binding.root.getColor(R.color.darkGray2))
|
||||
setText(warning)
|
||||
}
|
||||
|
||||
private fun setText(warning: WalletWarningDescription) = with(binding.warningContentContainer) {
|
||||
tvTitle.text = warning.title
|
||||
tvMessage.text = warning.message
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.analytics
|
||||
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.extensions.isGreaterThan
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
class WalletAnalyticsEventsMapper : Converter<TotalFiatBalance, AnalyticsParam.CardBalanceState?> {
|
||||
|
||||
override fun convert(value: TotalFiatBalance): AnalyticsParam.CardBalanceState? {
|
||||
return when (value) {
|
||||
is TotalFiatBalance.Failed -> AnalyticsParam.CardBalanceState.BlockchainError
|
||||
is TotalFiatBalance.Loaded -> when {
|
||||
value.isWarning -> AnalyticsParam.CardBalanceState.CustomToken
|
||||
value.amount.isGreaterThan(BigDecimal.ZERO) -> AnalyticsParam.CardBalanceState.Full
|
||||
else -> AnalyticsParam.CardBalanceState.Empty
|
||||
}
|
||||
is TotalFiatBalance.Loading -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
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