Updated on 2026-08-14
This commit is contained in:
parent
d1798a2c49
commit
6dee2eeed7
112 changed files with 2146 additions and 1562 deletions
|
|
@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.common.util.userWalletId
|
||||
|
|
@ -61,7 +62,7 @@ class DetailsMiddleware {
|
|||
is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action)
|
||||
is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(state, action)
|
||||
is DetailsAction.ShowDisclaimer -> {
|
||||
val uri = store.state.detailsState.cardTermsOfUseUrl
|
||||
val uri = state.cardTermsOfUseUrl
|
||||
if (uri != null) {
|
||||
store.dispatch(NavigationAction.OpenDocument(uri))
|
||||
}
|
||||
|
|
@ -71,19 +72,26 @@ class DetailsMiddleware {
|
|||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
|
||||
}
|
||||
is DetailsAction.CreateBackup -> {
|
||||
store.state.detailsState.scanResponse?.let {
|
||||
state.scanResponse?.let {
|
||||
store.dispatch(GlobalAction.Onboarding.Start(it, canSkipBackup = false))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
}
|
||||
}
|
||||
DetailsAction.ScanCard -> {
|
||||
scope.launch {
|
||||
tangemSdkManager.scanCard(cardId = state.scanResponse?.card?.cardId)
|
||||
tangemSdkManager.scanCard(allowRequestAccessCodeFromRepository = true)
|
||||
.doOnSuccess { card ->
|
||||
val currentCardId = store.state.globalState.scanResponse?.card
|
||||
?.userWalletId
|
||||
?.stringValue
|
||||
if (card.userWalletId.stringValue == currentCardId) {
|
||||
val isSameWallet = state.scanResponse?.card?.userWalletId
|
||||
?.equals(card.userWalletId)
|
||||
?: false
|
||||
|
||||
// !!! Workaround !!!
|
||||
// TODO: Remove after [REDACTED_JIRA]
|
||||
val isTwinned = card.wallets.firstOrNull()?.publicKey?.toHexString()
|
||||
?.equals(state.scanResponse?.secondTwinPublicKey)
|
||||
?: false
|
||||
|
||||
if (isSameWallet || isTwinned) {
|
||||
store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card))
|
||||
} else {
|
||||
store.dispatchDialogShow(
|
||||
|
|
@ -116,6 +124,7 @@ class DetailsMiddleware {
|
|||
scope.launch {
|
||||
tangemSdkManager.resetToFactorySettings(card.cardId)
|
||||
.flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) }
|
||||
.flatMap { tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId)) }
|
||||
.doOnSuccess {
|
||||
Analytics.send(Settings.CardSettings.FactoryResetFinished())
|
||||
|
||||
|
|
@ -255,10 +264,10 @@ class DetailsMiddleware {
|
|||
private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch {
|
||||
if (state.saveAccessCodes == enable) return@launch
|
||||
if (enable) {
|
||||
saveAccessCodes(state)
|
||||
if (!state.saveWallets) {
|
||||
saveCurrentWallet(state)
|
||||
}
|
||||
saveAccessCodes(state)
|
||||
} else {
|
||||
deleteSavedAccessCodes()
|
||||
}
|
||||
|
|
@ -334,12 +343,15 @@ class DetailsMiddleware {
|
|||
useBiometricsForAccessCode = false,
|
||||
)
|
||||
|
||||
store.dispatchOnMain(
|
||||
DetailsAction.AppSettings.SwitchPrivacySetting.Success(
|
||||
setting = PrivacySetting.SaveAccessCode,
|
||||
enable = false,
|
||||
),
|
||||
)
|
||||
tangemSdkManager.clearSavedUserCodes()
|
||||
.doOnSuccess {
|
||||
store.dispatchOnMain(
|
||||
DetailsAction.AppSettings.SwitchPrivacySetting.Success(
|
||||
setting = PrivacySetting.SaveAccessCode,
|
||||
enable = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ import androidx.compose.foundation.rememberScrollState
|
|||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
|
|
@ -38,10 +36,6 @@ fun CardSettingsScreen(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val needReadCard = state.cardDetails == null
|
||||
val backgroundColor by rememberUpdatedState(
|
||||
newValue = if (needReadCard) TangemTheme.colors.background.primary
|
||||
else TangemTheme.colors.background.secondary,
|
||||
)
|
||||
|
||||
SettingsScreensScaffold(
|
||||
content = {
|
||||
|
|
@ -52,7 +46,7 @@ fun CardSettingsScreen(
|
|||
}
|
||||
},
|
||||
titleRes = R.string.card_settings_title,
|
||||
backgroundColor = backgroundColor,
|
||||
backgroundColor = TangemTheme.colors.background.secondary,
|
||||
onBackClick = onBackPressed,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.tap.common.compose.TangemTypography
|
||||
import com.tangem.tap.features.details.ui.common.ScreenTitle
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
|
|
@ -36,6 +38,10 @@ fun DetailsScreen(
|
|||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(color = TangemColorPalette.Light1)
|
||||
}
|
||||
|
||||
SettingsScreensScaffold(
|
||||
content = { Content(state = state, modifier = modifier) },
|
||||
onBackClick = onBackPressed,
|
||||
|
|
|
|||
|
|
@ -9,9 +9,19 @@ sealed class DisclaimerAction : Action {
|
|||
val type: DisclaimerType,
|
||||
) : DisclaimerAction()
|
||||
|
||||
data class Show(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction()
|
||||
data class Show(
|
||||
val onAcceptCallback: VoidCallback? = null,
|
||||
val onDismissCallback: VoidCallback? = null,
|
||||
) : DisclaimerAction()
|
||||
|
||||
data class AcceptDisclaimer(val type: DisclaimerType) : DisclaimerAction()
|
||||
|
||||
internal data class UpdateState(val type: DisclaimerType, val accepted: Boolean) : DisclaimerAction()
|
||||
internal data class SetOnAcceptCallback(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction()
|
||||
|
||||
internal data class SetCallbacks(
|
||||
val onAcceptCallback: VoidCallback? = null,
|
||||
val onDismissCallback: VoidCallback? = null,
|
||||
) : DisclaimerAction()
|
||||
|
||||
object OnBackPressed : DisclaimerAction()
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) {
|
|||
}
|
||||
is DisclaimerAction.Show -> {
|
||||
handleUpdateState = state.type.createUpdateState()
|
||||
store.dispatch(DisclaimerAction.SetOnAcceptCallback(action.onAcceptCallback))
|
||||
store.dispatch(DisclaimerAction.SetCallbacks(action.onAcceptCallback, action.onDismissCallback))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
}
|
||||
is DisclaimerAction.AcceptDisclaimer -> {
|
||||
|
|
@ -36,7 +36,13 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) {
|
|||
handleUpdateState = action.type.createUpdateState()
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
state.onAcceptCallback?.invoke()
|
||||
store.dispatch(DisclaimerAction.SetOnAcceptCallback(null))
|
||||
store.dispatch(DisclaimerAction.SetCallbacks(null, null))
|
||||
}
|
||||
is DisclaimerAction.OnBackPressed -> {
|
||||
state.onDismissCallback?.invoke()
|
||||
store.dispatch(DisclaimerAction.SetCallbacks(null, null))
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState {
|
|||
type = action.type,
|
||||
accepted = action.accepted,
|
||||
)
|
||||
is DisclaimerAction.SetOnAcceptCallback -> disclaimerState.copy(
|
||||
is DisclaimerAction.SetCallbacks -> disclaimerState.copy(
|
||||
onAcceptCallback = action.onAcceptCallback,
|
||||
onDismissCallback = action.onDismissCallback,
|
||||
)
|
||||
else -> disclaimerState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ data class DisclaimerState(
|
|||
val accepted: Boolean = false,
|
||||
val type: DisclaimerType = DisclaimerType.Tangem,
|
||||
val onAcceptCallback: VoidCallback? = null,
|
||||
val onDismissCallback: VoidCallback? = null,
|
||||
) : StateType
|
||||
|
||||
sealed class DisclaimerType(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||
import com.tangem.tap.common.extensions.configureSettings
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.BaseFragment
|
||||
|
|
@ -32,6 +34,7 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setStatusBarColor(R.color.backgroundLightGray)
|
||||
store.subscribe(subscriber = this) { state ->
|
||||
state
|
||||
.skipRepeats { oldState, newState -> oldState.disclaimerState == newState.disclaimerState }
|
||||
|
|
@ -65,4 +68,8 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs
|
|||
|
||||
webView.loadUrl(state.type.uri.toString())
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(DisclaimerAction.OnBackPressed)
|
||||
}
|
||||
}
|
||||
|
|
@ -79,13 +79,17 @@ private fun readCard() = scope.launch {
|
|||
onProgressStateChange = { showProgress ->
|
||||
if (showProgress) {
|
||||
changeButtonState(ButtonState.PROGRESS)
|
||||
} else {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
}
|
||||
// else { //todo hide this because
|
||||
// changeButtonState(ButtonState.ENABLED)
|
||||
// }
|
||||
},
|
||||
onScanStateChange = { scanInProgress ->
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress))
|
||||
},
|
||||
onFailure = {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
scope.launch {
|
||||
if (preferencesStorage.shouldSaveUserWallets) {
|
||||
|
|
@ -93,21 +97,18 @@ private fun readCard() = scope.launch {
|
|||
userWalletsListManager.save(userWallet)
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false)
|
||||
store.onCardScanned(scanResponse)
|
||||
}
|
||||
.doOnSuccess {
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
|
||||
userWallet.hasAccessCode,
|
||||
)
|
||||
store.onUserWalletSelected(userWallet)
|
||||
scope.launch { store.onUserWalletSelected(userWallet) }
|
||||
}
|
||||
.doOnResult {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
} else {
|
||||
store.onCardScanned(scanResponse)
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,16 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.domain.common.ProductType
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.onCardScanned
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.model.builders.UserWalletBuilder
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -59,17 +55,6 @@ class OnboardingHelper {
|
|||
backupCardsIds: List<String>? = null,
|
||||
) {
|
||||
when {
|
||||
// When should save user wallets but manager is locked, then unlock manager with card
|
||||
preferencesStorage.shouldSaveUserWallets &&
|
||||
userWalletsListManager.isLockedSync -> scope.launch {
|
||||
val userWallet = UserWalletBuilder(scanResponse).build()
|
||||
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false)
|
||||
userWalletsListManager.unlockWithCard(userWallet)
|
||||
.doOnSuccess {
|
||||
store.onUserWalletSelected(userWallet)
|
||||
}
|
||||
}
|
||||
// When should save user wallets, then save card without navigate to save wallet screen
|
||||
preferencesStorage.shouldSaveUserWallets -> scope.launch {
|
||||
store.dispatchOnMain(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.userWalletId
|
||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
|
|
@ -31,6 +32,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState
|
|||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -69,7 +71,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
fun updateScanResponse(response: ScanResponse) {
|
||||
when (twinCardsState.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response
|
||||
CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanNoteResponse(response))
|
||||
CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanResponse(response))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,10 +153,15 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
finishCardActivation()
|
||||
postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) }
|
||||
}
|
||||
|
||||
TwinCardsStep.CreateFirstWallet -> {
|
||||
scope.launch {
|
||||
userWalletsListManager.delete(
|
||||
listOf(getScanResponse().card.userWalletId),
|
||||
)
|
||||
}
|
||||
}
|
||||
TwinCardsStep.None,
|
||||
TwinCardsStep.Warning,
|
||||
TwinCardsStep.CreateFirstWallet,
|
||||
TwinCardsStep.CreateSecondWallet,
|
||||
TwinCardsStep.CreateThirdWallet,
|
||||
-> Unit
|
||||
|
|
@ -294,13 +301,14 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
when (twinCardsState.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> {
|
||||
store.dispatchOnMain(GlobalAction.Onboarding.Stop)
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(
|
||||
scanResponse = scanResponse,
|
||||
backupCardsIds = listOfNotNull(twinCardsState.twinCardsManager?.secondCardPublicKey),
|
||||
)
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
|
||||
}
|
||||
CreateTwinWalletMode.RecreateWallet -> {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
if (preferencesStorage.shouldSaveUserWallets) {
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import coil.load
|
|||
import com.tangem.Message
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
|
|
@ -97,6 +98,11 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setStatusBarColor(R.color.backgroundWhite)
|
||||
}
|
||||
|
||||
private fun reconfigureLayoutForTwins(containerBinding: LayoutOnboardingContainerTopBinding) =
|
||||
with(containerBinding) {
|
||||
imvFrontCard.hide()
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ internal class SaveWalletMiddleware {
|
|||
|
||||
saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet)
|
||||
.flatMap { userWalletsListManager.save(userWallet, canOverride = true) }
|
||||
.flatMap { userWalletsListManager.selectWallet(userWallet.walletId) }
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(SaveWalletAction.Save.Error(error))
|
||||
}
|
||||
|
|
@ -105,12 +104,6 @@ internal class SaveWalletMiddleware {
|
|||
preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet ||
|
||||
preferencesStorage.shouldSaveAccessCodes
|
||||
|
||||
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
|
||||
userWallet.hasAccessCode,
|
||||
)
|
||||
|
||||
store.dispatchOnMain(SaveWalletAction.Save.Success)
|
||||
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import com.tangem.tap.features.send.redux.states.ButtonState
|
|||
import com.tangem.tap.features.send.redux.states.ExternalTransactionData
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -328,12 +329,15 @@ private fun updateWarnings(dispatch: (Action) -> Unit) {
|
|||
}
|
||||
|
||||
private suspend fun updateWallet(walletManager: WalletManager) {
|
||||
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
|
||||
if (selectedUserWallet != null) {
|
||||
val wallet = walletManager.wallet
|
||||
walletCurrenciesManager.update(
|
||||
userWallet = selectedUserWallet,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
currency = Currency.Blockchain(
|
||||
blockchain = wallet.blockchain,
|
||||
derivationPath = wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
store.dispatchOnMain(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager)))
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ class TokensMiddleware {
|
|||
val updatedScanResponse = scanResponse.copy(
|
||||
derivedKeys = updatedDerivedKeys,
|
||||
)
|
||||
store.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse))
|
||||
store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
|
||||
onSuccess(updatedScanResponse)
|
||||
|
|
@ -383,7 +383,7 @@ class TokensMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
val addedCurrencies = store.state.walletState.wallets.map { walletStore ->
|
||||
val addedCurrencies = store.state.walletState.walletsStores.map { walletStore ->
|
||||
walletStore.walletsData.map { walletData -> walletData.currency }
|
||||
}.flatten().map {
|
||||
when (it) {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
toolbar.setNavigationOnClickListener { handleOnBackPressed() }
|
||||
|
||||
val onSaveChanges = { tokens: List<TokenWithBlockchain>, blockchains: List<Blockchain> ->
|
||||
Analytics.send(ManageTokens.ButtonSaveChanges())
|
||||
|
|
@ -144,7 +144,6 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc
|
|||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
super.handleOnBackPressed()
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(TokensAction.ResetState)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import androidx.compose.ui.res.colorResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
|
|
@ -81,6 +82,11 @@ fun CurrenciesScreen(
|
|||
}
|
||||
}
|
||||
|
||||
val statusBarColor = colorResource(id = R.color.backgroundLightGray)
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(color = statusBarColor)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
floatingActionButton = {
|
||||
if (tokensState.value.allowToAdd) {
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ 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.UserWallet
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.TotalBalance
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -36,7 +36,7 @@ sealed class WalletAction : Action {
|
|||
|
||||
data class LoadWallet(
|
||||
val blockchain: BlockchainNetwork? = null,
|
||||
val walletManager: WalletManager? = null
|
||||
val walletManager: WalletManager? = null,
|
||||
) : WalletAction() {
|
||||
data class Success(val wallet: Wallet, val blockchain: BlockchainNetwork) : WalletAction()
|
||||
data class NoAccount(
|
||||
|
|
@ -90,7 +90,8 @@ sealed class WalletAction : Action {
|
|||
val blockchain: BlockchainNetwork,
|
||||
) : MultiWallet()
|
||||
|
||||
data class SelectWallet(val walletData: WalletData?) : MultiWallet()
|
||||
data class SelectWallet(val currency: Currency?) : MultiWallet()
|
||||
data class SetSingleWalletCurrency(val currency: Currency?) : MultiWallet()
|
||||
|
||||
data class TryToRemoveWallet(val currency: Currency) : MultiWallet()
|
||||
data class RemoveWallet(val currency: Currency) : MultiWallet()
|
||||
|
|
@ -128,7 +129,7 @@ sealed class WalletAction : Action {
|
|||
val wallet: Wallet? = null, val coinsList: List<Currency>? = null,
|
||||
) : WalletAction() {
|
||||
data class Success(
|
||||
val fiatRates: Map<Currency, BigDecimal?>
|
||||
val fiatRates: Map<Currency, BigDecimal?>,
|
||||
) : WalletAction()
|
||||
|
||||
object Failure : WalletAction()
|
||||
|
|
@ -163,7 +164,7 @@ sealed class WalletAction : Action {
|
|||
object ChooseTradeActionDialog : DialogAction()
|
||||
data class ChooseCurrency(val amounts: List<Amount>?) : DialogAction()
|
||||
data class RussianCardholdersWarningDialog(
|
||||
val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null
|
||||
val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null,
|
||||
) : DialogAction()
|
||||
|
||||
object Hide : DialogAction()
|
||||
|
|
@ -181,12 +182,13 @@ sealed class WalletAction : Action {
|
|||
data class Buy(
|
||||
val checkUserLocation: Boolean = true,
|
||||
) : TradeCryptoAction()
|
||||
|
||||
data class FinishSelling(val transactionId: String) : TradeCryptoAction()
|
||||
data class SendCrypto(
|
||||
val currencyId: String,
|
||||
val amount: String,
|
||||
val destinationAddress: String,
|
||||
val transactionId: String
|
||||
val transactionId: String,
|
||||
) : TradeCryptoAction()
|
||||
}
|
||||
|
||||
|
|
@ -206,6 +208,9 @@ sealed class WalletAction : Action {
|
|||
}
|
||||
|
||||
data class UserWalletChanged(val userWallet: UserWallet) : WalletAction()
|
||||
data class WalletStoresChanged(val walletStores: List<WalletStoreModel>) : WalletAction()
|
||||
data class TotalFiatBalanceChanged(val balance: TotalFiatBalance) : WalletAction()
|
||||
data class WalletStoresChanged(val walletStores: List<WalletStoreModel>) : WalletAction() {
|
||||
data class UpdateWalletStores(val reduxWalletStores: List<WalletStore>) : WalletAction()
|
||||
}
|
||||
|
||||
data class TotalFiatBalanceChanged(val balance: TotalBalance) : WalletAction()
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ data class WalletState(
|
|||
val cardImage: Artwork? = null,
|
||||
val hashesCountVerified: Boolean? = null,
|
||||
val mainWarningsList: List<WarningMessage> = mutableListOf(),
|
||||
val wallets: List<WalletStore> = listOf(),
|
||||
val walletsStores: List<WalletStore> = listOf(),
|
||||
val isMultiwalletAllowed: Boolean = false,
|
||||
val cardCurrency: CryptoCurrencyName? = null,
|
||||
val selectedCurrency: Currency? = null,
|
||||
|
|
@ -50,6 +50,12 @@ data class WalletState(
|
|||
val walletCardsCount: Int? = null,
|
||||
) : StateType {
|
||||
|
||||
val walletsDataFromStores: List<WalletData>
|
||||
get() = walletsStores.map { it.walletsData }.flatten()
|
||||
|
||||
val selectedWalletData: WalletData?
|
||||
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> { _, _ ->
|
||||
|
|
@ -63,20 +69,17 @@ data class WalletState(
|
|||
get() = store.state.globalState.exchangeManager.featureIsSwitchedOn()
|
||||
|
||||
val blockchains: List<Blockchain>
|
||||
get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain }
|
||||
get() = walletsStores.mapNotNull { it.walletManager?.wallet?.blockchain }
|
||||
|
||||
val currencies: List<Currency>
|
||||
get() = wallets.flatMap { it.walletsData }.map { it.currency }
|
||||
|
||||
val walletsData: List<WalletData>
|
||||
get() = wallets.flatMap { it.walletsData }
|
||||
get() = walletsStores.flatMap { it.walletsData }.map { it.currency }
|
||||
|
||||
val walletManagers: List<WalletManager>
|
||||
get() = wallets.mapNotNull { it.walletManager }
|
||||
get() = walletsStores.mapNotNull { it.walletManager }
|
||||
|
||||
val primaryWallet: WalletData? = wallets.firstOrNull()?.walletsData?.firstOrNull()
|
||||
val primaryWallet: WalletData? = walletsStores.firstOrNull()?.walletsData?.firstOrNull()
|
||||
|
||||
val primaryWalletManager: WalletManager? = if (wallets.isNotEmpty()) wallets[0].walletManager else null
|
||||
val primaryWalletManager: WalletManager? = if (walletsStores.isNotEmpty()) walletsStores[0].walletManager else null
|
||||
|
||||
val shouldShowDetails: Boolean =
|
||||
primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
|
||||
|
|
@ -91,12 +94,12 @@ data class WalletState(
|
|||
}
|
||||
|
||||
fun getWalletManager(blockchain: BlockchainNetwork): WalletManager? {
|
||||
return wallets.find { it.blockchainNetwork == blockchain }?.walletManager
|
||||
return walletsStores.find { it.blockchainNetwork == blockchain }?.walletManager
|
||||
}
|
||||
|
||||
fun getWalletData(blockchain: BlockchainNetwork?): WalletData? {
|
||||
if (blockchain == null) return null
|
||||
return walletsData.find {
|
||||
return walletsDataFromStores.find {
|
||||
it.currency is Currency.Blockchain &&
|
||||
it.currency.blockchain == blockchain.blockchain &&
|
||||
it.currency.derivationPath == blockchain.derivationPath
|
||||
|
|
@ -105,7 +108,7 @@ data class WalletState(
|
|||
|
||||
fun getWalletStore(currency: Currency?): WalletStore? {
|
||||
if (currency == null) return null
|
||||
return wallets.firstOrNull {
|
||||
return walletsStores.firstOrNull {
|
||||
it.blockchainNetwork.derivationPath == currency.derivationPath &&
|
||||
(it.blockchainNetwork.blockchain == currency.blockchain)
|
||||
}
|
||||
|
|
@ -120,7 +123,7 @@ data class WalletState(
|
|||
|
||||
fun getWalletStore(blockchainNetwork: BlockchainNetwork?): WalletStore? {
|
||||
if (blockchainNetwork == null) return null
|
||||
return wallets.firstOrNull {
|
||||
return walletsStores.firstOrNull {
|
||||
it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath &&
|
||||
(it.blockchainNetwork.blockchain == blockchainNetwork.blockchain)
|
||||
}
|
||||
|
|
@ -131,10 +134,6 @@ data class WalletState(
|
|||
return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency }
|
||||
}
|
||||
|
||||
fun getSelectedWalletData(): WalletData? {
|
||||
return walletsData.find { it.currency == selectedCurrency }
|
||||
}
|
||||
|
||||
private fun isPrimaryCurrency(walletData: WalletData): Boolean {
|
||||
return (walletData.currency is Currency.Blockchain &&
|
||||
walletData.currency.blockchain == store.state.walletState.primaryBlockchain)
|
||||
|
|
@ -142,10 +141,10 @@ data class WalletState(
|
|||
walletData.currency.token == store.state.walletState.primaryToken)
|
||||
}
|
||||
|
||||
fun replaceWalletInWallets(wallet: WalletStore?): List<WalletStore> {
|
||||
if (wallet == null) return wallets
|
||||
fun replaceWalletStoreInWalletsStores(wallet: WalletStore?): List<WalletStore> {
|
||||
if (wallet == null) return walletsStores
|
||||
var changed = false
|
||||
val updatedWallets = wallets.map {
|
||||
val updatedWallets = walletsStores.map {
|
||||
if (it.blockchainNetwork == wallet.blockchainNetwork) {
|
||||
changed = true
|
||||
wallet
|
||||
|
|
@ -153,7 +152,7 @@ data class WalletState(
|
|||
it
|
||||
}
|
||||
}
|
||||
return if (changed) updatedWallets else wallets + wallet
|
||||
return if (changed) updatedWallets else walletsStores + wallet
|
||||
}
|
||||
|
||||
fun updateWalletData(walletData: WalletData?): WalletState {
|
||||
|
|
@ -161,26 +160,23 @@ data class WalletState(
|
|||
return updateWalletsData(listOf(walletData))
|
||||
}
|
||||
|
||||
fun updateWalletsData(
|
||||
walletsData: List<WalletData>
|
||||
): WalletState {
|
||||
|
||||
fun updateWalletsData(walletsData: List<WalletData>): WalletState {
|
||||
val walletStores = walletsData
|
||||
.map { BlockchainNetwork(it.currency.blockchain, it.currency.derivationPath, emptyList()) }
|
||||
.distinct().map { getWalletStore(it) }.mapNotNull { it?.updateWallets(walletsData) }
|
||||
|
||||
return updateWalletStores(walletStores)
|
||||
return updateWalletsStores(walletStores)
|
||||
}
|
||||
|
||||
fun updateWalletStore(walletStore: WalletStore?): WalletState {
|
||||
return copy(wallets = replaceWalletInWallets(walletStore))
|
||||
return copy(walletsStores = replaceWalletStoreInWalletsStores(walletStore))
|
||||
.updateTotalBalance()
|
||||
.updateProgressState()
|
||||
}
|
||||
|
||||
private fun updateWalletStores(walletStores: List<WalletStore>): WalletState {
|
||||
private fun updateWalletsStores(walletStores: List<WalletStore>): WalletState {
|
||||
val walletStoresMutable = walletStores.toMutableList()
|
||||
val updatedWallets = wallets.map { oldWalletStore ->
|
||||
val updatedWallets = walletsStores.map { oldWalletStore ->
|
||||
val walletStore = walletStoresMutable.find {
|
||||
it.blockchainNetwork == oldWalletStore.blockchainNetwork
|
||||
}
|
||||
|
|
@ -191,20 +187,20 @@ data class WalletState(
|
|||
oldWalletStore
|
||||
}
|
||||
}
|
||||
return copy(wallets = updatedWallets + walletStoresMutable)
|
||||
return copy(walletsStores = updatedWallets + walletStoresMutable)
|
||||
.updateTotalBalance()
|
||||
.updateProgressState()
|
||||
}
|
||||
|
||||
fun removeWallet(walletData: WalletData?): WalletState {
|
||||
fun removeWalletData(walletData: WalletData?): WalletState {
|
||||
if (walletData == null) return this
|
||||
return when (val currency = walletData.currency) {
|
||||
is Currency.Blockchain -> {
|
||||
val walletStores = wallets.filterNot {
|
||||
val walletStores = walletsStores.filterNot {
|
||||
it.blockchainNetwork.blockchain == currency.blockchain
|
||||
&& it.blockchainNetwork.derivationPath == currency.derivationPath
|
||||
}
|
||||
copy(wallets = walletStores)
|
||||
copy(walletsStores = walletStores)
|
||||
.updateTotalBalance()
|
||||
.updateProgressState()
|
||||
}
|
||||
|
|
@ -223,23 +219,8 @@ data class WalletState(
|
|||
}
|
||||
}
|
||||
|
||||
fun replaceSomeWallets(newWallets: List<WalletData>): List<WalletData> {
|
||||
val remainingWallets: MutableList<WalletData> = newWallets.toMutableList()
|
||||
val updatedWallets = walletsData.map { wallet ->
|
||||
val newWallet = newWallets
|
||||
.firstOrNull { wallet.currency == it.currency }
|
||||
if (newWallet == null) {
|
||||
wallet
|
||||
} else {
|
||||
remainingWallets.remove(newWallet)
|
||||
newWallet
|
||||
}
|
||||
}
|
||||
return updatedWallets + remainingWallets
|
||||
}
|
||||
|
||||
private fun updateTotalBalance(): WalletState {
|
||||
val walletsData = this.wallets
|
||||
val walletsData = this.walletsStores
|
||||
.flatMap(WalletStore::walletsData)
|
||||
|
||||
return if (walletsData.isNotEmpty()) {
|
||||
|
|
@ -256,7 +237,7 @@ data class WalletState(
|
|||
}
|
||||
|
||||
private fun updateProgressState(): WalletState {
|
||||
val walletsData = this.wallets
|
||||
val walletsData = this.walletsStores
|
||||
.flatMap(WalletStore::walletsData)
|
||||
|
||||
return if (walletsData.isNotEmpty()) {
|
||||
|
|
@ -264,7 +245,7 @@ data class WalletState(
|
|||
|
||||
this.copy(
|
||||
state = walletsData.findProgressState(),
|
||||
error = this.error.takeIf { newProgressState == ProgressState.Error }
|
||||
error = this.error.takeIf { newProgressState == ProgressState.Error },
|
||||
)
|
||||
} else this
|
||||
}
|
||||
|
|
@ -276,6 +257,21 @@ data class WalletState(
|
|||
}
|
||||
}
|
||||
|
||||
fun List<WalletData>.replaceSomeWalletsData(newWallets: List<WalletData>): List<WalletData> {
|
||||
val remainingWallets: MutableList<WalletData> = newWallets.toMutableList()
|
||||
val updatedWallets = this.map { wallet ->
|
||||
val newWallet = newWallets
|
||||
.firstOrNull { wallet.currency == it.currency }
|
||||
if (newWallet == null) {
|
||||
wallet
|
||||
} else {
|
||||
remainingWallets.remove(newWallet)
|
||||
newWallet
|
||||
}
|
||||
}
|
||||
return updatedWallets + remainingWallets
|
||||
}
|
||||
|
||||
enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error }
|
||||
|
||||
enum class ErrorType { NoInternetConnection }
|
||||
|
|
@ -287,7 +283,7 @@ sealed class WalletMainButton(enabled: Boolean) : Button(enabled) {
|
|||
|
||||
data class WalletAddresses(
|
||||
val selectedAddress: AddressData,
|
||||
val list: List<AddressData>
|
||||
val list: List<AddressData>,
|
||||
)
|
||||
|
||||
data class AddressData(
|
||||
|
|
@ -301,7 +297,7 @@ data class AddressData(
|
|||
|
||||
data class Artwork(
|
||||
val artworkId: String,
|
||||
val artwork: Bitmap? = null
|
||||
val artwork: Bitmap? = null,
|
||||
) {
|
||||
companion object {
|
||||
const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png"
|
||||
|
|
@ -374,7 +370,7 @@ data class WalletData(
|
|||
}
|
||||
}
|
||||
|
||||
private fun assembleTokenWarnings(walletWarnings: MutableList<WalletWarning>){
|
||||
private fun assembleTokenWarnings(walletWarnings: MutableList<WalletWarning>) {
|
||||
if (!currency.isToken()) return
|
||||
|
||||
val blockchainFullName = currency.blockchain.fullName
|
||||
|
|
@ -391,7 +387,7 @@ data class WalletData(
|
|||
data class WalletStore(
|
||||
val walletManager: WalletManager?,
|
||||
val blockchainNetwork: BlockchainNetwork,
|
||||
val walletsData: List<WalletData>
|
||||
val walletsData: List<WalletData>,
|
||||
) {
|
||||
fun updateWallets(walletDataList: List<WalletData>): WalletStore {
|
||||
val relevantWalletDataList = walletDataList.filter {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.extensions.toFiatRateString
|
||||
import com.tangem.tap.common.extensions.toFiatValue
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.common.extensions.toFormattedFiatValue
|
||||
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.models.TotalBalance
|
||||
import com.tangem.tap.features.wallet.models.WalletRent
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAddresses
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.WalletStore
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import com.tangem.tap.features.wallet.ui.TokenData
|
||||
import com.tangem.tap.store
|
||||
|
||||
internal fun List<WalletStoreModel>.mapToReduxModels(
|
||||
isMultiWalletAllowed: Boolean,
|
||||
): List<WalletStore> {
|
||||
return this.map { walletStoreModel ->
|
||||
walletStoreModel.mapToReduxModel(isMultiWalletAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TotalFiatBalance.mapToReduxModel(): TotalBalance {
|
||||
return TotalBalance(
|
||||
state = when (this) {
|
||||
is TotalFiatBalance.Loading -> ProgressState.Loading
|
||||
is TotalFiatBalance.Error -> ProgressState.Error
|
||||
is TotalFiatBalance.Loaded -> ProgressState.Done
|
||||
},
|
||||
fiatAmount = amount,
|
||||
fiatCurrency = store.state.globalState.appCurrency,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.mapToReduxModel(
|
||||
isMultiWalletAllowed: Boolean,
|
||||
): WalletStore {
|
||||
return WalletStore(
|
||||
walletManager = walletManager,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
walletsData = walletsData.mapToReduxModel(isMultiWalletAllowed, walletRent),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<WalletDataModel>.mapToReduxModel(
|
||||
isMultiWalletAllowed: Boolean,
|
||||
walletRent: WalletStoreModel.WalletRent?,
|
||||
): List<WalletData> {
|
||||
return this.map { walletDataModel ->
|
||||
with(walletDataModel) {
|
||||
val amount = status.amount
|
||||
val amountFormatted = amount.toFormattedCurrencyString(
|
||||
decimals = currency.decimals,
|
||||
currency = currency.currencySymbol,
|
||||
)
|
||||
val appCurrency = store.state.globalState.appCurrency
|
||||
val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) }
|
||||
val fiatAmountFormatted = fiatAmount
|
||||
?.takeIf { !status.isErrorStatus }
|
||||
?.toFormattedFiatValue(appCurrency.symbol)
|
||||
val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol)
|
||||
|
||||
WalletData(
|
||||
currency = currency,
|
||||
walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress ->
|
||||
WalletAddresses(
|
||||
selectedAddress = selectedAddress,
|
||||
list = walletAddresses,
|
||||
)
|
||||
},
|
||||
existentialDepositString = existentialDeposit?.toPlainString(),
|
||||
fiatRate = fiatRate,
|
||||
fiatRateString = fiatRateFormatted,
|
||||
pendingTransactions = status.pendingTransactions,
|
||||
mainButton = WalletMainButton.SendButton(
|
||||
enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(),
|
||||
),
|
||||
walletRent = walletRent?.let {
|
||||
WalletRent(
|
||||
minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}",
|
||||
rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}",
|
||||
)
|
||||
},
|
||||
currencyData = BalanceWidgetData(
|
||||
status = when (status) {
|
||||
is WalletDataModel.Loading -> BalanceStatus.Loading
|
||||
is WalletDataModel.NoAccount -> BalanceStatus.NoAccount
|
||||
is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress
|
||||
is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress
|
||||
is WalletDataModel.Unreachable -> BalanceStatus.Unreachable
|
||||
is WalletDataModel.MissedDerivation -> BalanceStatus.MissedDerivation
|
||||
is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline
|
||||
},
|
||||
currency = currency.currencyName,
|
||||
currencySymbol = currency.currencySymbol,
|
||||
blockchainAmount = status.amount,
|
||||
amount = amount,
|
||||
amountFormatted = amountFormatted,
|
||||
fiatAmount = fiatAmount,
|
||||
fiatAmountFormatted = fiatAmountFormatted,
|
||||
token = when {
|
||||
!isMultiWalletAllowed && currency is Currency.Token -> {
|
||||
TokenData(
|
||||
amount = amount,
|
||||
amountFormatted = amountFormatted,
|
||||
fiatAmount = fiatAmount,
|
||||
fiatAmountFormatted = fiatAmountFormatted,
|
||||
tokenSymbol = currency.currencySymbol,
|
||||
fiatRate = fiatRate,
|
||||
fiatRateString = fiatRateFormatted,
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
},
|
||||
amountToCreateAccount = (status as? WalletDataModel.NoAccount)
|
||||
?.amountToCreateAccount
|
||||
?.toString(),
|
||||
errorMessage = status.errorMessage,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ class MultiWalletMiddleware {
|
|||
handleAddingWalletManagers(globalState, action.walletManagers)
|
||||
}
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
if (action.walletData != null) {
|
||||
if (action.currency != null) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletDetails))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class TradeCryptoMiddleware {
|
|||
return
|
||||
}
|
||||
|
||||
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
val card = store.state.globalState.scanResponse?.card ?: return
|
||||
|
||||
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
|
||||
|
|
@ -85,7 +85,7 @@ class TradeCryptoMiddleware {
|
|||
}
|
||||
|
||||
private fun proceedSellAction() {
|
||||
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
|
||||
val appCurrency = store.state.globalState.appCurrency
|
||||
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
|
||||
|
|
@ -107,7 +107,7 @@ class TradeCryptoMiddleware {
|
|||
}
|
||||
|
||||
private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) {
|
||||
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
|
||||
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency)))
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class WalletDialogsMiddleware {
|
|||
store.dispatchDialogShow(WalletDialog.SignedHashesMultiWalletDialog)
|
||||
}
|
||||
is WalletAction.DialogAction.ChooseTradeActionDialog -> {
|
||||
store.state.walletState.getSelectedWalletData()?.let {
|
||||
store.state.walletState.selectedWalletData?.let {
|
||||
Analytics.send(Token.ButtonExchange(AnalyticsParam.CurrencyType.Currency(it.currency)))
|
||||
}
|
||||
store.dispatchDialogShow(WalletDialog.ChooseTradeActionDialog)
|
||||
|
|
|
|||
|
|
@ -97,11 +97,7 @@ class WalletMiddleware {
|
|||
when (action) {
|
||||
is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action)
|
||||
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
|
||||
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(
|
||||
action,
|
||||
walletState,
|
||||
globalState
|
||||
)
|
||||
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState)
|
||||
is WalletAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action)
|
||||
is WalletAction.DialogAction -> walletDialogMiddleware.handle(action)
|
||||
is WalletAction.LoadWallet -> {
|
||||
|
|
@ -129,9 +125,7 @@ class WalletMiddleware {
|
|||
true,
|
||||
),
|
||||
)
|
||||
store.dispatch(
|
||||
action = WalletAction.LoadWallet.Success(action.wallet, action.blockchain)
|
||||
)
|
||||
store.dispatch(WalletAction.LoadWallet.Success(action.wallet, action.blockchain))
|
||||
}
|
||||
}
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
|
||||
|
|
@ -150,7 +144,7 @@ class WalletMiddleware {
|
|||
action.coinsList != null -> action.coinsList
|
||||
else -> {
|
||||
if (walletState.isMultiwalletAllowed) {
|
||||
walletState.walletsData.map { it.currency }
|
||||
walletState.walletsDataFromStores.map { it.currency }
|
||||
} else {
|
||||
val derivationPath = walletState.primaryWallet?.currency?.derivationPath
|
||||
val primaryBlockchain = walletState.primaryBlockchain
|
||||
|
|
@ -175,7 +169,7 @@ class WalletMiddleware {
|
|||
Timber.e(
|
||||
throwable,
|
||||
"Loading rates failed for [%s]",
|
||||
currency.currencySymbol
|
||||
currency.currencySymbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -188,14 +182,11 @@ class WalletMiddleware {
|
|||
}
|
||||
is WalletAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createWallet(
|
||||
globalState.scanResponse?.card?.cardId
|
||||
)
|
||||
val result = tangemSdkManager.createWallet(globalState.scanResponse?.card?.cardId)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val scanNoteResponse =
|
||||
globalState.scanResponse?.copy(card = result.data)
|
||||
scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) }
|
||||
val scanResponse = globalState.scanResponse?.copy(card = result.data)
|
||||
scanResponse?.let { store.onCardScanned(scanResponse) }
|
||||
}
|
||||
is CompletionResult.Failure -> {}
|
||||
}
|
||||
|
|
@ -210,9 +201,7 @@ class WalletMiddleware {
|
|||
store.dispatchOnMain(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
|
||||
|
||||
scope.launch {
|
||||
val response = OnlineCardVerifier().getCardInfo(
|
||||
action.card.cardId, action.card.cardPublicKey
|
||||
)
|
||||
val response = OnlineCardVerifier().getCardInfo(action.card.cardId, action.card.cardPublicKey)
|
||||
when (response) {
|
||||
is Result.Success -> {
|
||||
val actionList = listOf(
|
||||
|
|
@ -237,11 +226,12 @@ class WalletMiddleware {
|
|||
refresh = action is WalletAction.LoadData.Refresh,
|
||||
)
|
||||
} else {
|
||||
val scanNoteResponse = globalState.scanResponse ?: return@launch
|
||||
if (walletState.walletsData.isNotEmpty()) {
|
||||
globalState.tapWalletManager.reloadData(scanNoteResponse)
|
||||
val scanResponse = globalState.scanResponse ?: return@launch
|
||||
|
||||
if (walletState.walletsDataFromStores.isNotEmpty()) {
|
||||
globalState.tapWalletManager.reloadData(scanResponse)
|
||||
} else {
|
||||
globalState.tapWalletManager.loadData(scanNoteResponse)
|
||||
globalState.tapWalletManager.loadData(scanResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -249,8 +239,8 @@ class WalletMiddleware {
|
|||
is NetworkStateChanged -> {
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
|
||||
val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync
|
||||
if (selectedUserWallet != null) scope.launch {
|
||||
globalState.tapWalletManager.loadData(selectedUserWallet)
|
||||
if (selectedUserWallet != null) {
|
||||
scope.launch { globalState.tapWalletManager.loadData(selectedUserWallet) }
|
||||
} else {
|
||||
globalState.scanResponse?.let { scanNoteResponse ->
|
||||
scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) }
|
||||
|
|
@ -295,6 +285,7 @@ class WalletMiddleware {
|
|||
}
|
||||
is WalletAction.UserWalletChanged -> Unit
|
||||
is WalletAction.WalletStoresChanged -> {
|
||||
updateWalletStores(action.walletStores, walletState)
|
||||
fetchTotalFiatBalance(action.walletStores, walletState)
|
||||
findMissedDerivations(action.walletStores)
|
||||
tryToShowAppRatingWarning(action.walletStores)
|
||||
|
|
@ -303,12 +294,29 @@ class WalletMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateWalletStores(wallStores: List<WalletStoreModel>, state: WalletState) {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
if (!state.isMultiwalletAllowed) {
|
||||
wallStores.firstOrNull()?.walletsData?.firstOrNull()?.let {
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.SetSingleWalletCurrency(it.currency))
|
||||
}
|
||||
}
|
||||
|
||||
val reduxWalletStores = wallStores.mapToReduxModels(state.isMultiwalletAllowed)
|
||||
store.dispatchOnMain(
|
||||
WalletAction.WalletStoresChanged.UpdateWalletStores(
|
||||
reduxWalletStores = reduxWalletStores.toList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchTotalFiatBalance(walletStores: List<WalletStoreModel>, state: WalletState) {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull(
|
||||
prevAmount = state.totalBalance?.fiatAmount,
|
||||
walletStores = walletStores,
|
||||
)
|
||||
)?.mapToReduxModel()
|
||||
|
||||
if (totalFiatBalance != null) {
|
||||
store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance))
|
||||
|
|
@ -361,7 +369,7 @@ class WalletMiddleware {
|
|||
}
|
||||
|
||||
private fun prepareSendAction(amount: Amount?, state: WalletState?): Action {
|
||||
val selectedWalletData = state?.getSelectedWalletData()
|
||||
val selectedWalletData = state?.selectedWalletData
|
||||
val currency = selectedWalletData?.currency
|
||||
val walletStore = state?.getWalletStore(currency)
|
||||
|
||||
|
|
@ -376,36 +384,34 @@ class WalletMiddleware {
|
|||
if (currency != null && state.isMultiwalletAllowed) {
|
||||
when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
val amountToSend =
|
||||
amounts?.find { it.currencySymbol == currency.blockchain.currency }
|
||||
?: return WalletAction.DialogAction.ChooseCurrency(amounts)
|
||||
val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency }
|
||||
?: return WalletAction.DialogAction.ChooseCurrency(amounts)
|
||||
PrepareSendScreen(
|
||||
coinAmount = amountToSend,
|
||||
coinRate = selectedWalletData.fiatRate,
|
||||
walletManager = walletStore.walletManager
|
||||
walletManager = walletStore.walletManager,
|
||||
)
|
||||
}
|
||||
is Currency.Token -> {
|
||||
val amountToSend =
|
||||
amounts?.find { it.currencySymbol == currency.token.symbol }
|
||||
?: return WalletAction.DialogAction.ChooseCurrency(amounts)
|
||||
val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol }
|
||||
?: return WalletAction.DialogAction.ChooseCurrency(amounts)
|
||||
prepareSendActionForToken(
|
||||
amount = amountToSend,
|
||||
state = state,
|
||||
selectedWalletData = selectedWalletData,
|
||||
walletStore = walletStore
|
||||
walletStore = walletStore,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (amounts?.size ?: 0 > 1) {
|
||||
if ((amounts?.size ?: 0) > 1) {
|
||||
WalletAction.DialogAction.ChooseCurrency(amounts)
|
||||
} else {
|
||||
val amountToSend = amounts?.first()
|
||||
PrepareSendScreen(
|
||||
coinAmount = amountToSend,
|
||||
coinRate = selectedWalletData?.fiatRate,
|
||||
walletManager = walletStore?.walletManager
|
||||
walletManager = walletStore?.walletManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -416,7 +422,7 @@ class WalletMiddleware {
|
|||
amount: Amount,
|
||||
state: WalletState?,
|
||||
selectedWalletData: WalletData?,
|
||||
walletStore: WalletStore?
|
||||
walletStore: WalletStore?,
|
||||
): PrepareSendScreen {
|
||||
val coinRate = state?.getWalletData(walletStore?.blockchainNetwork)?.fiatRate
|
||||
val tokenRate = if (state?.isMultiwalletAllowed == true) {
|
||||
|
|
@ -431,7 +437,7 @@ class WalletMiddleware {
|
|||
coinRate = coinRate,
|
||||
walletManager = walletStore?.walletManager,
|
||||
tokenAmount = amount,
|
||||
tokenRate = tokenRate
|
||||
tokenRate = tokenRate,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -447,7 +453,7 @@ class WalletMiddleware {
|
|||
|
||||
val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
|
||||
val outgoingTxs = walletManager.wallet.getPendingTransactions(
|
||||
PendingTransactionType.Outgoing
|
||||
PendingTransactionType.Outgoing,
|
||||
).filterByCoin()
|
||||
|
||||
val rentExempt = result.data
|
||||
|
|
@ -465,8 +471,8 @@ class WalletMiddleware {
|
|||
WalletAction.SetWalletRent(
|
||||
wallet = walletManager.wallet,
|
||||
minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
|
||||
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
|
||||
)
|
||||
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
dispatchOnMain(WalletAction.RemoveWalletRent(walletManager.wallet))
|
||||
|
|
|
|||
|
|
@ -125,7 +125,8 @@ class WarningsMiddleware {
|
|||
if (scanResponse.isTangemTwins() || scanResponse.isDemoCard()) return null
|
||||
|
||||
if (scanResponse.card.isMultiwalletAllowed) {
|
||||
return if (scanResponse.card.hasSignedHashes()) {
|
||||
val isBackupForbidden = with(scanResponse.card.settings) { !(isBackupAllowed || isHDWalletAllowed) }
|
||||
return if (scanResponse.card.hasSignedHashes() && isBackupForbidden) {
|
||||
WarningMessagesManager.signedHashesMultiWalletWarning()
|
||||
} else {
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
|
|
@ -133,8 +134,7 @@ class WarningsMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
val validator = store.state.walletState.walletManagers.firstOrNull()
|
||||
as? SignatureCountValidator
|
||||
val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator
|
||||
return if (validator == null) {
|
||||
if (scanResponse.card.hasSignedHashes()) {
|
||||
WarningMessagesManager.alreadySignedHashesWarning()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class MultiWalletReducer {
|
|||
fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.MultiWallet.AddBlockchains -> {
|
||||
val wallets: List<WalletStore> = action.blockchains.map { blockchain ->
|
||||
val walletStores: List<WalletStore> = action.blockchains.mapNotNull { blockchain ->
|
||||
val walletManager = action.walletManagers.firstOrNull {
|
||||
it.wallet.blockchain == blockchain.blockchain &&
|
||||
(it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath)
|
||||
|
|
@ -52,13 +52,13 @@ class MultiWalletReducer {
|
|||
status = BalanceStatus.Loading,
|
||||
currency = blockchain.blockchain.fullName,
|
||||
currencySymbol = blockchain.blockchain.currency,
|
||||
token = cardToken
|
||||
token = cardToken,
|
||||
),
|
||||
walletAddresses = createAddressList(wallet),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
currency = Currency.Blockchain(
|
||||
blockchain.blockchain,
|
||||
blockchain.derivationPath
|
||||
blockchain.derivationPath,
|
||||
),
|
||||
existentialDepositString = getExistentialDeposit(walletManager),
|
||||
)
|
||||
|
|
@ -66,16 +66,19 @@ class MultiWalletReducer {
|
|||
WalletStore(
|
||||
walletManager = walletManager,
|
||||
blockchainNetwork = blockchain,
|
||||
walletsData = listOf(walletData)
|
||||
walletsData = listOf(walletData),
|
||||
)
|
||||
}
|
||||
|
||||
val selectedCurrency = if (!state.isMultiwalletAllowed) {
|
||||
wallets.firstOrNull()?.walletsData?.firstOrNull()?.currency
|
||||
walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency
|
||||
} else {
|
||||
state.selectedCurrency
|
||||
}
|
||||
state.copy(wallets = wallets, selectedCurrency = selectedCurrency)
|
||||
state.copy(
|
||||
walletsStores = walletStores,
|
||||
selectedCurrency = selectedCurrency,
|
||||
)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddBlockchain -> {
|
||||
val walletManager = action.walletManager ?: state.getWalletManager(action.blockchain)
|
||||
|
|
@ -91,14 +94,14 @@ class MultiWalletReducer {
|
|||
mainButton = WalletMainButton.SendButton(false),
|
||||
currency = Currency.Blockchain(
|
||||
action.blockchain.blockchain,
|
||||
action.blockchain.derivationPath
|
||||
action.blockchain.derivationPath,
|
||||
),
|
||||
existentialDepositString = getExistentialDeposit(walletManager),
|
||||
)
|
||||
val walletStore = WalletStore(
|
||||
walletManager = walletManager,
|
||||
blockchainNetwork = action.blockchain,
|
||||
walletsData = listOf(walletData)
|
||||
walletsData = listOf(walletData),
|
||||
)
|
||||
|
||||
val newState = state.updateWalletStore(walletStore)
|
||||
|
|
@ -142,31 +145,40 @@ class MultiWalletReducer {
|
|||
status = tokenBalanceStatus,
|
||||
amount = action.amount.value,
|
||||
amountFormatted = action.amount.value?.toFormattedCurrencyString(
|
||||
action.amount.decimals, action.amount.currencySymbol
|
||||
action.amount.decimals, action.amount.currencySymbol,
|
||||
),
|
||||
fiatAmountFormatted = tokenWalletData.fiatRate?.let {
|
||||
action.amount.value?.toFiatString(it, store.state.globalState.appCurrency.symbol)
|
||||
} ?: UNKNOWN_AMOUNT_SIGN,
|
||||
blockchainAmount = wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO
|
||||
blockchainAmount = wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO,
|
||||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled),
|
||||
currency = Currency.Token(
|
||||
token = action.token,
|
||||
blockchain = action.blockchain.blockchain,
|
||||
derivationPath = action.blockchain.derivationPath
|
||||
derivationPath = action.blockchain.derivationPath,
|
||||
),
|
||||
walletRent = findWalletRent(state.getWalletStore(walletManager.wallet))
|
||||
walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)),
|
||||
)
|
||||
state.updateWalletData(newTokenWalletData)
|
||||
}
|
||||
is WalletAction.MultiWallet.SetIsMultiwalletAllowed -> state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed)
|
||||
is WalletAction.MultiWallet.SelectWallet -> state.copy(selectedCurrency = action.walletData?.currency)
|
||||
is WalletAction.MultiWallet.SetIsMultiwalletAllowed ->
|
||||
state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed)
|
||||
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
state.copy(selectedCurrency = action.currency)
|
||||
}
|
||||
is WalletAction.MultiWallet.SetSingleWalletCurrency -> {
|
||||
state.copy(selectedCurrency = action.currency)
|
||||
}
|
||||
is WalletAction.MultiWallet.TryToRemoveWallet -> state
|
||||
is WalletAction.MultiWallet.RemoveWallet -> state.removeWallet(state.getWalletData(action.currency))
|
||||
is WalletAction.MultiWallet.RemoveWallet -> {
|
||||
state.removeWalletData(state.getWalletData(action.currency))
|
||||
}
|
||||
is WalletAction.MultiWallet.RemoveWallets -> {
|
||||
var updatedState = state
|
||||
action.currencies.forEach { updatedState = updatedState.removeWallet(state.getWalletData(it)) }
|
||||
action.currencies.forEach { updatedState = updatedState.removeWalletData(state.getWalletData(it)) }
|
||||
updatedState
|
||||
}
|
||||
is WalletAction.MultiWallet.SetPrimaryBlockchain -> state.copy(primaryBlockchain = action.blockchain)
|
||||
|
|
@ -205,10 +217,10 @@ fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletDat
|
|||
currencyData = BalanceWidgetData(
|
||||
status = BalanceStatus.Loading,
|
||||
currency = this.name,
|
||||
currencySymbol = this.symbol
|
||||
currencySymbol = this.symbol,
|
||||
),
|
||||
walletAddresses = walletAddresses,
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
currency = currency
|
||||
currency = currency,
|
||||
)
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState
|
|||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import com.tangem.tap.features.wallet.ui.TokenData
|
||||
|
|
@ -106,18 +107,15 @@ class OnWalletLoadedReducer {
|
|||
mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled),
|
||||
)
|
||||
}
|
||||
val newWallets = tokens + newWalletData
|
||||
val wallets = walletState.replaceSomeWallets((newWallets))
|
||||
val newWalletsData = tokens + newWalletData
|
||||
val walletsData = walletState.walletsDataFromStores.replaceSomeWalletsData(newWalletsData)
|
||||
|
||||
return walletState
|
||||
.updateWalletsData(wallets)
|
||||
return walletState.updateWalletsData(walletsData)
|
||||
}
|
||||
|
||||
private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState {
|
||||
if (wallet.blockchain != walletState.primaryBlockchain) return walletState
|
||||
|
||||
// val ratesRespository = store.state.globalState.tapWalletManager.ratesRepository
|
||||
// val tokenCurrency =
|
||||
val fiatCurrencyName = store.state.globalState.appCurrency.code
|
||||
val token = wallet.getFirstToken()
|
||||
val tokenData = if (token != null) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ private fun List<WalletData>.mapToProgressState(): List<ProgressState> {
|
|||
BalanceStatus.Unreachable,
|
||||
BalanceStatus.EmptyCard,
|
||||
BalanceStatus.UnknownBlockchain,
|
||||
BalanceStatus.MissedDerivation,
|
||||
-> ProgressState.Error
|
||||
BalanceStatus.Loading,
|
||||
null,
|
||||
|
|
|
|||
|
|
@ -3,29 +3,38 @@ package com.tangem.tap.features.wallet.redux.reducers
|
|||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.extensions.mapNotNullValues
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.extensions.toFiatRateString
|
||||
import com.tangem.tap.common.extensions.toFiatString
|
||||
import com.tangem.tap.common.extensions.toFiatValue
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.common.extensions.toFormattedFiatValue
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.getArtworkUrl
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
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.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.TotalBalance
|
||||
import com.tangem.tap.features.wallet.models.WalletRent
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.redux.AddressData
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
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.WalletAddresses
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.WalletStore
|
||||
import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import com.tangem.tap.features.wallet.ui.TokenData
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -63,7 +72,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
is WalletAction.EmptyWallet -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Done,
|
||||
wallets = listOf(
|
||||
walletsStores = listOf(
|
||||
WalletStore(
|
||||
walletManager = null,
|
||||
blockchainNetwork = BlockchainNetwork(
|
||||
|
|
@ -85,7 +94,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
is WalletAction.LoadData.Failure -> {
|
||||
when (action.error) {
|
||||
is TapError.NoInternetConnection -> {
|
||||
val wallets = newState.wallets
|
||||
val wallets = newState.walletsStores
|
||||
.map { store ->
|
||||
store.copy(
|
||||
walletsData = store.walletsData.map {
|
||||
|
|
@ -95,20 +104,19 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
),
|
||||
)
|
||||
},
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Error,
|
||||
error = ErrorType.NoInternetConnection,
|
||||
wallets = wallets,
|
||||
walletsStores = wallets,
|
||||
)
|
||||
}
|
||||
is TapError.UnknownBlockchain -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Done,
|
||||
wallets = listOf(
|
||||
walletsStores = listOf(
|
||||
WalletStore(
|
||||
walletManager = null,
|
||||
blockchainNetwork = BlockchainNetwork(
|
||||
|
|
@ -153,7 +161,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
BalanceStatus.Loading
|
||||
}
|
||||
if (action.blockchain == null) {
|
||||
val wallets = newState.wallets.map { walletStore ->
|
||||
val wallets = newState.walletsStores.map { walletStore ->
|
||||
walletStore.copy(
|
||||
walletsData = walletStore.walletsData.map { walletData ->
|
||||
walletData.copy(
|
||||
|
|
@ -170,7 +178,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
}
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Loading,
|
||||
wallets = wallets,
|
||||
walletsStores = wallets,
|
||||
)
|
||||
} else {
|
||||
val walletManager = newState.getWalletManager(action.blockchain) ?: return newState
|
||||
|
|
@ -178,7 +186,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
walletManager.cardTokens.map {
|
||||
Currency.fromBlockchainNetwork(action.blockchain, it)
|
||||
}
|
||||
val newWallets = newState.walletsData.filter { currencies.contains(it.currency) }
|
||||
val newWalletsData = newState.walletsDataFromStores.filter { currencies.contains(it.currency) }
|
||||
.map { wallet ->
|
||||
wallet.copy(
|
||||
currencyData = wallet.currencyData.copy(
|
||||
|
|
@ -189,8 +197,8 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
mainButton = WalletMainButton.SendButton(false),
|
||||
)
|
||||
}
|
||||
val wallets = newState.replaceSomeWallets(newWallets)
|
||||
val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(wallets)
|
||||
val walletsData = newState.walletsDataFromStores.replaceSomeWalletsData(newWalletsData)
|
||||
val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(walletsData)
|
||||
newState = newState.updateWalletStore(walletStore)
|
||||
}
|
||||
}
|
||||
|
|
@ -260,7 +268,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
newState = newState.updateWalletsData(updatedWallets)
|
||||
|
||||
val progressState =
|
||||
if (newState.walletsData.any { it.currencyData.status == BalanceStatus.Loading }) {
|
||||
if (newState.walletsDataFromStores.any { it.currencyData.status == BalanceStatus.Loading }) {
|
||||
ProgressState.Loading
|
||||
} else {
|
||||
ProgressState.Done
|
||||
|
|
@ -292,15 +300,13 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
}
|
||||
is WalletAction.TradeCryptoAction -> return newState
|
||||
is WalletAction.ChangeSelectedAddress -> {
|
||||
val selectedWalletData = newState.getWalletData(newState.selectedCurrency)
|
||||
|
||||
val walletAddresses =
|
||||
newState.getWalletData(selectedWalletData?.currency)?.walletAddresses
|
||||
?: return newState
|
||||
val walletAddresses = newState.getWalletData(newState.selectedCurrency)?.walletAddresses
|
||||
?: return newState
|
||||
val address = walletAddresses.list.firstOrNull { it.type == action.type }
|
||||
?: return newState
|
||||
|
||||
newState = newState.updateWalletData(
|
||||
selectedWalletData?.copy(
|
||||
newState.selectedWalletData?.copy(
|
||||
walletAddresses = WalletAddresses(
|
||||
selectedAddress = address,
|
||||
list = walletAddresses.list,
|
||||
|
|
@ -328,37 +334,41 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
|
|||
val card = scanResponse.card
|
||||
newState = WalletState(
|
||||
cardId = card.cardId,
|
||||
isMultiwalletAllowed = card.isMultiwalletAllowed,
|
||||
isMultiwalletAllowed = isMultiCurrency,
|
||||
cardImage = Artwork(
|
||||
artworkId = artworkUrl,
|
||||
),
|
||||
isTestnet = card.isTestCard,
|
||||
state = ProgressState.Loading,
|
||||
wallets = newState.wallets,
|
||||
showBackupWarning = card.isMultiwalletAllowed &&
|
||||
showBackupWarning = isMultiCurrency &&
|
||||
card.settings.isBackupAllowed &&
|
||||
card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
walletCardsCount = card.findCardsCount(),
|
||||
totalBalance = if (isMultiCurrency) {
|
||||
TotalBalance(ProgressState.Loading, BigDecimal.ZERO, store.state.globalState.appCurrency)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
is WalletAction.WalletStoresChanged -> {
|
||||
is WalletAction.WalletStoresChanged.UpdateWalletStores -> {
|
||||
newState = newState.copy(
|
||||
wallets = action.walletStores.mapToReduxModel(newState.isMultiwalletAllowed),
|
||||
walletsStores = action.reduxWalletStores,
|
||||
)
|
||||
}
|
||||
is WalletAction.TotalFiatBalanceChanged -> {
|
||||
newState = newState.copy(
|
||||
totalBalance = action.balance.mapToReduxModel(),
|
||||
totalBalance = action.balance,
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadData.Success -> {
|
||||
val selectedCurrency = if (!newState.isMultiwalletAllowed) {
|
||||
newState.wallets.firstOrNull()
|
||||
newState.walletsStores.firstOrNull()
|
||||
?.walletsData
|
||||
?.firstOrNull()
|
||||
?.currency
|
||||
} else {
|
||||
newState.selectedCurrency
|
||||
newState.selectedWalletData?.currency
|
||||
}
|
||||
|
||||
newState = newState.copy(
|
||||
|
|
@ -377,117 +387,6 @@ private fun CardDTO.findCardsCount(): Int? {
|
|||
?.takeIf { this.isMultiwalletAllowed }
|
||||
}
|
||||
|
||||
@JvmName("walletStoreModelToReduxModel")
|
||||
private fun List<WalletStoreModel>.mapToReduxModel(
|
||||
isMultiWalletAllowed: Boolean,
|
||||
): List<WalletStore> {
|
||||
return this.map { walletStoreModel ->
|
||||
with(walletStoreModel) {
|
||||
WalletStore(
|
||||
walletManager = walletManager,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
walletsData = walletsData.mapToReduxModel(isMultiWalletAllowed, walletStoreModel.walletRent),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmName("walletDataModelToReduxModel")
|
||||
private fun List<WalletDataModel>.mapToReduxModel(
|
||||
isMultiWalletAllowed: Boolean,
|
||||
walletRent: WalletStoreModel.WalletRent?,
|
||||
): List<WalletData> {
|
||||
return this.map { walletDataModel ->
|
||||
with(walletDataModel) {
|
||||
val amount = status.amount
|
||||
val amountFormatted = amount.toFormattedCurrencyString(
|
||||
decimals = currency.decimals,
|
||||
currency = currency.currencySymbol,
|
||||
)
|
||||
val appCurrency = store.state.globalState.appCurrency
|
||||
val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) }
|
||||
val fiatAmountFormatted = fiatAmount
|
||||
?.takeIf { !status.isErrorStatus }
|
||||
?.toFormattedFiatValue(appCurrency.symbol)
|
||||
val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol)
|
||||
|
||||
WalletData(
|
||||
currency = currency,
|
||||
walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress ->
|
||||
WalletAddresses(
|
||||
selectedAddress = selectedAddress,
|
||||
list = walletAddresses,
|
||||
)
|
||||
},
|
||||
existentialDepositString = existentialDeposit?.toPlainString(),
|
||||
fiatRate = fiatRate,
|
||||
fiatRateString = fiatRateFormatted,
|
||||
pendingTransactions = status.pendingTransactions,
|
||||
mainButton = WalletMainButton.SendButton(
|
||||
enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(),
|
||||
),
|
||||
walletRent = walletRent?.let {
|
||||
WalletRent(
|
||||
minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}",
|
||||
rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}",
|
||||
)
|
||||
},
|
||||
currencyData = BalanceWidgetData(
|
||||
status = when (status) {
|
||||
is WalletDataModel.Loading -> BalanceStatus.Loading
|
||||
is WalletDataModel.NoAccount -> BalanceStatus.NoAccount
|
||||
is WalletDataModel.Refreshing -> BalanceStatus.Refreshing
|
||||
is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress
|
||||
is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress
|
||||
is WalletDataModel.Unreachable,
|
||||
is WalletDataModel.MissedDerivation,
|
||||
-> BalanceStatus.Unreachable
|
||||
is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline
|
||||
},
|
||||
currency = currency.currencyName,
|
||||
currencySymbol = currency.currencySymbol,
|
||||
blockchainAmount = status.amount,
|
||||
amount = amount,
|
||||
amountFormatted = amountFormatted,
|
||||
fiatAmount = fiatAmount,
|
||||
fiatAmountFormatted = fiatAmountFormatted,
|
||||
token = when {
|
||||
!isMultiWalletAllowed && currency is Currency.Token -> {
|
||||
TokenData(
|
||||
amount = amount,
|
||||
amountFormatted = amountFormatted,
|
||||
fiatAmount = fiatAmount,
|
||||
fiatAmountFormatted = fiatAmountFormatted,
|
||||
tokenSymbol = currency.currencySymbol,
|
||||
fiatRate = fiatRate,
|
||||
fiatRateString = fiatRateFormatted,
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
},
|
||||
amountToCreateAccount = (status as? WalletDataModel.NoAccount)
|
||||
?.amountToCreateAccount
|
||||
?.toString(),
|
||||
errorMessage = status.errorMessage,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TotalFiatBalance.mapToReduxModel(): TotalBalance {
|
||||
return TotalBalance(
|
||||
state = when (this) {
|
||||
is TotalFiatBalance.Loading -> ProgressState.Loading
|
||||
is TotalFiatBalance.Refreshing -> ProgressState.Refreshing
|
||||
is TotalFiatBalance.Error -> ProgressState.Error
|
||||
is TotalFiatBalance.Loaded -> ProgressState.Done
|
||||
},
|
||||
fiatAmount = amount,
|
||||
fiatCurrency = store.state.globalState.appCurrency,
|
||||
)
|
||||
}
|
||||
|
||||
fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? {
|
||||
if (wallet == null) return null
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ enum class BalanceStatus {
|
|||
Refreshing,
|
||||
NoAccount,
|
||||
EmptyCard,
|
||||
UnknownBlockchain
|
||||
UnknownBlockchain,
|
||||
MissedDerivation,
|
||||
}
|
||||
|
||||
data class BalanceWidgetData(
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
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 androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.badoo.mvicore.modelWatcher
|
||||
import com.tangem.common.doOnResult
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
|
|
@ -18,26 +25,38 @@ import com.tangem.tap.common.TestActions
|
|||
import com.tangem.tap.common.analytics.Analytics
|
||||
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.extensions.appendIfNotNull
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
|
||||
import com.tangem.tap.common.extensions.getColor
|
||||
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.common.extensions.toQrCode
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.onboarding.getQRReceiveMessage
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
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.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.WalletState.Companion.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.scope
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
||||
|
|
@ -48,6 +67,42 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
|
||||
private val binding: FragmentWalletDetailsBinding by viewBinding(FragmentWalletDetailsBinding::bind)
|
||||
|
||||
private val walletDataWatcher = modelWatcher<WalletData> {
|
||||
WalletData::pendingTransactions {
|
||||
showPendingTransactionsIfPresent(it)
|
||||
}
|
||||
WalletData::currency {
|
||||
handleCurrencyIcon(it)
|
||||
}
|
||||
WalletData::currencyData {
|
||||
setupBalanceData(it)
|
||||
}
|
||||
(WalletData::currencyData or WalletData::currency) { walletData ->
|
||||
setupCurrency(walletData.currencyData, walletData.currency)
|
||||
setupSwipeRefresh(walletData.currencyData, walletData.currency)
|
||||
}
|
||||
}
|
||||
|
||||
private val walletStateWatcher = modelWatcher<WalletState> {
|
||||
(WalletState::selectedCurrency or WalletState::selectedWalletData) { state ->
|
||||
val selectedWalletData = state.selectedWalletData
|
||||
if (selectedWalletData != null) {
|
||||
walletDataWatcher.invoke(selectedWalletData)
|
||||
setupButtons(selectedWalletData, state.isExchangeServiceFeatureOn)
|
||||
setupAddressCard(selectedWalletData)
|
||||
handleWarnings(selectedWalletData)
|
||||
}
|
||||
}
|
||||
(WalletState::selectedCurrency or WalletState::isExchangeServiceFeatureOn) { state ->
|
||||
if (state.selectedWalletData != null) {
|
||||
setupButtons(state.selectedWalletData!!, state.isExchangeServiceFeatureOn)
|
||||
}
|
||||
}
|
||||
(WalletState::state or WalletState::error) { state ->
|
||||
setupNoInternetHandling(state.state, state.error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
|
@ -79,6 +134,12 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
walletDataWatcher.clear()
|
||||
walletStateWatcher.clear()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
|
|
@ -129,50 +190,10 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
|
||||
override fun newState(state: WalletState) {
|
||||
if (activity == null || view == null) return
|
||||
if (state.selectedCurrency == null) return
|
||||
val selectedWallet = state.getSelectedWalletData() ?: return
|
||||
if (state.selectedWalletData == null) return
|
||||
walletStateWatcher.invoke(state)
|
||||
|
||||
|
||||
showPendingTransactionsIfPresent(selectedWallet.pendingTransactions)
|
||||
setupCurrency(selectedWallet.currencyData, selectedWallet.currency)
|
||||
setupAddressCard(selectedWallet)
|
||||
setupNoInternetHandling(state)
|
||||
setupBalanceData(selectedWallet.currencyData)
|
||||
setupButtons(selectedWallet, state.isExchangeServiceFeatureOn)
|
||||
|
||||
handleCurrencyIcon(selectedWallet)
|
||||
handleWarnings(selectedWallet)
|
||||
updateViewMeasurements()
|
||||
|
||||
binding.srlWalletDetails.setOnRefreshListener {
|
||||
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
|
||||
Analytics.send(Token.Refreshed())
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = selectedWallet.currency.blockchain,
|
||||
derivationPath = selectedWallet.currency.derivationPath,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync
|
||||
if (selectedUserWallet != null) scope.launch {
|
||||
walletCurrenciesManager.update(selectedUserWallet, blockchainNetwork)
|
||||
} else {
|
||||
store.dispatch(
|
||||
WalletAction.LoadWallet(
|
||||
blockchain = BlockchainNetwork(
|
||||
selectedWallet.currency.blockchain,
|
||||
selectedWallet.currency.derivationPath,
|
||||
emptyList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
store.dispatch(WalletAction.LoadFiatRate(coinsList = listOf(selectedWallet.currency)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
|
||||
binding.srlWalletDetails.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateViewMeasurements() {
|
||||
|
|
@ -203,6 +224,37 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupSwipeRefresh(currencyData: BalanceWidgetData, currency: Currency) {
|
||||
binding.srlWalletDetails.setOnRefreshListener {
|
||||
if (currencyData.status != BalanceStatus.Loading && currencyData.status != BalanceStatus.Refreshing) {
|
||||
Analytics.send(Token.Refreshed())
|
||||
lifecycleScope.launch(Dispatchers.Default) {
|
||||
val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync
|
||||
if (selectedUserWallet != null) {
|
||||
walletCurrenciesManager.update(selectedUserWallet, currency)
|
||||
.doOnResult {
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.srlWalletDetails.isRefreshing = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = currency.blockchain,
|
||||
derivationPath = currency.derivationPath,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
|
||||
store.dispatch(WalletAction.LoadWallet(blockchainNetwork))
|
||||
store.dispatch(WalletAction.LoadFiatRate(coinsList = listOf(currency)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.srlWalletDetails.isRefreshing = currencyData.status == BalanceStatus.Loading ||
|
||||
currencyData.status == BalanceStatus.Refreshing
|
||||
}
|
||||
|
||||
private fun setupButtons(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) = with(binding) {
|
||||
lWalletDetails.btnCopy.setOnClickListener {
|
||||
selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
|
||||
|
|
@ -231,9 +283,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
rvWarningMessages.show(warningDetails.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) {
|
||||
private fun handleCurrencyIcon(currency: Currency) = with(binding.lWalletDetails.lBalance) {
|
||||
ivCurrency.load(
|
||||
currency = wallet.currency,
|
||||
currency = currency,
|
||||
derivationStyle = store.state.globalState
|
||||
.scanResponse
|
||||
?.card
|
||||
|
|
@ -281,9 +333,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupNoInternetHandling(state: WalletState) {
|
||||
if (state.state == ProgressState.Error) {
|
||||
if (state.error == ErrorType.NoInternetConnection) {
|
||||
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,
|
||||
|
|
@ -350,7 +402,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_remove -> {
|
||||
store.state.walletState.getSelectedWalletData()?.let { walletData ->
|
||||
store.state.walletState.selectedWalletData?.let { walletData ->
|
||||
store.dispatch(WalletAction.MultiWallet.TryToRemoveWallet(walletData.currency))
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.transition.TransitionInflater
|
|||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import coil.load
|
||||
import coil.size.Scale
|
||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
import com.tangem.tap.MainActivity
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
|
|
@ -80,15 +81,18 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
viewModel.launch()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
setStatusBarColor(R.color.background_secondary)
|
||||
|
||||
store.subscribe(this) { state ->
|
||||
state.select { it.walletState }
|
||||
}
|
||||
walletView.setFragment(this, binding)
|
||||
viewModel.launch()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
|
@ -97,6 +101,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
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)
|
||||
|
|
@ -166,7 +175,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
|
||||
binding.srlWallet.isRefreshing = state.state == ProgressState.Refreshing
|
||||
binding.srlWallet.setOnRefreshListener {
|
||||
if (state.state != ProgressState.Loading ||
|
||||
if (state.state != ProgressState.Loading &&
|
||||
state.state != ProgressState.Refreshing
|
||||
) {
|
||||
Analytics.send(Portfolio.Refreshed())
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@ class WalletAdapter : ListAdapter<WalletData, WalletAdapter.WalletsViewHolder>(D
|
|||
BalanceStatus.Unreachable -> {
|
||||
root.getString(R.string.wallet_balance_blockchain_unreachable)
|
||||
}
|
||||
BalanceStatus.MissedDerivation -> {
|
||||
root.getString(R.string.wallet_balance_missing_derivation)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +98,7 @@ class WalletAdapter : ListAdapter<WalletData, WalletAdapter.WalletsViewHolder>(D
|
|||
if (wallet.walletAddresses != null) {
|
||||
cardWallet.setOnClickListener {
|
||||
Analytics.send(Portfolio.TokenTapped())
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet.currency))
|
||||
}
|
||||
} else {
|
||||
cardWallet.setOnClickListener(null)
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ private class SolanaRentWarningActionEmitter {
|
|||
}
|
||||
|
||||
private fun getBlockchainNetwork(): BlockchainNetwork {
|
||||
val currency = store.state.walletState.getSelectedWalletData()!!.currency
|
||||
val currency = store.state.walletState.selectedWalletData!!.currency
|
||||
return BlockchainNetwork(currency.blockchain, currency.derivationPath, listOf())
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ package com.tangem.tap.features.wallet.ui.wallet
|
|||
import android.widget.Button
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.badoo.mvicore.DiffStrategy
|
||||
import com.badoo.mvicore.modelWatcher
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
|
|
@ -33,6 +35,45 @@ class MultiWalletView : WalletView() {
|
|||
|
||||
private lateinit var walletsAdapter: WalletAdapter
|
||||
|
||||
private val watcher = modelWatcher<WalletState> {
|
||||
val totalBalanceStrategy: DiffStrategy<WalletState> = { old, new ->
|
||||
old.cardId != new.cardId ||
|
||||
old.totalBalance != new.totalBalance ||
|
||||
old.state != new.state ||
|
||||
old.walletsStores.size != new.walletsStores.size
|
||||
}
|
||||
|
||||
// !!! Workaround !!!
|
||||
// Checking state properties instead of state params can reduce application performance,
|
||||
// but here it is necessary because the WalletStore has an unsuitable equals method
|
||||
WalletState::walletsDataFromStores {
|
||||
walletsAdapter.submitList(it)
|
||||
}
|
||||
WalletState::loadingUserTokens {
|
||||
binding?.pbLoadingUserTokens?.show(it)
|
||||
}
|
||||
WalletState::walletCardsCount { walletCardsCount ->
|
||||
binding?.let {
|
||||
setupWalletCardNumber(it, walletCardsCount)
|
||||
}
|
||||
}
|
||||
WalletState::missingDerivations { missingDerivations ->
|
||||
binding?.let {
|
||||
handleRescanWarning(it, missingDerivations.isNotEmpty())
|
||||
}
|
||||
}
|
||||
WalletState::showBackupWarning { showBackupWarnings ->
|
||||
binding?.let {
|
||||
handleBackupWarning(it, showBackupWarnings)
|
||||
}
|
||||
}
|
||||
watch({ it }, totalBalanceStrategy) { walletState ->
|
||||
binding?.let {
|
||||
handleTotalBalance(it, walletState.totalBalance, walletState.state, walletState.walletsDataFromStores.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
|
||||
setFragment(fragment, binding)
|
||||
onViewCreated()
|
||||
|
|
@ -40,6 +81,7 @@ class MultiWalletView : WalletView() {
|
|||
}
|
||||
|
||||
private fun showMultiWalletView(binding: FragmentWalletBinding) = with(binding) {
|
||||
watcher.clear()
|
||||
tvTwinCardNumber.hide()
|
||||
rvPendingTransaction.hide()
|
||||
lCardBalance.root.hide()
|
||||
|
|
@ -68,13 +110,7 @@ class MultiWalletView : WalletView() {
|
|||
val fragment = fragment ?: return
|
||||
val binding = binding ?: return
|
||||
|
||||
handleTotalBalance(binding, state.totalBalance, state.state, state.walletsData.size)
|
||||
handleBackupWarning(binding, state.showBackupWarning)
|
||||
handleRescanWarning(binding, state.missingDerivations.isNotEmpty())
|
||||
setupWalletCardNumber(binding, state.walletCardsCount)
|
||||
walletsAdapter.submitList(state.walletsData)
|
||||
|
||||
binding.pbLoadingUserTokens.show(state.loadingUserTokens)
|
||||
watcher.invoke(state)
|
||||
|
||||
binding.btnAddToken.setOnClickListener {
|
||||
val card = store.state.globalState.scanResponse!!.card
|
||||
|
|
@ -91,7 +127,7 @@ class MultiWalletView : WalletView() {
|
|||
store.dispatch(TokensAction.AllowToAddTokens(true))
|
||||
store.dispatch(
|
||||
TokensAction.SetAddedCurrencies(
|
||||
wallets = state.walletsData,
|
||||
wallets = state.walletsDataFromStores,
|
||||
derivationStyle = card.derivationStyle,
|
||||
),
|
||||
)
|
||||
|
|
@ -139,21 +175,26 @@ class MultiWalletView : WalletView() {
|
|||
walletsCount: Int,
|
||||
) = with(binding.lCardTotalBalance) {
|
||||
if (walletsCount == 0) {
|
||||
root.isVisible = false
|
||||
if (progressState != ProgressState.Loading) {
|
||||
root.isVisible = false
|
||||
}
|
||||
} else {
|
||||
if (totalBalance == null) {
|
||||
veilBalance.animateVisibility(show = true)
|
||||
root.isVisible = progressState == ProgressState.Loading
|
||||
if (progressState != ProgressState.Loading) {
|
||||
root.isVisible = false
|
||||
}
|
||||
} else {
|
||||
root.isVisible = true
|
||||
|
||||
// Skip changes when on refreshing state
|
||||
if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) {
|
||||
return@with
|
||||
}
|
||||
|
||||
veilBalance.animateVisibility(show = totalBalance.state == ProgressState.Loading)
|
||||
tvBalance.animateVisibility(show = totalBalance.state != ProgressState.Loading)
|
||||
if (totalBalance.state == ProgressState.Loading) {
|
||||
veilBalance.veil()
|
||||
} else {
|
||||
veilBalance.unVeil()
|
||||
}
|
||||
tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error)
|
||||
|
||||
tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString(
|
||||
|
|
@ -218,6 +259,11 @@ class MultiWalletView : WalletView() {
|
|||
rowButtons.btnSend.text = fragment?.getText(R.string.wallet_button_create_wallet)
|
||||
rowButtons.onSendClick = { store.dispatch(WalletAction.CreateWallet) }
|
||||
}
|
||||
|
||||
override fun onDestroyFragment() {
|
||||
super.onDestroyFragment()
|
||||
watcher.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private val WalletDetailsButtonsRow.btnBuy: Button
|
||||
|
|
|
|||
|
|
@ -52,4 +52,4 @@ class SaltPaySingleWalletView : WalletView() {
|
|||
),
|
||||
).setup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -153,14 +153,12 @@ class SingleWalletView : WalletView() {
|
|||
(binding.lAddress.root as? ViewGroup)?.beginDelayedTransition()
|
||||
chipGroupAddressType.show()
|
||||
chipGroupAddressType.fitChipsByGroupWidth()
|
||||
val checkedId =
|
||||
MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
|
||||
val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
|
||||
if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
|
||||
|
||||
chipGroupAddressType.setOnCheckedChangeListener { group, checkedId ->
|
||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||
val type =
|
||||
MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain)
|
||||
val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain)
|
||||
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ abstract class WalletView {
|
|||
binding = null
|
||||
}
|
||||
|
||||
open fun onDestroyFragment() {}
|
||||
|
||||
abstract fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)
|
||||
abstract fun onViewCreated()
|
||||
abstract fun onNewState(state: WalletState)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package com.tangem.tap.features.walletSelector.redux
|
||||
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
|
||||
data class UserWalletModel(
|
||||
val id: String,
|
||||
val id: UserWalletId,
|
||||
val name: String,
|
||||
val artworkUrl: String,
|
||||
val type: Type,
|
||||
|
|
|
|||
|
|
@ -34,20 +34,16 @@ internal sealed interface WalletSelectorAction : Action {
|
|||
}
|
||||
|
||||
data class SelectWallet(
|
||||
val walletId: String,
|
||||
) : WalletSelectorAction
|
||||
|
||||
data class UnlockWalletWithCard(
|
||||
val walletId: String,
|
||||
val userWalletId: UserWalletId,
|
||||
) : WalletSelectorAction
|
||||
|
||||
data class RenameWallet(
|
||||
val walletId: String,
|
||||
val userWalletId: UserWalletId,
|
||||
val newName: String,
|
||||
) : WalletSelectorAction
|
||||
|
||||
data class RemoveWallets(
|
||||
val walletIdsToRemove: List<String>,
|
||||
val userWalletsIds: List<UserWalletId>,
|
||||
) : WalletSelectorAction
|
||||
|
||||
object AddWallet : WalletSelectorAction {
|
||||
|
|
|
|||
|
|
@ -19,13 +19,16 @@ import com.tangem.tap.domain.model.UserWallet
|
|||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.model.builders.UserWalletBuilder
|
||||
import com.tangem.tap.domain.scanCard.ScanCardProcessor
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.totalFiatBalanceCalculator
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
|
@ -59,18 +62,17 @@ internal class WalletSelectorMiddleware {
|
|||
addWallet()
|
||||
}
|
||||
is WalletSelectorAction.SelectWallet -> {
|
||||
selectWallet(action.walletId)
|
||||
}
|
||||
is WalletSelectorAction.UnlockWalletWithCard -> {
|
||||
unlockWalletWithCard(action.walletId)
|
||||
selectWallet(action.userWalletId)
|
||||
}
|
||||
is WalletSelectorAction.RemoveWallets -> {
|
||||
removeWallets(action.walletIdsToRemove, state)
|
||||
deleteWallets(action.userWalletsIds, state)
|
||||
}
|
||||
is WalletSelectorAction.RenameWallet -> {
|
||||
renameWallet(action.walletId, action.newName)
|
||||
renameWallet(action.userWalletId, action.newName)
|
||||
}
|
||||
is WalletSelectorAction.ChangeAppCurrency -> {
|
||||
refreshUserWalletsAmounts()
|
||||
}
|
||||
is WalletSelectorAction.ChangeAppCurrency,
|
||||
is WalletSelectorAction.AddWallet.Success,
|
||||
is WalletSelectorAction.AddWallet.Error,
|
||||
is WalletSelectorAction.SelectedWalletChanged,
|
||||
|
|
@ -96,13 +98,17 @@ internal class WalletSelectorMiddleware {
|
|||
|
||||
private fun updateBalances(walletStores: Map<UserWalletId, List<WalletStoreModel>>, state: WalletSelectorState) {
|
||||
walletStores.forEach { (walletId, walletStores) ->
|
||||
scope.launch {
|
||||
val updatedWallet = state.wallets
|
||||
.find { it.id == walletId.stringValue }
|
||||
?.updateWalletStoresAndCalculateFiatBalance(walletStores)
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val foundWallet = state.wallets
|
||||
.find { it.id == walletId }
|
||||
|
||||
if (updatedWallet != null) {
|
||||
store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet))
|
||||
if (foundWallet != null) {
|
||||
val updatedWallet = foundWallet
|
||||
.updateWalletStoresAndCalculateFiatBalance(walletStores)
|
||||
|
||||
if (foundWallet != updatedWallet) {
|
||||
store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -135,45 +141,38 @@ internal class WalletSelectorMiddleware {
|
|||
.doOnSuccess {
|
||||
Analytics.send(MyWallets.CardWasScanned)
|
||||
|
||||
userWalletsListManager.selectWallet(userWallet.walletId)
|
||||
store.dispatchOnMain(WalletSelectorAction.AddWallet.Success)
|
||||
updateAccessCodeRequestPolicy(userWallet)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.onUserWalletSelected(userWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectWallet(id: String) {
|
||||
private fun selectWallet(userWalletId: UserWalletId) {
|
||||
scope.launch {
|
||||
userWalletsListManager.selectWallet(UserWalletId(id))
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.HandleError(error))
|
||||
}
|
||||
.doOnSuccess { selectedWallet ->
|
||||
updateAccessCodeRequestPolicy(selectedWallet)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.onUserWalletSelected(selectedWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unlockWalletWithCard(id: String) {
|
||||
scope.launch {
|
||||
userWalletsListManager.get(UserWalletId(id))
|
||||
userWalletsListManager.get(userWalletId)
|
||||
.flatMap { userWallet ->
|
||||
updateUserWalletWithScannedCard(userWallet)
|
||||
}
|
||||
.flatMap { updatedUserWallet ->
|
||||
unlockUserWallet(updatedUserWallet)
|
||||
if (userWallet.isLocked) {
|
||||
unlockUserWalletWithScannedCard(userWallet)
|
||||
} else {
|
||||
userWalletsListManager.selectWallet(userWalletId)
|
||||
}
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.HandleError(error))
|
||||
}
|
||||
.doOnSuccess {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
|
||||
if (selectedUserWallet != null) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.onUserWalletSelected(selectedUserWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult<UserWallet> {
|
||||
private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse)
|
||||
return tangemSdkManager.scanCard(userWallet.cardId)
|
||||
.map { scannedCard ->
|
||||
userWallet.copy(
|
||||
|
|
@ -182,23 +181,26 @@ internal class WalletSelectorMiddleware {
|
|||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun unlockUserWallet(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
return userWalletsListManager.unlockWithCard(userWallet)
|
||||
.doOnSuccess {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.onUserWalletSelected(userWallet)
|
||||
.flatMap { updatedUserWallet ->
|
||||
userWalletsListManager.save(updatedUserWallet, canOverride = true)
|
||||
}
|
||||
.doOnFailure {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(
|
||||
scanResponse = userWalletsListManager.selectedUserWalletSync?.scanResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeWallets(walletIdsToRemove: List<String>, state: WalletSelectorState) {
|
||||
private fun deleteWallets(userWalletsIds: List<UserWalletId>, state: WalletSelectorState) {
|
||||
Analytics.send(MyWallets.Button.DeleteWalletTapped)
|
||||
|
||||
scope.launch {
|
||||
when (walletIdsToRemove.size) {
|
||||
when (userWalletsIds.size) {
|
||||
state.wallets.size -> clearUserWallets()
|
||||
else -> removeUserWallets(walletIdsToRemove, state)
|
||||
else -> deleteUserWallets(
|
||||
userWalletsIds = userWalletsIds,
|
||||
currentSelectedWalletId = state.selectedWalletId,
|
||||
)
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.HandleError(error))
|
||||
|
|
@ -206,11 +208,11 @@ internal class WalletSelectorMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun renameWallet(walletId: String, newName: String) {
|
||||
private fun renameWallet(userWalletId: UserWalletId, newName: String) {
|
||||
Analytics.send(MyWallets.Button.EditWalletTapped)
|
||||
|
||||
scope.launch {
|
||||
userWalletsListManager.get(walletId = UserWalletId(walletId))
|
||||
userWalletsListManager.get(userWalletId)
|
||||
.map { it.copy(name = newName) }
|
||||
.flatMap { userWalletsListManager.save(it, canOverride = true) }
|
||||
.doOnFailure { error ->
|
||||
|
|
@ -219,6 +221,50 @@ internal class WalletSelectorMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun refreshUserWalletsAmounts() {
|
||||
scope.launch {
|
||||
walletStoresManager.updateAmounts(
|
||||
userWallets = userWalletsListManager.userWallets.first(),
|
||||
)
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.HandleError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun clearUserWallets(): CompletionResult<Unit> {
|
||||
return userWalletsListManager.clear()
|
||||
.flatMap { walletStoresManager.clear() }
|
||||
.flatMap { tangemSdkManager.clearSavedUserCodes() }
|
||||
.doOnSuccess {
|
||||
// !!! Workaround !!!
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
delay(timeMillis = 280)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun deleteUserWallets(
|
||||
userWalletsIds: List<UserWalletId>,
|
||||
currentSelectedWalletId: UserWalletId?,
|
||||
): CompletionResult<Unit> {
|
||||
return userWalletsListManager.delete(userWalletsIds)
|
||||
.flatMap { walletStoresManager.delete(userWalletsIds) }
|
||||
.flatMap { deleteAccessCodes(userWalletsIds) }
|
||||
.doOnSuccess {
|
||||
val selectedWallet = userWalletsListManager.selectedUserWalletSync
|
||||
|
||||
when {
|
||||
selectedWallet == null -> {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
|
||||
}
|
||||
currentSelectedWalletId != selectedWallet.walletId -> {
|
||||
store.onUserWalletSelected(selectedWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun scanCardInternal(
|
||||
crossinline onCardScanned: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
|
|
@ -236,37 +282,13 @@ internal class WalletSelectorMiddleware {
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun clearUserWallets(): CompletionResult<Unit> {
|
||||
return userWalletsListManager.clear()
|
||||
.flatMap { walletStoresManager.clear() }
|
||||
.doOnSuccess {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
}
|
||||
private suspend fun deleteAccessCodes(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty()
|
||||
.asSequence()
|
||||
.filter { it.walletId in userWalletsIds }
|
||||
.flatMap { it.cardsInWallet }
|
||||
|
||||
private suspend fun removeUserWallets(
|
||||
walletIdsToRemove: List<String>,
|
||||
state: WalletSelectorState,
|
||||
): CompletionResult<Unit> {
|
||||
val prevSelectedWalletId = state.selectedWalletId
|
||||
return userWalletsListManager.delete(walletIdsToRemove.map { UserWalletId(it) })
|
||||
.flatMap { walletStoresManager.delete(walletIdsToRemove) }
|
||||
.doOnSuccess {
|
||||
val selectedWallet = userWalletsListManager.selectedUserWalletSync ?: return@doOnSuccess
|
||||
val isSelectedWalletRemoved = prevSelectedWalletId != selectedWallet.walletId.stringValue
|
||||
|
||||
if (isSelectedWalletRemoved) {
|
||||
updateAccessCodeRequestPolicy(selectedWallet)
|
||||
store.onUserWalletSelected(selectedWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAccessCodeRequestPolicy(userWallet: UserWallet) {
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
|
||||
userWallet.hasAccessCode,
|
||||
)
|
||||
return tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet())
|
||||
}
|
||||
|
||||
private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.tap.features.walletSelector.redux
|
||||
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.tap.common.extensions.replaceByOrAdd
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -21,7 +19,7 @@ internal object WalletSelectorReducer {
|
|||
wallets = action.userWallets.updateWalletsModels(state.wallets),
|
||||
)
|
||||
is WalletSelectorAction.SelectedWalletChanged -> state.copy(
|
||||
selectedWalletId = action.selectedWallet.walletId.stringValue,
|
||||
selectedWalletId = action.selectedWallet.walletId,
|
||||
)
|
||||
is WalletSelectorAction.IsLockedChanged -> state.copy(
|
||||
isLocked = action.isLocked,
|
||||
|
|
@ -56,7 +54,6 @@ internal object WalletSelectorReducer {
|
|||
)
|
||||
is WalletSelectorAction.WalletStoresChanged,
|
||||
is WalletSelectorAction.SelectWallet,
|
||||
is WalletSelectorAction.UnlockWalletWithCard,
|
||||
is WalletSelectorAction.RemoveWallets,
|
||||
is WalletSelectorAction.RenameWallet,
|
||||
-> state
|
||||
|
|
@ -66,7 +63,7 @@ internal object WalletSelectorReducer {
|
|||
private fun List<UserWallet>.updateWalletsModels(prevWallets: List<UserWalletModel>): List<UserWalletModel> {
|
||||
return this.map { userWallet ->
|
||||
prevWallets
|
||||
.find { it.id == userWallet.walletId.stringValue }
|
||||
.find { it.id == userWallet.walletId }
|
||||
?.let {
|
||||
it.copy(
|
||||
name = userWallet.name,
|
||||
|
|
@ -77,7 +74,7 @@ internal object WalletSelectorReducer {
|
|||
}
|
||||
?: with(userWallet) {
|
||||
UserWalletModel(
|
||||
id = walletId.stringValue,
|
||||
id = walletId,
|
||||
name = name,
|
||||
artworkUrl = artworkUrl,
|
||||
type = getType(),
|
||||
|
|
@ -92,12 +89,17 @@ internal object WalletSelectorReducer {
|
|||
userWalletModel: UserWalletModel,
|
||||
): List<UserWalletModel> {
|
||||
return ArrayList(this).apply {
|
||||
replaceByOrAdd(userWalletModel) { it.id == userWalletModel.id }
|
||||
val index = indexOfFirst { it.id == userWalletModel.id }
|
||||
if (index == -1) {
|
||||
add(userWalletModel)
|
||||
} else {
|
||||
this[index] = userWalletModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserWallet.getType(prevType: UserWalletModel.Type? = null): UserWalletModel.Type {
|
||||
return if (scanResponse.card.isMultiwalletAllowed) {
|
||||
return if (isMultiCurrency) {
|
||||
UserWalletModel.Type.MultiCurrency(
|
||||
cardsInWallet = (scanResponse.card.backupStatus as? CardDTO.BackupStatus.Active)
|
||||
?.cardCount?.inc()
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package com.tangem.tap.features.walletSelector.redux
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class WalletSelectorState(
|
||||
val wallets: List<UserWalletModel> = emptyList(),
|
||||
val selectedWalletId: String? = null,
|
||||
val selectedWalletId: UserWalletId? = null,
|
||||
val isLocked: Boolean = false,
|
||||
val fiatCurrency: FiatCurrency = FiatCurrency.Default,
|
||||
val isCardSavingInProgress: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ internal fun WalletSelectorScreenState.updateWithNewState(
|
|||
return this.copy(
|
||||
multiCurrencyWallets = multiCurrencyWallets,
|
||||
singleCurrencyWallets = singleCurrencyWallets,
|
||||
selectedWalletId = newState.selectedWalletId,
|
||||
editingWalletsIds = editingWalletsIds.filter { it in walletsIds },
|
||||
selectedUserWalletId = newState.selectedWalletId,
|
||||
editingUserWalletsIds = editingUserWalletsIds.filter { it in walletsIds },
|
||||
isLocked = newState.isLocked,
|
||||
showUnlockProgress = newState.isUnlockInProgress,
|
||||
showAddCardProgress = newState.isCardSavingInProgress,
|
||||
|
|
@ -70,7 +70,7 @@ private fun List<UserWalletModel>.toUiModels(
|
|||
imageUrl = artworkUrl,
|
||||
balance = balance,
|
||||
isLocked = isLocked,
|
||||
tokenName = type.blockchainName ?: "—",
|
||||
tokenName = type.blockchainName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -39,18 +45,12 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<Wa
|
|||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
override fun ScreenContent(
|
||||
modifier: Modifier,
|
||||
state: WalletSelectorScreenState,
|
||||
) {
|
||||
override fun ScreenContent(modifier: Modifier, state: WalletSelectorScreenState) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
|
||||
val renameWalletDialog by rememberUpdatedState(newValue = state.renameWalletDialog)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.nestedScroll(connection = rememberNestedScrollInteropConnection()),
|
||||
) {
|
||||
Box(modifier = modifier.nestedScroll(rememberNestedScrollInteropConnection())) {
|
||||
WalletSelectorScreenContent(
|
||||
state = state,
|
||||
onWalletClick = viewModel::walletClicked,
|
||||
|
|
@ -82,10 +82,7 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<Wa
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun RenameWalletDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
dialog: RenameWalletDialog?,
|
||||
) {
|
||||
private fun RenameWalletDialog(modifier: Modifier = Modifier, dialog: RenameWalletDialog?) {
|
||||
if (dialog == null) return
|
||||
RenameWalletDialogContent(modifier, dialog)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.walletSelector.ui
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem
|
||||
import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog
|
||||
|
|
@ -10,9 +11,9 @@ import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletI
|
|||
internal data class WalletSelectorScreenState(
|
||||
val multiCurrencyWallets: List<MultiCurrencyUserWalletItem> = emptyList(),
|
||||
val singleCurrencyWallets: List<SingleCurrencyUserWalletItem> = emptyList(),
|
||||
val selectedWalletId: String? = null,
|
||||
val selectedUserWalletId: UserWalletId? = null,
|
||||
val isLocked: Boolean = false,
|
||||
val editingWalletsIds: List<String> = listOf(),
|
||||
val editingUserWalletsIds: List<UserWalletId> = listOf(),
|
||||
val renameWalletDialog: RenameWalletDialog? = null,
|
||||
val showAddCardProgress: Boolean = false,
|
||||
val showUnlockProgress: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.walletSelector.ui
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||
|
|
@ -35,40 +36,38 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
store.dispatch(WalletSelectorAction.AddWallet)
|
||||
}
|
||||
|
||||
fun walletClicked(walletId: String) = with(state.value) {
|
||||
fun walletClicked(userWalletId: UserWalletId) = with(state.value) {
|
||||
when {
|
||||
isLocked -> {
|
||||
store.dispatch(WalletSelectorAction.UnlockWalletWithCard(walletId))
|
||||
editingUserWalletsIds.isNotEmpty() && isWalletLocked(userWalletId, this) -> Unit
|
||||
editingUserWalletsIds.isNotEmpty() && !editingUserWalletsIds.contains(userWalletId) -> {
|
||||
editWallet(userWalletId)
|
||||
}
|
||||
editingWalletsIds.isNotEmpty() && !editingWalletsIds.contains(walletId) -> {
|
||||
editWallet(walletId)
|
||||
editingUserWalletsIds.isNotEmpty() && editingUserWalletsIds.contains(userWalletId) -> {
|
||||
cancelWalletEditing(userWalletId)
|
||||
}
|
||||
editingWalletsIds.isNotEmpty() && editingWalletsIds.contains(walletId) -> {
|
||||
cancelWalletEditing(walletId)
|
||||
}
|
||||
selectedWalletId != walletId -> {
|
||||
store.dispatch(WalletSelectorAction.SelectWallet(walletId))
|
||||
selectedUserWalletId != userWalletId -> {
|
||||
store.dispatch(WalletSelectorAction.SelectWallet(userWalletId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun walletLongClicked(walletId: String) = with(state.value) {
|
||||
if (!isLocked && editingWalletsIds.isEmpty()) {
|
||||
editWallet(walletId)
|
||||
fun walletLongClicked(userWalletId: UserWalletId) = with(state.value) {
|
||||
if (!isWalletLocked(userWalletId, this) && editingUserWalletsIds.isEmpty()) {
|
||||
editWallet(userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelWalletsEditing() {
|
||||
stateInternal.update { prevState ->
|
||||
prevState.copy(
|
||||
editingWalletsIds = emptyList(),
|
||||
editingUserWalletsIds = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun renameWallet() = with(state.value) {
|
||||
if (editingWalletsIds.isNotEmpty() && renameWalletDialog == null) {
|
||||
val editedWalletId = editingWalletsIds.first()
|
||||
if (editingUserWalletsIds.isNotEmpty() && renameWalletDialog == null) {
|
||||
val editedWalletId = editingUserWalletsIds.first()
|
||||
val editedWallet = (multiCurrencyWallets + singleCurrencyWallets)
|
||||
.find { it.id == editedWalletId }
|
||||
|
||||
|
|
@ -80,7 +79,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
stateInternal.update { prevState ->
|
||||
prevState.copy(
|
||||
renameWalletDialog = null,
|
||||
editingWalletsIds = emptyList(),
|
||||
editingUserWalletsIds = emptyList(),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -103,8 +102,8 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
}
|
||||
|
||||
fun deleteWallets() = with(state.value) {
|
||||
if (editingWalletsIds.isNotEmpty()) {
|
||||
store.dispatch(WalletSelectorAction.RemoveWallets(walletIdsToRemove = editingWalletsIds))
|
||||
if (editingUserWalletsIds.isNotEmpty()) {
|
||||
store.dispatch(WalletSelectorAction.RemoveWallets(userWalletsIds = editingUserWalletsIds))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,22 +121,27 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
private fun editWallet(walletId: String) {
|
||||
private fun editWallet(userWalletId: UserWalletId) {
|
||||
stateInternal.update { prevState ->
|
||||
prevState.copy(
|
||||
editingWalletsIds = prevState.editingWalletsIds + walletId,
|
||||
editingUserWalletsIds = prevState.editingUserWalletsIds + userWalletId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelWalletEditing(walletId: String) {
|
||||
private fun cancelWalletEditing(userWalletId: UserWalletId) {
|
||||
stateInternal.update { prevState ->
|
||||
prevState.copy(
|
||||
editingWalletsIds = prevState.editingWalletsIds - walletId,
|
||||
editingUserWalletsIds = prevState.editingUserWalletsIds - userWalletId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isWalletLocked(userWalletId: UserWalletId, state: WalletSelectorScreenState): Boolean = with(state) {
|
||||
multiCurrencyWallets.find { it.id == userWalletId }?.isLocked
|
||||
?: singleCurrencyWallets.find { it.id == userWalletId }?.isLocked ?: isLocked
|
||||
}
|
||||
|
||||
private fun subscribeToStoreChanges() {
|
||||
store.subscribe(this) { appState ->
|
||||
appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.walletSelector.ui.components
|
||||
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState
|
||||
import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem
|
||||
import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem
|
||||
|
|
@ -7,7 +8,7 @@ import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem
|
|||
|
||||
internal object MockData {
|
||||
private val multiCurrencyUserWallet = MultiCurrencyUserWalletItem(
|
||||
id = "wallet_1",
|
||||
id = UserWalletId("wallet_1"),
|
||||
balance = UserWalletItem.Balance(
|
||||
amount = "6781.05 $",
|
||||
isLoading = false,
|
||||
|
|
@ -20,7 +21,7 @@ internal object MockData {
|
|||
)
|
||||
|
||||
private val singleCurrencyUserWallet = SingleCurrencyUserWalletItem(
|
||||
id = "wallet_4",
|
||||
id = UserWalletId("wallet_4"),
|
||||
balance = UserWalletItem.Balance(
|
||||
amount = "6781.05 $",
|
||||
isLoading = false,
|
||||
|
|
@ -34,11 +35,11 @@ internal object MockData {
|
|||
val state = WalletSelectorScreenState(
|
||||
multiCurrencyWallets = listOf(
|
||||
multiCurrencyUserWallet,
|
||||
multiCurrencyUserWallet.copy(id = "wallet_2"),
|
||||
multiCurrencyUserWallet.copy(id = "wallet_3", tokensCount = 2, cardsInWallet = 1),
|
||||
multiCurrencyUserWallet.copy(id = UserWalletId("wallet_2")),
|
||||
multiCurrencyUserWallet.copy(id = UserWalletId("wallet_3"), tokensCount = 2, cardsInWallet = 1),
|
||||
),
|
||||
singleCurrencyWallets = listOf(singleCurrencyUserWallet),
|
||||
selectedWalletId = multiCurrencyUserWallet.id,
|
||||
selectedUserWalletId = multiCurrencyUserWallet.id,
|
||||
isLocked = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tap.features.walletSelector.ui.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -11,8 +11,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Text
|
||||
|
|
@ -23,61 +23,84 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconRight
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.atoms.Hand
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState
|
||||
import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun WalletSelectorScreenContent(
|
||||
modifier: Modifier = Modifier,
|
||||
state: WalletSelectorScreenState,
|
||||
onWalletClick: (walletId: String) -> Unit,
|
||||
onWalletLongClick: (walletId: String) -> Unit,
|
||||
onWalletClick: (UserWalletId) -> Unit,
|
||||
onWalletLongClick: (UserWalletId) -> Unit,
|
||||
onUnlockClick: () -> Unit,
|
||||
onAddCardClick: () -> Unit,
|
||||
onClearSelectedClick: () -> Unit,
|
||||
onEditSelectedWalletClick: () -> Unit,
|
||||
onDeleteSelectedWalletsClick: () -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Header(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
editingWalletsIds = state.editingWalletsIds,
|
||||
onClearSelectedClick = onClearSelectedClick,
|
||||
onEditSelectedWalletClick = onEditSelectedWalletClick,
|
||||
onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(
|
||||
state = rememberScrollState(),
|
||||
),
|
||||
) {
|
||||
WalletsList(
|
||||
multiCurrencyWallets = state.multiCurrencyWallets,
|
||||
singleCurrencyWallets = state.singleCurrencyWallets,
|
||||
selectedWalletId = state.selectedWalletId,
|
||||
checkedWalletIds = state.editingWalletsIds,
|
||||
LazyColumn {
|
||||
stickyHeader {
|
||||
Header(
|
||||
editingWalletsIds = state.editingUserWalletsIds,
|
||||
onClearSelectedClick = onClearSelectedClick,
|
||||
onEditSelectedWalletClick = onEditSelectedWalletClick,
|
||||
onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
WalletsTitle(textResId = R.string.user_wallet_list_multi_header, wallets = state.multiCurrencyWallets)
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
items = state.multiCurrencyWallets,
|
||||
key = { _, wallet -> wallet.id.stringValue },
|
||||
) { _, wallet ->
|
||||
|
||||
WalletItem(
|
||||
wallet = wallet,
|
||||
isSelected = remember(state.selectedUserWalletId) { wallet.id == state.selectedUserWalletId },
|
||||
isChecked = remember(state.editingUserWalletsIds) { wallet.id in state.editingUserWalletsIds },
|
||||
onWalletClick = onWalletClick,
|
||||
onWalletLongClick = onWalletLongClick,
|
||||
)
|
||||
SpacerH24()
|
||||
}
|
||||
|
||||
item {
|
||||
WalletsTitle(textResId = R.string.user_wallet_list_single_header, wallets = state.singleCurrencyWallets)
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
items = state.singleCurrencyWallets,
|
||||
key = { _, wallet -> wallet.id.stringValue },
|
||||
) { _, wallet ->
|
||||
WalletItem(
|
||||
wallet = wallet,
|
||||
isSelected = remember(state.selectedUserWalletId) { wallet.id == state.selectedUserWalletId },
|
||||
isChecked = remember(state.editingUserWalletsIds) { wallet.id in state.editingUserWalletsIds },
|
||||
onWalletClick = onWalletClick,
|
||||
onWalletLongClick = onWalletLongClick,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Footer(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = dimensionResource(id = R.dimen.spacing24),
|
||||
bottom = dimensionResource(id = R.dimen.spacing16),
|
||||
)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
isLocked = state.isLocked,
|
||||
|
|
@ -86,25 +109,28 @@ internal fun WalletSelectorScreenContent(
|
|||
onUnlockClick = onUnlockClick,
|
||||
onAddCardClick = onAddCardClick,
|
||||
)
|
||||
SpacerH16()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Header(
|
||||
modifier: Modifier = Modifier,
|
||||
editingWalletsIds: List<String>,
|
||||
editingWalletsIds: List<UserWalletId>,
|
||||
onClearSelectedClick: () -> Unit,
|
||||
onEditSelectedWalletClick: () -> Unit,
|
||||
onDeleteSelectedWalletsClick: () -> Unit,
|
||||
) {
|
||||
val editingWalletsSize by rememberUpdatedState(newValue = editingWalletsIds.size)
|
||||
val hasEditingWallets by remember {
|
||||
derivedStateOf { editingWalletsSize > 0 }
|
||||
}
|
||||
val hasEditingWallets by remember { derivedStateOf { editingWalletsSize > 0 } }
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = TangemTheme.colors.background.plain,
|
||||
shape = TangemTheme.shapes.bottomSheet,
|
||||
),
|
||||
) {
|
||||
Hand()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -114,7 +140,9 @@ private fun Header(
|
|||
) {
|
||||
if (hasEditingWallets) {
|
||||
EditWalletsBar(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
editingWalletsSize = editingWalletsSize,
|
||||
onClearSelectedClick = onClearSelectedClick,
|
||||
onEditSelectedWalletClick = onEditSelectedWalletClick,
|
||||
|
|
@ -132,44 +160,15 @@ private fun Header(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun WalletsList(
|
||||
modifier: Modifier = Modifier,
|
||||
multiCurrencyWallets: List<UserWalletItem>,
|
||||
singleCurrencyWallets: List<UserWalletItem>,
|
||||
selectedWalletId: String?,
|
||||
checkedWalletIds: List<String>,
|
||||
onWalletClick: (walletId: String) -> Unit,
|
||||
onWalletLongClick: (walletId: String) -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
val walletsSection = @Composable { wallets: List<UserWalletItem> ->
|
||||
wallets.forEachIndexed { index, wallet ->
|
||||
if (index == 0) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing16),
|
||||
text = wallet.headerText.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
WalletItem(
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onWalletClick(wallet.id) },
|
||||
onLongClick = { onWalletLongClick(wallet.id) },
|
||||
)
|
||||
.padding(all = TangemTheme.dimens.spacing16),
|
||||
wallet = wallet,
|
||||
isSelected = wallet.id == selectedWalletId,
|
||||
isChecked = wallet.id in checkedWalletIds,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
walletsSection(multiCurrencyWallets)
|
||||
walletsSection(singleCurrencyWallets)
|
||||
private fun WalletsTitle(@StringRes textResId: Int, wallets: List<*>) {
|
||||
if (wallets.isNotEmpty()) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing16),
|
||||
text = stringResource(id = textResId),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -278,11 +277,6 @@ private fun WalletSelectorScreenContentSample(
|
|||
.background(color = TangemTheme.colors.background.primary),
|
||||
) {
|
||||
WalletSelectorScreenContent(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.plain,
|
||||
shape = TangemTheme.shapes.bottomSheet,
|
||||
),
|
||||
state = MockData.state.copy(isLocked = true),
|
||||
onWalletClick = { /* no-op */ },
|
||||
onWalletLongClick = { /* no-op */ },
|
||||
|
|
@ -320,13 +314,8 @@ private fun WalletSelectorScreenContent_EditWallets_Sample(
|
|||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
WalletSelectorScreenContent(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.plain,
|
||||
shape = TangemTheme.shapes.bottomSheet,
|
||||
),
|
||||
state = MockData.state
|
||||
.copy(editingWalletsIds = listOf(MockData.state.multiCurrencyWallets[2].id)),
|
||||
.copy(editingUserWalletsIds = listOf(MockData.state.multiCurrencyWallets[2].id)),
|
||||
onWalletClick = { /* no-op */ },
|
||||
onWalletLongClick = { /* no-op */ },
|
||||
onUnlockClick = { /* no-op */ },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tap.features.walletSelector.ui.components
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
|
|
@ -26,6 +28,7 @@ import com.tangem.core.ui.components.SpacerH2
|
|||
import com.tangem.core.ui.components.SpacerW6
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.common.compose.TangemTypography
|
||||
import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem
|
||||
import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem
|
||||
|
|
@ -33,15 +36,23 @@ import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem
|
|||
import com.tangem.wallet.R
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun WalletItem(
|
||||
modifier: Modifier = Modifier,
|
||||
wallet: UserWalletItem,
|
||||
isSelected: Boolean,
|
||||
isChecked: Boolean,
|
||||
onWalletClick: (UserWalletId) -> Unit,
|
||||
onWalletLongClick: (UserWalletId) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
onClick = { onWalletClick(wallet.id) },
|
||||
onLongClick = { onWalletLongClick(wallet.id) },
|
||||
)
|
||||
.height(72.dp)
|
||||
.padding(all = TangemTheme.dimens.spacing16),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
WalletCardImage(
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
package com.tangem.tap.features.walletSelector.ui.model
|
||||
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
|
||||
internal sealed interface UserWalletItem {
|
||||
val id: String
|
||||
val id: UserWalletId
|
||||
val name: String
|
||||
val imageUrl: String
|
||||
val balance: Balance
|
||||
val isLocked: Boolean
|
||||
|
||||
val headerText: TextReference
|
||||
get() = when (this) {
|
||||
is MultiCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_multi_header)
|
||||
is SingleCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_single_header)
|
||||
}
|
||||
|
||||
data class Balance(
|
||||
val amount: String,
|
||||
val isLoading: Boolean,
|
||||
|
|
@ -23,7 +16,7 @@ internal sealed interface UserWalletItem {
|
|||
}
|
||||
|
||||
internal data class MultiCurrencyUserWalletItem(
|
||||
override val id: String,
|
||||
override val id: UserWalletId,
|
||||
override val name: String,
|
||||
override val imageUrl: String,
|
||||
override val balance: UserWalletItem.Balance,
|
||||
|
|
@ -33,7 +26,7 @@ internal data class MultiCurrencyUserWalletItem(
|
|||
) : UserWalletItem
|
||||
|
||||
internal data class SingleCurrencyUserWalletItem(
|
||||
override val id: String,
|
||||
override val id: UserWalletId,
|
||||
override val name: String,
|
||||
override val imageUrl: String,
|
||||
override val balance: UserWalletItem.Balance,
|
||||
|
|
|
|||
|
|
@ -59,11 +59,6 @@ internal class WelcomeMiddleware {
|
|||
}
|
||||
.doOnSuccess { selectedUserWallet ->
|
||||
if (selectedUserWallet != null) {
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
|
||||
selectedUserWallet.hasAccessCode,
|
||||
)
|
||||
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(selectedUserWallet)
|
||||
|
|
@ -78,8 +73,7 @@ internal class WelcomeMiddleware {
|
|||
scanCardInternal { scanResponse ->
|
||||
val userWallet = UserWalletBuilder(scanResponse).build()
|
||||
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false)
|
||||
userWalletsListManager.unlockWithCard(userWallet)
|
||||
userWalletsListManager.save(userWallet, canOverride = true)
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error))
|
||||
}
|
||||
|
|
@ -100,6 +94,9 @@ internal class WelcomeMiddleware {
|
|||
private suspend inline fun scanCardInternal(
|
||||
crossinline onCardScanned: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
tangemSdkManager.setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
|
||||
)
|
||||
ScanCardProcessor.scan(
|
||||
onSuccess = { scanResponse ->
|
||||
scope.launch { onCardScanned(scanResponse) }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue