Updated on 2026-08-14
This commit is contained in:
parent
e561cac009
commit
c28d80cb18
91 changed files with 178 additions and 5792 deletions
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.entities
|
||||
|
||||
import com.tangem.tap.features.send.redux.states.ButtonState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
|
||||
open class Button(val enabled: Boolean)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.tap.common.entities
|
||||
|
||||
import com.tangem.tap.common.toggleWidget.WidgetState
|
||||
|
||||
enum class ProgressState : WidgetState { Loading, Done, Error }
|
||||
|
|
@ -27,8 +27,6 @@ import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
|
|||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.shop.ui.ShopFragment
|
||||
import com.tangem.tap.features.tokens.impl.presentation.TokensListFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.walletSelector.ui.WalletSelectorBottomSheetFragment
|
||||
import com.tangem.tap.features.welcome.ui.WelcomeFragment
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
@ -132,16 +130,9 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.OnboardingTwins -> TwinsCardsFragment()
|
||||
AppScreen.OnboardingOther -> OnboardingOtherCardsFragment()
|
||||
AppScreen.Wallet -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
getDependency = DaggerGraphState::walletFeatureToggles,
|
||||
)
|
||||
if (featureToggles.isRedesignedScreenEnabled) {
|
||||
store.state.daggerGraphState
|
||||
.get(getDependency = DaggerGraphState::walletRouter)
|
||||
.getEntryFragment()
|
||||
} else {
|
||||
WalletFragment()
|
||||
}
|
||||
store.state.daggerGraphState
|
||||
.get(getDependency = DaggerGraphState::walletRouter)
|
||||
.getEntryFragment()
|
||||
}
|
||||
AppScreen.Send -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
|
|
@ -176,16 +167,9 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
}
|
||||
AppScreen.AddCustomToken -> AddCustomTokenFragment()
|
||||
AppScreen.WalletDetails -> {
|
||||
val featureToggles = store.state.daggerGraphState.get(
|
||||
getDependency = DaggerGraphState::walletFeatureToggles,
|
||||
)
|
||||
if (featureToggles.isRedesignedScreenEnabled) {
|
||||
store.state.daggerGraphState
|
||||
.get(getDependency = DaggerGraphState::tokenDetailsRouter)
|
||||
.getEntryFragment()
|
||||
} else {
|
||||
WalletDetailsFragment()
|
||||
}
|
||||
store.state.daggerGraphState
|
||||
.get(getDependency = DaggerGraphState::tokenDetailsRouter)
|
||||
.getEntryFragment()
|
||||
}
|
||||
AppScreen.WalletConnectSessions -> WalletConnectFragment()
|
||||
AppScreen.QrScan -> QrScanFragment()
|
||||
|
|
|
|||
|
|
@ -42,12 +42,8 @@ fun Store<*>.dispatchNotification(resId: Int) {
|
|||
dispatchOnMain(GlobalAction.ShowNotification(resId))
|
||||
}
|
||||
|
||||
suspend fun Store<AppState>.onUserWalletSelected(
|
||||
userWallet: UserWallet,
|
||||
refresh: Boolean = false,
|
||||
sendAnalyticsEvent: Boolean = false,
|
||||
) {
|
||||
state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh, sendAnalyticsEvent)
|
||||
suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean = false) {
|
||||
state.globalState.tapWalletManager.onWalletSelected(userWallet, sendAnalyticsEvent)
|
||||
}
|
||||
|
||||
fun Store<*>.dispatchToastNotification(resId: Int) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.common.extensions
|
|||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.amountToCreateAccount
|
||||
import com.tangem.tap.common.TestActions
|
||||
|
|
@ -10,8 +11,6 @@ import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -78,4 +77,23 @@ fun WalletManager?.getAddressData(): WalletDataModel.AddressData? {
|
|||
|
||||
val addressDataList = wallet.createAddressesData()
|
||||
return if (addressDataList.isEmpty()) null else addressDataList[0]
|
||||
}
|
||||
|
||||
fun Wallet.createAddressesData(): List<WalletDataModel.AddressData> {
|
||||
val listOfAddressData = mutableListOf<WalletDataModel.AddressData>()
|
||||
// put a defaultAddress at the first place
|
||||
addresses.forEach {
|
||||
val addressData = WalletDataModel.AddressData(
|
||||
it.value,
|
||||
it.type,
|
||||
getShareUri(it.value),
|
||||
getExploreUrl(it.value),
|
||||
)
|
||||
if (it.type == AddressType.Default) {
|
||||
listOfAddressData.add(0, addressData)
|
||||
} else {
|
||||
listOfAddressData.add(addressData)
|
||||
}
|
||||
}
|
||||
return listOfAddressData
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
|
|||
import com.tangem.tap.features.shop.redux.ShopReducer
|
||||
import com.tangem.tap.features.signin.redux.SignInReducer
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensReducer
|
||||
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
|
|
@ -33,7 +32,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
|
|||
onboardingNoteState = OnboardingNoteReducer.reduce(action, state),
|
||||
onboardingWalletState = OnboardingWalletReducer.reduce(action, state),
|
||||
onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
|
||||
walletState = WalletReducer.reduce(action, state, appStateHolder),
|
||||
twinCardsState = TwinCardsReducer.reduce(action, state),
|
||||
sendState = SendScreenReducer.reduce(action, state.sendState),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
|
|
|
|||
|
|
@ -35,8 +35,6 @@ import com.tangem.tap.features.signin.redux.SignInMiddleware
|
|||
import com.tangem.tap.features.signin.redux.SignInState
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensState
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
|
|
@ -54,7 +52,6 @@ data class AppState(
|
|||
val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(),
|
||||
val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(),
|
||||
val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(),
|
||||
val walletState: WalletState = WalletState(),
|
||||
val twinCardsState: TwinCardsState = TwinCardsState(),
|
||||
val sendState: SendState = SendState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
|
|
@ -92,7 +89,6 @@ data class AppState(
|
|||
OnboardingNoteMiddleware.handler,
|
||||
OnboardingWalletMiddleware.handler,
|
||||
OnboardingOtherCardsMiddleware.handler,
|
||||
WalletMiddleware().walletMiddleware,
|
||||
TwinCardsMiddleware.handler,
|
||||
SendMiddleware().sendMiddleware,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
|
|
|
|||
|
|
@ -3,13 +3,9 @@ package com.tangem.tap.common.redux
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.tap.features.home.data.HomeRepositoryImpl
|
||||
import com.tangem.tap.features.home.domain.HomeRepository
|
||||
import com.tangem.tap.features.wallet.data.WalletRepositoryImpl
|
||||
import com.tangem.tap.features.wallet.domain.WalletRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
||||
class FeatureRepositoryProvider(tangemTechApi: TangemTechApi, dispatchers: CoroutineDispatcherProvider) {
|
||||
|
||||
val walletRepository: WalletRepository = WalletRepositoryImpl(tangemTechApi, dispatchers)
|
||||
|
||||
val homeRepository: HomeRepository = HomeRepositoryImpl(tangemTechApi, dispatchers)
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import com.tangem.tap.common.extensions.dispatchOnMain
|
|||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.exchangeServices.BuyExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.CardExchangeRules
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
|
|
@ -87,7 +86,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
// store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
// }
|
||||
|
||||
store.dispatch(WalletAction.Warnings.Update)
|
||||
store.dispatch(SendAction.Warnings.Update)
|
||||
}
|
||||
}
|
||||
|
|
@ -115,9 +113,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
}
|
||||
feedbackManager.openChat(chatConfig, action.feedbackData)
|
||||
}
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures))
|
||||
}
|
||||
is GlobalAction.UpdateFeedbackInfo -> {
|
||||
store.state.globalState.feedbackManager?.infoHolder
|
||||
?.setWalletsInfo(action.walletManagers)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package com.tangem.tap.common.toggleWidget
|
|||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -2,25 +2,16 @@ package com.tangem.tap.common.toggleWidget
|
|||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.AlphaAnimation
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationSet
|
||||
import android.view.animation.AnticipateOvershootInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.view.animation.RotateAnimation
|
||||
import android.view.animation.ScaleAnimation
|
||||
import android.view.animation.*
|
||||
import android.widget.ViewSwitcher
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
class RefreshBalanceWidget(
|
||||
private val root: ViewGroup,
|
||||
) : ViewStateWidget {
|
||||
class RefreshBalanceWidget(root: ViewGroup) : ViewStateWidget {
|
||||
|
||||
var isShowing: Boolean? = null
|
||||
private set
|
||||
|
|
|
|||
|
|
@ -4,39 +4,27 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
|
|||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresError
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Account
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
class TapWalletManager(
|
||||
private val dispatchers: CoroutineDispatcherProvider = AppCoroutineDispatcherProvider(),
|
||||
|
|
@ -55,15 +43,15 @@ class TapWalletManager(
|
|||
val walletManagerFactory: WalletManagerFactory
|
||||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
|
||||
suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean, sendAnalyticsEvent: Boolean) {
|
||||
suspend fun onWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean) {
|
||||
// If a previous job was running, it gets cancelled before the new one starts,
|
||||
// ensuring that only one job is active at any given time.
|
||||
loadUserWalletDataJob = CoroutineScope(dispatchers.io)
|
||||
.launch { loadUserWalletData(userWallet, refresh, sendAnalyticsEvent) }
|
||||
.launch { loadUserWalletData(userWallet, sendAnalyticsEvent) }
|
||||
.also { it.join() }
|
||||
}
|
||||
|
||||
private suspend fun loadUserWalletData(userWallet: UserWallet, refresh: Boolean, sendAnalyticsEvent: Boolean) {
|
||||
private suspend fun loadUserWalletData(userWallet: UserWallet, sendAnalyticsEvent: Boolean) {
|
||||
Analytics.setContext(userWallet.scanResponse)
|
||||
if (sendAnalyticsEvent) {
|
||||
Analytics.send(Basic.WalletOpened())
|
||||
|
|
@ -78,114 +66,11 @@ class TapWalletManager(
|
|||
withMainContext {
|
||||
// Order is important
|
||||
store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer()))
|
||||
store.dispatchWalletAction(action = WalletAction.UserWalletChanged(userWallet))
|
||||
store.dispatchWalletAction(
|
||||
action = WalletAction.UpdateCanSaveUserWallets(
|
||||
canSaveUserWallets = store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
|
||||
.shouldSaveUserWalletsSync(),
|
||||
),
|
||||
)
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
store.dispatch(WalletConnectAction.ResetState)
|
||||
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
|
||||
store.dispatch(WalletConnectAction.RestoreSessions(scanResponse))
|
||||
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
|
||||
store.dispatchWalletAction(action = WalletAction.Warnings.CheckIfNeeded)
|
||||
}
|
||||
|
||||
val walletFeatureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
|
||||
if (!walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
setupWalletConnectV2(userWallet)
|
||||
loadData(userWallet = userWallet, refresh = refresh)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Store<AppState>.dispatchWalletAction(action: WalletAction) {
|
||||
val walletFeatureToggles = state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles)
|
||||
|
||||
if (!walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
dispatch(action = action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWalletConnectV2(userWallet: UserWallet) {
|
||||
val cardId = if (userWallet.scanResponse.card.backupStatus?.isActive != true) {
|
||||
userWallet.cardId
|
||||
} else { // if wallet has backup, any card from wallet can be used to sign
|
||||
null
|
||||
}
|
||||
scope.launch {
|
||||
val wcInteractor = store.state.daggerGraphState.walletConnectInteractor ?: return@launch
|
||||
wcInteractor.startListening(
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
cardId = cardId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadData(userWallet: UserWallet, refresh: Boolean = false) {
|
||||
walletStoresManager.fetch(userWallet, refresh)
|
||||
.doOnSuccess {
|
||||
Timber.d("Wallet stores fetched for ${userWallet.walletId}")
|
||||
store.dispatchOnMain(WalletAction.LoadData.Success)
|
||||
store.state.globalState.topUpController?.loadDataSuccess()
|
||||
store.dispatchWithMain(WalletAction.Warnings.CheckHashesCount.VerifyOnlineIfNeeded)
|
||||
|
||||
val wcInteractor = store.state.daggerGraphState.walletConnectInteractor
|
||||
wcInteractor?.setUserChains(getAccountsForWc(wcInteractor))
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
val errorAction = when (error) {
|
||||
is WalletStoresError -> when (error) {
|
||||
is WalletStoresError.FetchFiatRatesError -> WalletAction.LoadData.Failure(error = null)
|
||||
is WalletStoresError.UpdateWalletManagerTokensError -> WalletAction.LoadData.Failure(
|
||||
error = TapError.WalletManager.InternalError(
|
||||
message = error.cause.localizedMessage ?: error.customMessage,
|
||||
),
|
||||
)
|
||||
is WalletStoresError.WalletManagerNotCreated -> WalletAction.LoadData.Failure(
|
||||
error = TapError.WalletManager.CreationError,
|
||||
)
|
||||
is WalletStoresError.UnknownBlockchain -> WalletAction.LoadData.Failure(
|
||||
error = TapError.UnknownBlockchain,
|
||||
)
|
||||
is WalletStoresError.NoInternetConnection -> WalletAction.LoadData.Failure(
|
||||
error = TapError.NoInternetConnection,
|
||||
)
|
||||
}
|
||||
else -> WalletAction.LoadData.Failure(error = null)
|
||||
}
|
||||
|
||||
Timber.e(error, "Wallet stores fetching failed for ${userWallet.walletId}")
|
||||
|
||||
store.dispatchOnMain(errorAction)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAccountsForWc(wcInteractor: WalletConnectInteractor): List<Account> {
|
||||
val walletManagerToggles = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletFeatureToggles)
|
||||
val walletManagers = if (walletManagerToggles.isRedesignedScreenEnabled) {
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList()
|
||||
walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
|
||||
} else {
|
||||
store.state.walletState.walletManagers
|
||||
}
|
||||
|
||||
return walletManagers.mapNotNull {
|
||||
val wallet = it.wallet
|
||||
val chainId = wcInteractor.blockchainHelper.networkIdToChainIdOrNull(
|
||||
wallet.blockchain.toNetworkId(),
|
||||
)
|
||||
chainId?.let {
|
||||
Account(
|
||||
chainId,
|
||||
wallet.address,
|
||||
wallet.publicKey.derivationPath?.rawPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import com.tangem.domain.common.BlockchainNetwork
|
|||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.extensions.createAddressesData
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
|
||||
import java.math.BigDecimal
|
||||
|
||||
interface WalletStoreBuilder {
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
package com.tangem.tap.domain.statePrinter
|
||||
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.domain.redux.state.StringStateConverter
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WalletStateConverter : StringStateConverter<AppState> {
|
||||
private val converter = MoshiJsonConverter.INSTANCE
|
||||
|
||||
override fun convert(stateHolder: AppState): String {
|
||||
val walletState = stateHolder.walletState
|
||||
|
||||
val walletManagers = mutableListOf<Map<String, Any?>>()
|
||||
walletState.walletManagers.forEach {
|
||||
val wallet = it.wallet
|
||||
|
||||
val walletManagerMap = mutableMapOf<String, Any?>()
|
||||
walletManagerMap["walletManager"] = mapOf<String, Any>(
|
||||
"wallet" to convertWallet(it.wallet),
|
||||
)
|
||||
|
||||
walletManagers.add(walletManagerMap)
|
||||
}
|
||||
val json = converter.prettyPrint(walletManagers)
|
||||
return json
|
||||
}
|
||||
|
||||
private fun convertWallet(wallet: Wallet): MutableMap<String, Any> {
|
||||
val walletMap = mutableMapOf<String, Any>()
|
||||
val amounts = mutableMapOf<String, Any>()
|
||||
wallet.amounts.forEach { (type, amount) ->
|
||||
val amountMap = mapOf<String, Any?>(
|
||||
"value" to amount.value?.toPlainString(),
|
||||
"currencySymbol" to amount.currencySymbol,
|
||||
"decimals" to amount.decimals,
|
||||
"type" to amount.type::class.java.simpleName,
|
||||
)
|
||||
amounts[type::class.java.simpleName] = amountMap
|
||||
}
|
||||
|
||||
val publicKeyMap = mapOf(
|
||||
"seedKey" to wallet.publicKey.seedKey,
|
||||
"derivedKey" to wallet.publicKey.derivedKey,
|
||||
"derivationPath" to wallet.publicKey.derivationPath?.rawPath,
|
||||
"blockchainKey" to wallet.publicKey.blockchainKey,
|
||||
)
|
||||
|
||||
walletMap["address"] = wallet.address
|
||||
walletMap["blockchain"] = wallet.blockchain.name
|
||||
walletMap["curve"] = wallet.blockchain.getSupportedCurves()[0].curve
|
||||
walletMap["publicKey"] = publicKeyMap
|
||||
walletMap["amounts"] = amounts
|
||||
walletMap["addresses"] = wallet.addresses.toString()
|
||||
|
||||
return walletMap
|
||||
}
|
||||
}
|
||||
|
||||
fun printWalletState() {
|
||||
val stringState = WalletStateConverter().convert(store.state)
|
||||
Timber.d(stringState)
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
|
||||
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.*
|
||||
|
|
@ -38,8 +37,6 @@ import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTok
|
|||
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContactAddressValidator
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContractAddressValidatorResult
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
|
@ -591,14 +588,6 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun isTokenAlreadyAdded(): Boolean {
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
isTokenAlreadyAddedNew()
|
||||
} else {
|
||||
isTokenAlreadyAddedOld()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTokenAlreadyAddedNew(): Boolean {
|
||||
return currentCryptoCurrencies
|
||||
.filterIsInstance<CryptoCurrency.Token>()
|
||||
.any { token ->
|
||||
|
|
@ -621,32 +610,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isTokenAlreadyAddedOld(): Boolean {
|
||||
return store.state.walletState.walletsStores
|
||||
.map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
|
||||
.flatten()
|
||||
.filterIsInstance<Currency.Token>()
|
||||
.any { wrappedCurrency ->
|
||||
val contractAddress = uiState.form.contractAddressInputField.value
|
||||
val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
|
||||
val sameId = foundToken?.id == wrappedCurrency.token.id
|
||||
val sameAddress = contractAddress == wrappedCurrency.token.contractAddress
|
||||
val sameBlockchain =
|
||||
Blockchain.fromNetworkId(networkSelectorValue.toNetworkId()) == wrappedCurrency.blockchain
|
||||
val isSameDerivationPath = getDerivationPath().isSameDerivationPath(wrappedCurrency.derivationPath)
|
||||
sameId && sameAddress && sameBlockchain && isSameDerivationPath
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBlockchainAlreadyAdded(): Boolean {
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
isBlockchainAlreadyAddedNew()
|
||||
} else {
|
||||
isBlockchainAlreadyAddedOld()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBlockchainAlreadyAddedNew(): Boolean {
|
||||
return currentCryptoCurrencies
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.any { coin ->
|
||||
|
|
@ -655,18 +619,6 @@ internal class AddCustomTokenViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isBlockchainAlreadyAddedOld(): Boolean {
|
||||
return store.state.walletState.walletsStores
|
||||
.map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
|
||||
.flatten()
|
||||
.filterIsInstance<Currency.Blockchain>()
|
||||
.any {
|
||||
val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
|
||||
networkSelectorValue == it.blockchain &&
|
||||
getDerivationPath().isSameDerivationPath(it.derivationPath)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DerivationPath?.isSameDerivationPath(rawDerivationPath: String?): Boolean {
|
||||
return this == rawDerivationPath?.let { DerivationPath(it) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.tangem.tap.common.redux.AppState
|
|||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -25,7 +24,6 @@ object DemoHelper {
|
|||
TradeCryptoAction.Buy::class.java,
|
||||
TradeCryptoAction.Sell::class.java,
|
||||
BackupAction.StartBackup::class.java,
|
||||
WalletAction.ExploreAddress::class.java,
|
||||
DetailsAction.ResetToFactory.Start::class.java,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
|||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
|
|||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
|
|
@ -381,8 +380,6 @@ class DetailsMiddleware {
|
|||
preferencesStorage.shouldShowSaveUserWalletScreen = false
|
||||
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
|
||||
.saveShouldSaveUserWallets(item = true)
|
||||
|
||||
store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true))
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
|
|
@ -399,7 +396,6 @@ class DetailsMiddleware {
|
|||
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
|
||||
.saveShouldSaveUserWallets(item = false)
|
||||
|
||||
store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true))
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
|
|
|
|||
|
|
@ -6,15 +6,16 @@ import com.tangem.blockchain.common.derivation.DerivationStyle
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.walletconnect.WalletConnectActions
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -29,11 +30,11 @@ import com.tangem.tap.domain.walletconnect2.domain.models.Account
|
|||
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -115,7 +116,6 @@ class WalletConnectMiddleware {
|
|||
scope.launch {
|
||||
prepareWalletManager(
|
||||
scanResponse = data.scanResponse,
|
||||
walletState = store.state.walletState,
|
||||
blockchain = action.blockchain,
|
||||
session = data.session,
|
||||
walletConnectManager = walletConnectManager,
|
||||
|
|
@ -176,8 +176,14 @@ class WalletConnectMiddleware {
|
|||
// )
|
||||
}
|
||||
is WalletConnectAction.ScanCard -> {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
scanCard(scanResponse, action.session, action.chainId)
|
||||
val scanResponse = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.w("Unable to get selected user wallet for WC session")
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch(Dispatchers.Main) {
|
||||
scanCard(scanResponse, action.session, action.chainId)
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.ApproveSession -> {
|
||||
walletConnectManager.approve(action.session)
|
||||
|
|
@ -274,7 +280,6 @@ class WalletConnectMiddleware {
|
|||
val walletManager = getWalletManager(
|
||||
wallet = action.session.wallet,
|
||||
blockchain = blockchain,
|
||||
walletState = store.state.walletState,
|
||||
).guard {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
|
|
@ -372,19 +377,14 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
|
||||
private suspend fun getWalletManagers(): List<WalletManager> {
|
||||
val walletManagerToggles = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletFeatureToggles)
|
||||
return if (walletManagerToggles.isRedesignedScreenEnabled) {
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList()
|
||||
walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
|
||||
} else {
|
||||
store.state.walletState.walletManagers
|
||||
}
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: return emptyList()
|
||||
|
||||
return walletManagerFacade.getStoredWalletManagers(userWallet.walletId)
|
||||
}
|
||||
|
||||
private fun scanCard(scanResponse: ScanResponse, session: WalletConnectSession, chainId: Int?) {
|
||||
private suspend fun scanCard(userWallet: UserWallet, session: WalletConnectSession, chainId: Int?) {
|
||||
val blockchain = WalletConnectNetworkUtils.parseBlockchain(
|
||||
chainId = chainId,
|
||||
peer = session.peerMeta,
|
||||
|
|
@ -393,27 +393,28 @@ class WalletConnectMiddleware {
|
|||
return
|
||||
}
|
||||
|
||||
handleScanResponse(scanResponse = scanResponse, session = session, blockchain = blockchain)
|
||||
handleScanResponse(userWallet, session, blockchain)
|
||||
}
|
||||
|
||||
private fun getAvailableBlockchains(
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
walletState: WalletState,
|
||||
): List<Blockchain> {
|
||||
return walletState.currencies.filter {
|
||||
it.isBlockchain() &&
|
||||
!it.isCustomCurrency(derivationStyleProvider.getDerivationStyle()) && it.blockchain.isEvm()
|
||||
}.map { it.blockchain }
|
||||
private suspend fun getAvailableEvmBlockchains(userWalletId: UserWalletId): List<Blockchain> {
|
||||
val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
|
||||
|
||||
return currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId)
|
||||
.asSequence()
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.filterNot { it.isCustom }
|
||||
.mapNotNull { Blockchain.fromNetworkId(it.network.id.value) }
|
||||
.filter { it.isEvm() }
|
||||
.toList()
|
||||
}
|
||||
|
||||
private suspend fun prepareWalletManager(
|
||||
scanResponse: ScanResponse,
|
||||
walletState: WalletState,
|
||||
blockchain: Blockchain,
|
||||
session: WalletConnectSession,
|
||||
walletConnectManager: WalletConnectManager,
|
||||
) {
|
||||
val walletManager = getWalletManager(session.wallet, blockchain, walletState).guard {
|
||||
val walletManager = getWalletManager(session.wallet, blockchain).guard {
|
||||
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
|
|
@ -445,20 +446,25 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleScanResponse(scanResponse: ScanResponse, session: WalletConnectSession, blockchain: Blockchain) {
|
||||
private suspend fun handleScanResponse(
|
||||
userWallet: UserWallet,
|
||||
session: WalletConnectSession,
|
||||
blockchain: Blockchain,
|
||||
) {
|
||||
val scanResponse = userWallet.scanResponse
|
||||
|
||||
if (!scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
|
||||
return
|
||||
}
|
||||
val walletState = store.state.walletState
|
||||
val updatedSession = session.copy(wallet = session.wallet.copy(blockchain = blockchain))
|
||||
store.dispatch(
|
||||
WalletConnectAction.SetNewSessionData(
|
||||
NewWcSessionData(session = updatedSession, scanResponse = scanResponse, blockchain = blockchain),
|
||||
NewWcSessionData(updatedSession, scanResponse, blockchain),
|
||||
),
|
||||
)
|
||||
val blockchains = if (blockchain.isEvm()) {
|
||||
getAvailableBlockchains(scanResponse.derivationStyleProvider, walletState)
|
||||
getAvailableEvmBlockchains(userWallet.walletId)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
|
@ -469,11 +475,7 @@ class WalletConnectMiddleware {
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun getWalletManager(
|
||||
wallet: WalletForSession,
|
||||
blockchain: Blockchain,
|
||||
walletState: WalletState,
|
||||
): WalletManager? {
|
||||
private suspend fun getWalletManager(wallet: WalletForSession, blockchain: Blockchain): WalletManager? {
|
||||
val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
|
|
@ -483,25 +485,15 @@ class WalletConnectMiddleware {
|
|||
val derivation = blockchainToMake.derivationPath(
|
||||
style = userWallet.scanResponse.derivationStyleProvider.getDerivationStyle(),
|
||||
)?.rawPath
|
||||
val walletFeatureToggles = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletFeatureToggles)
|
||||
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
walletManagerFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = derivation,
|
||||
)
|
||||
} else {
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = derivation,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
walletState.getWalletManager(blockchainNetwork)
|
||||
}
|
||||
val walletManagerFacade = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
|
||||
return walletManagerFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = derivation,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isWalletConnectUri(uri: String): Boolean {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import com.tangem.tap.features.details.redux.AppSetting
|
|||
import com.tangem.tap.features.details.redux.AppSettingsState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
|
|
@ -109,13 +108,7 @@ internal class AppSettingsViewModel(
|
|||
}
|
||||
|
||||
private fun showAppCurrencySelector() {
|
||||
val action = if (detailsFeatureToggles.isRedesignedAppCurrencySelectorEnabled) {
|
||||
NavigationAction.NavigateTo(AppScreen.AppCurrencySelector)
|
||||
} else {
|
||||
WalletAction.AppCurrencyAction.ChooseAppCurrency
|
||||
}
|
||||
|
||||
store.dispatchOnMain(action)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.AppCurrencySelector))
|
||||
}
|
||||
|
||||
private fun showThemeModeSelector(selectedMode: AppThemeMode) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.annotation.StringRes
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.domain.userwallets.Artwork
|
||||
import com.tangem.tap.features.details.redux.AccessCodeRecoveryState
|
||||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
import com.tangem.tap.features.details.ui.securitymode.toTitleRes
|
||||
|
|
@ -17,7 +16,6 @@ internal data class CardSettingsScreenState(
|
|||
val accessCodeRecoveryState: AccessCodeRecoveryState? = null,
|
||||
val onScanCardClick: () -> Unit,
|
||||
val onElementClick: (CardInfo) -> Unit,
|
||||
val cardImage: Artwork? = null,
|
||||
)
|
||||
|
||||
internal sealed class CardInfo(
|
||||
|
|
|
|||
|
|
@ -12,11 +12,9 @@ import com.tangem.domain.common.getTwinCardIdForUser
|
|||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.details.redux.CardSettingsState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -37,15 +35,12 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
is Either.Left -> {
|
||||
Timber.e(selectedWalletEither.value.toString())
|
||||
}
|
||||
is Either.Right -> {
|
||||
store.dispatchOnMain(WalletAction.UpdateUserWalletArtwork(selectedWalletEither.value.walletId))
|
||||
}
|
||||
is Either.Right -> Unit
|
||||
}
|
||||
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState &&
|
||||
oldState.walletState == newState.walletState
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +62,6 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
onScanCardClick = {
|
||||
store.dispatch(DetailsAction.ScanCard)
|
||||
},
|
||||
cardImage = store.state.walletState.cardImage,
|
||||
)
|
||||
} else {
|
||||
val cardId = if (state.card.isTangemTwins) {
|
||||
|
|
@ -105,7 +99,6 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
onElementClick = {
|
||||
handleClickingItem(it)
|
||||
},
|
||||
cardImage = store.state.walletState.cardImage,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.details.ui.details
|
|||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
|
|
@ -12,6 +13,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||
|
|
@ -24,7 +26,6 @@ import com.tangem.tap.features.details.redux.DetailsState
|
|||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
|
@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.flowOn
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
// TODO: change to Android ViewModel [REDACTED_JIRA]
|
||||
internal class DetailsViewModel(
|
||||
|
|
@ -158,7 +160,15 @@ internal class DetailsViewModel(
|
|||
|
||||
private fun linkMoreCards() {
|
||||
Analytics.send(Settings.ButtonCreateBackup())
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.BackupWallet)
|
||||
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to backup wallet, no user wallet selected")
|
||||
return
|
||||
}
|
||||
val scanResponse = selectedUserWallet.scanResponse
|
||||
Analytics.addContext(scanResponse)
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
}
|
||||
|
||||
private fun scanAndSaveUserWallet() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class DisclaimerAction : Action {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ package com.tangem.tap.features.disclaimer.redux
|
|||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.DummyDisclaimer
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class DisclaimerState(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.transition.TransitionInflater
|
|||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
|
|
@ -17,7 +18,6 @@ import com.tangem.tap.features.addBackPressHandler
|
|||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentDisclaimerBinding
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
package com.tangem.tap.features.disclaimer.ui
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.webkit.*
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
|
||||
class DisclaimerWebViewClient : WebViewClient() {
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.features.home.redux
|
|||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.send.redux.states.ButtonState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import org.rekotlin.StateType
|
||||
import java.util.Locale
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
package com.tangem.tap.features.intentHandler.handlers
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
|
||||
import com.tangem.tap.store
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -15,16 +9,19 @@ import com.tangem.tap.store
|
|||
class BuyCurrencyIntentHandler : IntentHandler {
|
||||
|
||||
override fun handleIntent(intent: Intent?): Boolean {
|
||||
val data = intent?.data ?: return false
|
||||
val currency = store.state.walletState.selectedCurrency ?: return false
|
||||
// FIXME: It hasn't worked since the redesign
|
||||
// val data = intent?.data ?: return false
|
||||
// val currency = store.state.walletState.selectedCurrency ?: return false
|
||||
//
|
||||
// val successUri = Uri.parse(ExchangeUrlBuilder.SUCCESS_URL)
|
||||
// return if (data.host == successUri.host && data.authority == successUri.authority) {
|
||||
// val currencyType = AnalyticsParam.CurrencyType.Currency(currency)
|
||||
// Analytics.send(TokenScreenAnalyticsEvent.Bought(currencyType.value))
|
||||
// true
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
|
||||
val successUri = Uri.parse(ExchangeUrlBuilder.SUCCESS_URL)
|
||||
return if (data.host == successUri.host && data.authority == successUri.authority) {
|
||||
val currencyType = AnalyticsParam.CurrencyType.Currency(currency)
|
||||
Analytics.send(TokenScreenAnalyticsEvent.Bought(currencyType.value))
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchShare
|
||||
import com.tangem.tap.common.extensions.dispatchToastNotification
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.toQrCode
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AddressInfoBottomSheetDialog(
|
||||
private val stateDialog: AppDialog.AddressInfoDialog,
|
||||
context: Context,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
var binding: DialogOnboardingAddressInfoBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DialogOnboardingAddressInfoBinding
|
||||
.inflate(LayoutInflater.from(context))
|
||||
setContentView(binding!!.root)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
setOnCancelListener {
|
||||
store.dispatchDialogHide()
|
||||
binding = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
Analytics.send(Token.Receive.ScreenOpened())
|
||||
showData(data = stateDialog.addressData)
|
||||
}
|
||||
|
||||
private fun showData(data: WalletDataModel.AddressData) = with(binding!!) {
|
||||
pseudoToolbar.imvClose.setOnClickListener {
|
||||
dismissWithAnimation = true
|
||||
cancel()
|
||||
}
|
||||
imvQrCode.setImageBitmap(data.shareUrl.toQrCode())
|
||||
tvAddress.text = data.address
|
||||
btnFlCopyAddress.setOnClickListener {
|
||||
Analytics.send(Token.Receive.ButtonCopyAddress())
|
||||
context.copyToClipboard(data.address)
|
||||
store.dispatchToastNotification(R.string.copy_toast_msg)
|
||||
}
|
||||
btnFlShare.setOnClickListener {
|
||||
Analytics.send(Token.Receive.ButtonShareAddress())
|
||||
store.dispatchShare(data.shareUrl)
|
||||
}
|
||||
val blockchain = stateDialog.currency.blockchain
|
||||
tvReceiveMessage.text = tvReceiveMessage.getString(
|
||||
id = R.string.address_qr_code_message_format,
|
||||
blockchain.fullName,
|
||||
blockchain.currency,
|
||||
blockchain.fullName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,18 +5,18 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.CardVerifyAndGetInfo
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.isPositive
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.hasPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
|||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
|
|
@ -19,7 +20,6 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
|||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.scope
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.domain.userwallets.UserWalletIdBuilder
|
|||
import com.tangem.domain.wallets.legacy.isLockedSync
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
|
|
@ -25,7 +26,6 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
|||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.preferencesStorage
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.tangem.tap.common.extensions.dispatchWithMain
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -119,16 +118,8 @@ internal class SaveWalletMiddleware {
|
|||
)
|
||||
}
|
||||
|
||||
val savedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("User wallet is not saved")
|
||||
return@launch
|
||||
}
|
||||
store.dispatchWithMain(SaveWalletAction.Save.Success)
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true))
|
||||
store.dispatchWithMain(
|
||||
action = WalletAction.MultiWallet.CheckForBackupWarning(savedUserWallet.scanResponse.card),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ package com.tangem.tap.features.send.redux.reducers
|
|||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.send.redux.FeeAction
|
||||
import com.tangem.tap.features.send.redux.FeeActionUi
|
||||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.states.FeeState
|
||||
import com.tangem.tap.features.send.redux.states.FeeType
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -2,23 +2,12 @@ package com.tangem.tap.features.send.redux.reducers
|
|||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.tap.common.extensions.scaleToFiat
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt
|
||||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.states.AmountState
|
||||
import com.tangem.tap.features.send.redux.states.FeeState
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptCrypto
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptFiat
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptLayoutType
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptState
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptSymbols
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptTokenCrypto
|
||||
import com.tangem.tap.features.send.redux.states.ReceiptTokenFiat
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
import com.tangem.tap.features.wallet.redux.utils.CAN_BE_LOWER_SIGN
|
||||
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.tap.features.send.redux.states.*
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -143,9 +132,9 @@ class ReceiptReducer : SendInternalReducer {
|
|||
)
|
||||
} else {
|
||||
ReceiptTokenFiat(
|
||||
amountFiat = UNKNOWN_AMOUNT_SIGN,
|
||||
feeFiat = UNKNOWN_AMOUNT_SIGN,
|
||||
totalFiat = UNKNOWN_AMOUNT_SIGN,
|
||||
amountFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
|
||||
feeFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
|
||||
totalFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
|
||||
willSentToken = tokensToSend.stripZeroPlainString(),
|
||||
willSentFeeCoin = feeCoin.stripZeroPlainString(),
|
||||
symbols = symbols,
|
||||
|
|
@ -175,7 +164,7 @@ class ReceiptReducer : SendInternalReducer {
|
|||
ReceiptTokenCrypto(
|
||||
amountToken = tokensToSend.stripZeroPlainString(),
|
||||
feeCoin = feeCoin.stripZeroPlainString().addPrecisionSign(),
|
||||
totalFiat = UNKNOWN_AMOUNT_SIGN,
|
||||
totalFiat = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
|
||||
symbols = symbols,
|
||||
)
|
||||
}
|
||||
|
|
@ -216,7 +205,7 @@ class ReceiptReducer : SendInternalReducer {
|
|||
sendState.tokenConverter!!.toFiatWithPrecision(value).stripZeroPlainString()
|
||||
}
|
||||
else -> {
|
||||
UNKNOWN_AMOUNT_SIGN
|
||||
BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -225,4 +214,8 @@ class ReceiptReducer : SendInternalReducer {
|
|||
val result = if (feeState.feeIsApproximate) "$CAN_BE_LOWER_SIGN $this" else this
|
||||
return result.trim()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CAN_BE_LOWER_SIGN = "<"
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.features.send.redux.states
|
|||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ import com.tangem.tap.features.send.redux.AmountActionUi.*
|
|||
import com.tangem.tap.features.send.redux.FeeActionUi.*
|
||||
import com.tangem.tap.features.send.redux.states.FeeType
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
package com.tangem.tap.features.send.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
|
@ -13,14 +13,13 @@ import com.tangem.tap.common.analytics.events.MainScreen
|
|||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutWarningCardActionBinding
|
||||
import timber.log.Timber
|
||||
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
// TODO: Delete with SendFeatureToggles
|
||||
@Deprecated(message = "Used only in old send screen")
|
||||
class WarningMessagesAdapter : ListAdapter<WarningMessage, WarningMessageVH>(DiffUtilCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WarningMessageVH {
|
||||
|
|
@ -81,11 +80,9 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
|
|||
private fun setupControlButtons(warning: WarningMessage) = when (warning.type) {
|
||||
WarningMessage.Type.Permanent, WarningMessage.Type.TestCard -> {
|
||||
binding.groupControlsTemporary.hide()
|
||||
binding.groupControlsRating.hide()
|
||||
binding.btnClose.hide()
|
||||
}
|
||||
WarningMessage.Type.Temporary -> {
|
||||
binding.groupControlsRating.hide()
|
||||
binding.groupControlsTemporary.show()
|
||||
binding.btnClose.hide()
|
||||
|
||||
|
|
@ -115,7 +112,6 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
|
|||
binding.btnClose.setOnClickListener {
|
||||
Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Closed))
|
||||
store.dispatch(GlobalAction.HideWarningMessage(warning))
|
||||
store.dispatch(WalletAction.Warnings.AppRating.RemindLater)
|
||||
}
|
||||
// binding.btnCanBeBetter.setOnClickListener {
|
||||
// Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Disliked))
|
||||
|
|
@ -127,7 +123,6 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
|
|||
val activity = binding.root.context.getActivity() ?: return@setOnClickListener
|
||||
|
||||
Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Liked))
|
||||
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
|
||||
val reviewManager = ReviewManagerFactory.create(activity)
|
||||
val task = reviewManager.requestReviewFlow()
|
||||
task.addOnCompleteListener {
|
||||
|
|
@ -7,6 +7,8 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.core.text.bold
|
||||
import com.tangem.common.extensions.remove
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.getMessageString
|
||||
import com.tangem.tap.common.text.DecimalDigitsInputFilter
|
||||
|
|
@ -19,11 +21,8 @@ import com.tangem.tap.features.send.redux.states.*
|
|||
import com.tangem.tap.features.send.ui.FeeUiHelper
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.send.ui.SendViewModel
|
||||
import com.tangem.tap.features.send.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.features.send.ui.dialogs.*
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.utils.ROUGH_SIGN
|
||||
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
|
|
@ -316,7 +315,7 @@ internal class SendStateSubscriber(
|
|||
fun getString(id: Int, vararg formatStrings: String): String = mainLayout.getString(id, *formatStrings)
|
||||
|
||||
fun roughOrEmpty(value: String): String {
|
||||
return if (value == UNKNOWN_AMOUNT_SIGN) value else "$ROUGH_SIGN $value"
|
||||
return if (value == BigDecimalFormatter.EMPTY_BALANCE_SIGN) value else "$ROUGH_SIGN $value"
|
||||
}
|
||||
|
||||
when (feeProgressState) {
|
||||
|
|
@ -362,7 +361,7 @@ internal class SendStateSubscriber(
|
|||
llTotalContainer.tvTotalValue.update("${receipt.totalCrypto} ${receipt.symbols.crypto}")
|
||||
}
|
||||
|
||||
if (receipt.willSentFiat == UNKNOWN_AMOUNT_SIGN) {
|
||||
if (receipt.willSentFiat == BigDecimalFormatter.EMPTY_BALANCE_SIGN) {
|
||||
llTotalContainer.tvWillBeSentValue.hide()
|
||||
} else {
|
||||
llTotalContainer.tvWillBeSentValue.show()
|
||||
|
|
@ -414,4 +413,8 @@ internal class SendStateSubscriber(
|
|||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ROUGH_SIGN = "≈"
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.tokens.impl.presentation.viewmodels
|
|||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
|
|
@ -13,8 +12,6 @@ import com.tangem.domain.tokens.model.CryptoCurrency
|
|||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
import kotlin.properties.Delegates
|
||||
|
|
@ -39,14 +36,6 @@ internal class TokensListMigration(
|
|||
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
|
||||
|
||||
suspend fun getCurrentCryptoCurrencies(): TokensListCryptoCurrencies {
|
||||
return if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
getNewCryptoCurrencies()
|
||||
} else {
|
||||
getLegacyCryptoCurrencies()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNewCryptoCurrencies(): TokensListCryptoCurrencies {
|
||||
return when (val selectedWalletEither = getSelectedWalletSyncUseCase()) {
|
||||
is Either.Left -> {
|
||||
Timber.e(selectedWalletEither.value.toString())
|
||||
|
|
@ -90,41 +79,6 @@ internal class TokensListMigration(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getLegacyCryptoCurrencies(): TokensListCryptoCurrencies {
|
||||
val wallets = store.state.walletState.walletsDataFromStores
|
||||
val derivationStyle = store.state.globalState.scanResponse?.derivationStyleProvider?.getDerivationStyle()
|
||||
|
||||
return TokensListCryptoCurrencies(
|
||||
coins = wallets.toNonCustomBlockchains(derivationStyle),
|
||||
tokens = wallets.toNonCustomTokensWithBlockchains(derivationStyle),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<WalletDataModel>.toNonCustomBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
|
||||
return this
|
||||
.mapNotNull { walletDataModel ->
|
||||
if (walletDataModel.currency.isCustomCurrency(derivationStyle)) {
|
||||
null
|
||||
} else {
|
||||
(walletDataModel.currency as? Currency.Blockchain)?.blockchain
|
||||
}
|
||||
}
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun List<WalletDataModel>.toNonCustomTokensWithBlockchains(
|
||||
derivationStyle: DerivationStyle?,
|
||||
): List<TokenWithBlockchain> {
|
||||
return this
|
||||
.mapNotNull { walletDataModel ->
|
||||
if (walletDataModel.currency !is Currency.Token) return@mapNotNull null
|
||||
if (walletDataModel.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
|
||||
|
||||
TokenWithBlockchain(walletDataModel.currency.token, walletDataModel.currency.blockchain)
|
||||
}
|
||||
.distinct()
|
||||
}
|
||||
|
||||
fun onSaveButtonClick(
|
||||
currentTokensList: List<TokenWithBlockchain>,
|
||||
currentBlockchainList: List<Blockchain>,
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.data
|
||||
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
import com.tangem.tap.features.wallet.domain.WalletRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Implementation of repository for Wallet feature
|
||||
*
|
||||
* @property tangemTechApi API for server requests
|
||||
* @property dispatchers coroutine dispatcher provider
|
||||
*/
|
||||
class WalletRepositoryImpl(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : WalletRepository {
|
||||
|
||||
override suspend fun getCurrencyList(): CurrenciesResponse = withContext(dispatchers.io) {
|
||||
tangemTechApi.getCurrencyList().getOrThrow()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.domain
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
|
||||
/** Repository for Wallet feature */
|
||||
interface WalletRepository {
|
||||
|
||||
/** Get list of currency */
|
||||
suspend fun getCurrencyList(): CurrenciesResponse
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.models
|
||||
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
|
||||
sealed class WalletWarning(val showingPosition: Int) {
|
||||
|
||||
data class ExistentialDeposit(
|
||||
val currencyName: String,
|
||||
val edStringValueWithSymbol: String,
|
||||
) : WalletWarning(1)
|
||||
|
||||
data class TransactionInProgress(val currencyName: String) : WalletWarning(showingPosition = 10)
|
||||
|
||||
data class BalanceNotEnoughForFee(
|
||||
val currencyName: String,
|
||||
val blockchainFullName: String,
|
||||
val blockchainSymbol: String,
|
||||
) : WalletWarning(showingPosition = 30)
|
||||
|
||||
data class Rent(val walletRent: WalletStoreModel.WalletRent) : WalletWarning(showingPosition = 40)
|
||||
}
|
||||
|
||||
data class WalletWarningDescription(val title: String, val message: String)
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class WalletAction : Action {
|
||||
|
||||
object PopBackToInitialScreen : WalletAction()
|
||||
|
||||
data class UpdateCanSaveUserWallets(val canSaveUserWallets: Boolean) : WalletAction()
|
||||
|
||||
object LoadData : WalletAction() {
|
||||
object Refresh : WalletAction()
|
||||
object Success : WalletAction()
|
||||
data class Failure(val error: TapError?) : WalletAction()
|
||||
}
|
||||
|
||||
sealed class MultiWallet : WalletAction() {
|
||||
|
||||
data class SelectWallet(val currency: Currency?) : MultiWallet()
|
||||
|
||||
data class TryToRemoveWallet(val currency: Currency) : MultiWallet()
|
||||
data class RemoveWallet(val currency: Currency) : MultiWallet()
|
||||
|
||||
object BackupWallet : MultiWallet()
|
||||
data class AddMissingDerivations(val blockchains: List<BlockchainNetwork>) : MultiWallet()
|
||||
object ScanToGetDerivations : MultiWallet()
|
||||
|
||||
/**
|
||||
* Display warning if card has no backup
|
||||
*
|
||||
* @param card card to check status
|
||||
* */
|
||||
data class CheckForBackupWarning(val card: CardDTO) : MultiWallet()
|
||||
}
|
||||
|
||||
sealed class Warnings : WalletAction() {
|
||||
object CheckHashesCount : Warnings() {
|
||||
|
||||
/**
|
||||
* Start online verification of signed hashes for single currency wallets if the warning not displayed
|
||||
* */
|
||||
object VerifyOnlineIfNeeded : Warnings()
|
||||
object SaveCardId : Warnings()
|
||||
}
|
||||
|
||||
object CheckIfNeeded : Warnings()
|
||||
object Update : Warnings()
|
||||
data class Set(val warningList: List<WarningMessage>) : Warnings()
|
||||
|
||||
object AppRating : Warnings() {
|
||||
object SetNeverToShow : Warnings()
|
||||
object RemindLater : Warnings()
|
||||
}
|
||||
|
||||
class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings()
|
||||
}
|
||||
|
||||
data class Scan(
|
||||
val onScanSuccessEvent: AnalyticsEvent?,
|
||||
val scope: CoroutineScope,
|
||||
) : WalletAction()
|
||||
|
||||
data class Send(val amount: Amount? = null) : WalletAction()
|
||||
|
||||
data class CopyAddress(val address: String, val context: Context) : WalletAction() {
|
||||
object Success : WalletAction(), NotificationAction {
|
||||
override val messageResource = R.string.wallet_notification_address_copied
|
||||
}
|
||||
}
|
||||
|
||||
data class ShareAddress(val address: String, val context: Context) : WalletAction()
|
||||
|
||||
sealed class DialogAction : WalletAction() {
|
||||
data class QrCode(
|
||||
val currency: Currency,
|
||||
val selectedAddress: WalletDataModel.AddressData,
|
||||
) : DialogAction()
|
||||
|
||||
object SignedHashesMultiWalletDialog : DialogAction()
|
||||
data class ChooseTradeActionDialog(
|
||||
val buyAllowed: Boolean,
|
||||
val sellAllowed: Boolean,
|
||||
val swapAllowed: Boolean,
|
||||
) : DialogAction()
|
||||
|
||||
data class ChooseCurrency(val amounts: List<Amount>) : DialogAction()
|
||||
data class RussianCardholdersWarningDialog(
|
||||
val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null,
|
||||
) : DialogAction()
|
||||
|
||||
object Hide : DialogAction()
|
||||
}
|
||||
|
||||
data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction()
|
||||
|
||||
object CreateWallet : WalletAction()
|
||||
data class ChangeWallet(val scope: LifecycleCoroutineScope) : WalletAction()
|
||||
object ShowSaveWalletIfNeeded : WalletAction()
|
||||
|
||||
sealed class TradeCryptoAction : WalletAction() {
|
||||
object Sell : TradeCryptoAction()
|
||||
|
||||
data class Buy(val checkUserLocation: Boolean = true) : TradeCryptoAction()
|
||||
|
||||
object Swap : TradeCryptoAction()
|
||||
}
|
||||
|
||||
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
|
||||
|
||||
sealed class AppCurrencyAction : WalletAction() {
|
||||
object ChooseAppCurrency : AppCurrencyAction()
|
||||
data class SelectAppCurrency(val fiatCurrency: FiatCurrency) : AppCurrencyAction()
|
||||
}
|
||||
|
||||
data class UserWalletChanged(val userWallet: UserWallet) : WalletAction()
|
||||
data class WalletStoresChanged(val walletStores: List<WalletStoreModel>) : WalletAction()
|
||||
|
||||
data class TotalFiatBalanceChanged(val balance: TotalFiatBalance) : WalletAction()
|
||||
|
||||
data class UpdateUserWalletArtwork(val walletId: UserWalletId) : WalletAction()
|
||||
|
||||
data class SetArtworkUrl(val userWalletId: UserWalletId, val url: String) : WalletAction()
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.userwallets.Artwork
|
||||
import com.tangem.tap.common.entities.Button
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.common.toggleWidget.WidgetState
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.StateType
|
||||
import java.math.BigDecimal
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
data class WalletState(
|
||||
val state: ProgressState = ProgressState.Done,
|
||||
val error: ErrorType? = null,
|
||||
val cardImage: Artwork? = null,
|
||||
val mainWarningsList: List<WarningMessage> = mutableListOf(),
|
||||
val walletsStores: List<WalletStoreModel> = listOf(),
|
||||
val isMultiwalletAllowed: Boolean = false,
|
||||
val cardCurrency: CryptoCurrencyName? = null,
|
||||
val selectedCurrency: Currency? = null,
|
||||
val isTestnet: Boolean = false,
|
||||
val totalBalance: TotalFiatBalance? = null,
|
||||
val showBackupWarning: Boolean = false,
|
||||
val missingDerivations: List<BlockchainNetwork> = emptyList(),
|
||||
val loadingUserTokens: Boolean = false,
|
||||
val walletCardsCount: Int? = null,
|
||||
val canSaveUserWallets: Boolean = false,
|
||||
) : StateType {
|
||||
|
||||
val walletsDataFromStores: List<WalletDataModel>
|
||||
get() = walletsStores.flatMap { it.walletsData }
|
||||
|
||||
val selectedWalletData: WalletDataModel?
|
||||
get() = walletsDataFromStores.firstOrNull { it.currency == selectedCurrency }
|
||||
|
||||
// if you do not delegate - the application crashes on startup,
|
||||
// because twinCardsState has not been created yet
|
||||
val twinCardsState: TwinCardsState by ReadOnlyProperty<Any, TwinCardsState> { _, _ ->
|
||||
store.state.twinCardsState
|
||||
}
|
||||
|
||||
val isTangemTwins: Boolean
|
||||
get() = store.state.globalState.scanResponse?.cardTypesResolver?.isTangemTwins() == true
|
||||
|
||||
val isExchangeServiceFeatureOn: Boolean
|
||||
get() = store.state.globalState.exchangeManager.featureIsSwitchedOn()
|
||||
|
||||
val blockchains: List<Blockchain>
|
||||
get() = walletsStores.mapNotNull { it.walletManager?.wallet?.blockchain }
|
||||
|
||||
val currencies: List<Currency>
|
||||
get() = walletsStores.flatMap { it.walletsData }.map { it.currency }
|
||||
|
||||
val walletManagers: List<WalletManager>
|
||||
get() = walletsStores.mapNotNull { it.walletManager }
|
||||
|
||||
private val primaryWalletStore: WalletStoreModel?
|
||||
get() = if (isMultiwalletAllowed || walletsStores.isEmpty() || walletsStores.size > 1) {
|
||||
null
|
||||
} else {
|
||||
walletsStores[0]
|
||||
}
|
||||
|
||||
val primaryWalletManager: WalletManager?
|
||||
get() = primaryWalletStore?.walletManager
|
||||
|
||||
val primaryWalletData: WalletDataModel?
|
||||
get() = primaryWalletStore?.blockchainWalletData
|
||||
|
||||
val primaryTokenData: WalletDataModel?
|
||||
get() = primaryWalletStore?.walletsData
|
||||
?.firstOrNull { it.currency !is Currency.Blockchain }
|
||||
|
||||
fun getWalletManager(currency: Currency?): WalletManager? {
|
||||
if (currency?.blockchain == null) return null
|
||||
return getWalletStore(currency)?.walletManager
|
||||
}
|
||||
|
||||
fun getWalletManager(blockchain: BlockchainNetwork): WalletManager? {
|
||||
return walletsStores.firstOrNull {
|
||||
it.blockchain == blockchain.blockchain &&
|
||||
it.derivationPath?.rawPath == blockchain.derivationPath
|
||||
}?.walletManager
|
||||
}
|
||||
|
||||
fun getWalletStore(currency: Currency?): WalletStoreModel? {
|
||||
if (currency == null) return null
|
||||
return walletsStores.firstOrNull {
|
||||
it.blockchain == currency.blockchain &&
|
||||
it.derivationPath?.rawPath == currency.derivationPath
|
||||
}
|
||||
}
|
||||
|
||||
fun getBlockchainAmount(currency: Currency): BigDecimal =
|
||||
getWalletManager(currency)?.wallet?.amounts?.get(AmountType.Coin)?.value ?: BigDecimal.ZERO
|
||||
}
|
||||
|
||||
enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error }
|
||||
|
||||
enum class ErrorType {
|
||||
NoInternetConnection,
|
||||
UnknownBlockchain,
|
||||
}
|
||||
|
||||
sealed class WalletMainButton(enabled: Boolean) : Button(enabled) {
|
||||
class SendButton(enabled: Boolean) : WalletMainButton(enabled)
|
||||
}
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.data.source.preferences.model.DataSourceCurrency
|
||||
import com.tangem.data.source.preferences.model.DataSourceFiatCurrency
|
||||
import com.tangem.data.source.preferences.storage.FiatCurrenciesPrefStorage
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.wallet.domain.WalletRepository
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class AppCurrencyMiddleware(
|
||||
private val walletRepository: WalletRepository,
|
||||
private val tapWalletManager: TapWalletManager,
|
||||
private val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage,
|
||||
private val featureToggles: WalletFeatureToggles,
|
||||
private val appCurrencyRepository: AppCurrencyRepository,
|
||||
private val appCurrencyProvider: () -> FiatCurrency,
|
||||
) {
|
||||
private val showSelectorJobHolder = JobHolder()
|
||||
|
||||
fun handle(action: WalletAction.AppCurrencyAction) {
|
||||
when (action) {
|
||||
is WalletAction.AppCurrencyAction.ChooseAppCurrency -> showSelector()
|
||||
is WalletAction.AppCurrencyAction.SelectAppCurrency -> selectCurrency(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSelector() {
|
||||
if (featureToggles.isRedesignedScreenEnabled) {
|
||||
showSelectorNew()
|
||||
} else {
|
||||
showSelectorLegacy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) {
|
||||
if (featureToggles.isRedesignedScreenEnabled) {
|
||||
selectCurrencyNew(action.fiatCurrency)
|
||||
} else {
|
||||
selectCurrencyLegacy(action.fiatCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSelectorNew() {
|
||||
scope.launch {
|
||||
val currencies = appCurrencyRepository.getAvailableAppCurrencies()
|
||||
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.CurrencySelectionDialog(
|
||||
currenciesList = currencies.map { appCurrency ->
|
||||
FiatCurrency(
|
||||
code = appCurrency.code,
|
||||
name = appCurrency.name,
|
||||
symbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
currentAppCurrency = appCurrencyProvider.invoke(),
|
||||
),
|
||||
)
|
||||
}.saveIn(showSelectorJobHolder)
|
||||
}
|
||||
|
||||
private fun showSelectorLegacy() {
|
||||
val storedFiatCurrencies = fiatCurrenciesPrefStorage.restore()
|
||||
if (storedFiatCurrencies.isNotEmpty()) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.CurrencySelectionDialog(
|
||||
currenciesList = storedFiatCurrencies.mapToUiModel(),
|
||||
currentAppCurrency = appCurrencyProvider.invoke(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
runCatching { walletRepository.getCurrencyList() }
|
||||
.onSuccess { response ->
|
||||
val currenciesList = response.currencies
|
||||
.map { with(it) { DataSourceCurrency(id, code, name, rateBTC, unit, type) } }
|
||||
|
||||
if (currenciesList.isNotEmpty() && currenciesList.toSet() != storedFiatCurrencies.toSet()) {
|
||||
fiatCurrenciesPrefStorage.save(currenciesList)
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.CurrencySelectionDialog(
|
||||
currenciesList = currenciesList.mapToUiModel(),
|
||||
currentAppCurrency = appCurrencyProvider.invoke(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectCurrencyNew(fiatCurrency: FiatCurrency) {
|
||||
Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(fiatCurrency)))
|
||||
|
||||
scope.launch {
|
||||
appCurrencyRepository.changeAppCurrency(fiatCurrency.code)
|
||||
|
||||
store.dispatchWithMain(GlobalAction.ChangeAppCurrency(fiatCurrency))
|
||||
store.dispatchWithMain(DetailsAction.ChangeAppCurrency(fiatCurrency))
|
||||
store.dispatchWithMain(WalletSelectorAction.ChangeAppCurrency(fiatCurrency))
|
||||
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to select currency, no user wallet selected")
|
||||
return@launch
|
||||
}
|
||||
|
||||
tapWalletManager.loadData(selectedUserWallet, refresh = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectCurrencyLegacy(fiatCurrency: FiatCurrency) {
|
||||
Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(fiatCurrency)))
|
||||
fiatCurrenciesPrefStorage.saveAppCurrency(
|
||||
with(fiatCurrency) { DataSourceFiatCurrency(code, name, symbol) },
|
||||
)
|
||||
store.dispatch(GlobalAction.ChangeAppCurrency(fiatCurrency))
|
||||
store.dispatch(DetailsAction.ChangeAppCurrency(fiatCurrency))
|
||||
store.dispatch(WalletSelectorAction.ChangeAppCurrency(fiatCurrency))
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to select currency, no user wallet selected")
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
tapWalletManager.loadData(selectedUserWallet, refresh = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<DataSourceCurrency>.mapToUiModel(): List<FiatCurrency> {
|
||||
return this.map {
|
||||
FiatCurrency(
|
||||
code = it.code,
|
||||
name = it.name,
|
||||
symbol = it.unit,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class MultiWalletMiddleware {
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun handle(action: WalletAction.MultiWallet, walletState: WalletState?) {
|
||||
when (action) {
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
if (action.currency != null) {
|
||||
store.dispatch(NavigationAction.NavigateTo(screen = AppScreen.WalletDetails))
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.TryToRemoveWallet -> {
|
||||
val currency = action.currency
|
||||
val walletManager = walletState?.getWalletManager(currency).guard {
|
||||
store.dispatchErrorNotification(TapError.UnsupportedState("walletManager is NULL"))
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
return
|
||||
}
|
||||
|
||||
if (currency.isBlockchain() && walletManager.cardTokens.isNotEmpty()) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.TokensAreLinkedDialog(
|
||||
currencyTitle = currency.currencyName,
|
||||
currencySymbol = currency.currencySymbol,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.RemoveWalletDialog(
|
||||
currencyTitle = currency.currencyName,
|
||||
onOk = {
|
||||
Analytics.send(ButtonRemoveToken(AnalyticsParam.CurrencyType.Currency(currency)))
|
||||
store.dispatch(WalletAction.MultiWallet.RemoveWallet(currency))
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.RemoveWallet -> {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to remove wallet, no user wallet selected")
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
walletCurrenciesManager.removeCurrency(
|
||||
userWallet = selectedUserWallet,
|
||||
currencyToRemove = action.currency,
|
||||
)
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.BackupWallet -> {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to backup wallet, no user wallet selected")
|
||||
return
|
||||
}
|
||||
val scanResponse = selectedUserWallet.scanResponse
|
||||
Analytics.addContext(scanResponse)
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
}
|
||||
is WalletAction.MultiWallet.AddMissingDerivations -> {
|
||||
store.state.globalState.topUpController?.addMissingDerivations(action.blockchains)
|
||||
}
|
||||
is WalletAction.MultiWallet.ScanToGetDerivations -> {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to scan to get derivations, no user wallet selected")
|
||||
return
|
||||
}
|
||||
store.state.globalState.topUpController?.scanToGetDerivations()
|
||||
scanAndUpdateCard(selectedUserWallet)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanAndUpdateCard(selectedUserWallet: UserWallet) = scope.launch(Dispatchers.Default) {
|
||||
store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor)
|
||||
.scan(cardId = selectedUserWallet.cardId, allowsRequestAccessCodeFromRepository = true)
|
||||
.flatMap { scanResponse ->
|
||||
userWalletsListManager.update(
|
||||
userWalletId = selectedUserWallet.walletId,
|
||||
update = { userWallet ->
|
||||
userWallet.copy(
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
.doOnSuccess { updatedUserWallet ->
|
||||
store.dispatchWithMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList()))
|
||||
store.dispatchWithMain(GlobalAction.SaveScanResponse(updatedUserWallet.scanResponse))
|
||||
store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,368 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class TradeCryptoMiddleware {
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
fun handle(state: () -> AppState?, action: TradeCryptoAction) {
|
||||
if (DemoHelper.tryHandle(state, action)) return
|
||||
|
||||
when (action) {
|
||||
is TradeCryptoAction.Buy -> proceedBuyAction(state, action)
|
||||
is TradeCryptoAction.Sell -> proceedSellAction()
|
||||
is TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action)
|
||||
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
||||
is TradeCryptoAction.Swap -> {
|
||||
// todo remove old flow
|
||||
}
|
||||
is TradeCryptoAction.New.Buy -> proceedNewBuyAction(state, action)
|
||||
is TradeCryptoAction.New.Sell -> proceedNewSellAction(action)
|
||||
is TradeCryptoAction.New.Swap -> openSwap(
|
||||
currency = action.cryptoCurrency,
|
||||
)
|
||||
is TradeCryptoAction.New.SendToken -> handleNewSendToken(action = action)
|
||||
is TradeCryptoAction.New.SendCoin -> handleNewSendCoin(action = action)
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use proceedNewBuyAction instead")
|
||||
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
val currency = chooseAppropriateCurrency(store.state.walletState) ?: return
|
||||
|
||||
Analytics.send(Token.ButtonBuy(AnalyticsParam.CurrencyType.Currency(currency)))
|
||||
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
|
||||
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog())
|
||||
return
|
||||
}
|
||||
|
||||
val card = store.state.globalState.scanResponse?.card ?: return
|
||||
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
|
||||
if (addresses.isEmpty()) return
|
||||
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
val appCurrency = store.state.globalState.appCurrency
|
||||
|
||||
if (currency is Currency.Token && currency.blockchain.isTestnet()) {
|
||||
val walletManager = store.state.walletState.getWalletManager(currency)
|
||||
if (walletManager !is EthereumWalletManager) {
|
||||
store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum")
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
buyErc20TestnetTokens(
|
||||
card = card,
|
||||
walletManager = walletManager,
|
||||
destinationAddress = currency.token.contractAddress,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Buy,
|
||||
blockchain = currency.blockchain,
|
||||
cryptoCurrencyName = currency.currencySymbol,
|
||||
fiatCurrencyName = appCurrency.code,
|
||||
walletAddress = addresses[0].address,
|
||||
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
|
||||
)?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Topup.ScreenOpened())
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedNewBuyAction(state: () -> AppState?, action: TradeCryptoAction.New.Buy) {
|
||||
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
|
||||
?.defaultAddress
|
||||
?.let(NetworkAddress.Address::value)
|
||||
?: return
|
||||
|
||||
val status = action.cryptoCurrencyStatus
|
||||
val currency = status.currency
|
||||
val blockchain = Blockchain.fromId(currency.network.id.value)
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
val topUrl = exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Buy,
|
||||
blockchain = blockchain,
|
||||
cryptoCurrencyName = currency.symbol,
|
||||
fiatCurrencyName = action.appCurrencyCode,
|
||||
walletAddress = networkAddress,
|
||||
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
|
||||
)
|
||||
|
||||
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
|
||||
val dialogData = topUrl?.let {
|
||||
WalletDialog.RussianCardholdersWarningDialog.Data(
|
||||
topUpUrl = it,
|
||||
)
|
||||
}
|
||||
store.dispatchOnMain(
|
||||
WalletAction.DialogAction.RussianCardholdersWarningDialog(
|
||||
dialogData = dialogData,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (currency is CryptoCurrency.Token && currency.network.isTestnet) {
|
||||
scope.launch {
|
||||
val walletManager = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
.getOrCreateWalletManager(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = currency.network.derivationPath.value,
|
||||
)
|
||||
|
||||
if (walletManager !is EthereumWalletManager) {
|
||||
store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum")
|
||||
return@launch
|
||||
}
|
||||
|
||||
buyErc20TestnetTokens(
|
||||
card = action.userWallet.scanResponse.card,
|
||||
walletManager = walletManager,
|
||||
destinationAddress = currency.contractAddress,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
topUrl?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Topup.ScreenOpened())
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedSellAction() {
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
val currency = chooseAppropriateCurrency(store.state.walletState) ?: return
|
||||
|
||||
val appCurrency = store.state.globalState.appCurrency
|
||||
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
|
||||
if (addresses.isEmpty()) return
|
||||
|
||||
Analytics.send(Token.ButtonSell(AnalyticsParam.CurrencyType.Currency(currency)))
|
||||
|
||||
store.state.globalState.exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Sell,
|
||||
blockchain = currency.blockchain,
|
||||
cryptoCurrencyName = currency.currencySymbol,
|
||||
fiatCurrencyName = appCurrency.code,
|
||||
walletAddress = addresses[0].address,
|
||||
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
|
||||
)?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Withdraw.ScreenOpened())
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedNewSellAction(action: TradeCryptoAction.New.Sell) {
|
||||
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
|
||||
?.defaultAddress
|
||||
?.let(NetworkAddress.Address::value)
|
||||
?: return
|
||||
val currency = action.cryptoCurrencyStatus.currency
|
||||
|
||||
store.state.globalState.exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Sell,
|
||||
blockchain = Blockchain.fromId(currency.network.id.value),
|
||||
cryptoCurrencyName = currency.symbol,
|
||||
fiatCurrencyName = action.appCurrencyCode,
|
||||
walletAddress = networkAddress,
|
||||
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
|
||||
)?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Withdraw.ScreenOpened())
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseAppropriateCurrency(walletState: WalletState): Currency? {
|
||||
return if (walletState.primaryTokenData == null) {
|
||||
walletState.selectedWalletData?.currency
|
||||
} else {
|
||||
walletState.primaryTokenData?.currency as? Currency.Token
|
||||
}.guard {
|
||||
store.dispatchDebugErrorNotification("Can't select an appropriate currency for a Trade action")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun preconfigureAndOpenSendScreen(action: TradeCryptoAction.SendCrypto) {
|
||||
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
|
||||
|
||||
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency)))
|
||||
val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency).guard {
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException("WalletManager is null"))
|
||||
return
|
||||
}
|
||||
|
||||
store.dispatchOnMain(
|
||||
PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
|
||||
coinRate = selectedWalletData.fiatRate,
|
||||
),
|
||||
)
|
||||
store.dispatchOnMain(
|
||||
SendAction.SendSpecificTransaction(
|
||||
sendAmount = action.amount,
|
||||
destinationAddress = action.destinationAddress,
|
||||
transactionId = action.transactionId,
|
||||
),
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
}
|
||||
|
||||
private fun openReceiptUrl(transactionId: String) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
store.state.globalState.exchangeManager.getSellCryptoReceiptUrl(
|
||||
action = CurrencyExchangeManager.Action.Sell,
|
||||
transactionId = transactionId,
|
||||
)?.let { store.dispatchOpenUrl(it) }
|
||||
}
|
||||
|
||||
private fun openSwap(currency: CryptoCurrency) {
|
||||
val bundle = bundleOf(
|
||||
SwapFragment.CURRENCY_BUNDLE_KEY to currency,
|
||||
)
|
||||
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle))
|
||||
}
|
||||
|
||||
private fun handleNewSendToken(action: TradeCryptoAction.New.SendToken) {
|
||||
val currency = action.tokenCurrency
|
||||
val blockchain = Blockchain.fromId(currency.network.id.value)
|
||||
|
||||
scope.launch {
|
||||
val walletManager = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
.getOrCreateWalletManager(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = currency.network.derivationPath.value,
|
||||
)
|
||||
|
||||
if (walletManager == null) {
|
||||
val error = TapError.UnsupportedState(stateError = "WalletManager is null")
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
|
||||
store.dispatchErrorNotification(error)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val sendableAmount = walletManager.wallet.amounts.values.firstOrNull {
|
||||
val amountType = it.type
|
||||
amountType is AmountType.Token && amountType.token.contractAddress == currency.contractAddress
|
||||
}
|
||||
|
||||
store.dispatchOnMain(
|
||||
action = PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
|
||||
coinRate = action.coinFiatRate,
|
||||
tokenAmount = sendableAmount,
|
||||
tokenRate = action.tokenFiatRate,
|
||||
),
|
||||
)
|
||||
|
||||
val bundle = bundleOf(
|
||||
SendRouter.CRYPTO_CURRENCY_KEY to currency,
|
||||
SendRouter.USER_WALLET_ID_KEY to action.userWallet.walletId.stringValue,
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewSendCoin(action: TradeCryptoAction.New.SendCoin) {
|
||||
val cryptoStatus = action.coinStatus
|
||||
val currency = cryptoStatus.currency
|
||||
val blockchain = Blockchain.fromId(currency.network.id.value)
|
||||
|
||||
scope.launch {
|
||||
val walletManager = store.state.daggerGraphState
|
||||
.get(DaggerGraphState::walletManagersFacade)
|
||||
.getOrCreateWalletManager(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = currency.network.derivationPath.value,
|
||||
)
|
||||
|
||||
if (walletManager == null) {
|
||||
val error = TapError.UnsupportedState(stateError = "WalletManager is null")
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
|
||||
store.dispatchErrorNotification(error)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val sendableAmounts = walletManager.wallet.amounts.values.filter { it.type == AmountType.Coin }
|
||||
when (currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val amountToSend = sendableAmounts.find { it.currencySymbol == currency.symbol }
|
||||
|
||||
if (amountToSend == null) {
|
||||
val error = TapError.UnsupportedState(stateError = "Amount to send is null")
|
||||
FirebaseCrashlytics.getInstance()
|
||||
.recordException(IllegalStateException(error.stateError))
|
||||
store.dispatchErrorNotification(error)
|
||||
return@launch
|
||||
}
|
||||
|
||||
store.dispatchOnMain(
|
||||
action = PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = amountToSend,
|
||||
coinRate = cryptoStatus.value.fiatRate,
|
||||
),
|
||||
)
|
||||
}
|
||||
is CryptoCurrency.Token -> error("Action.tokenStatus.currency is Token")
|
||||
}
|
||||
|
||||
val bundle = bundleOf(
|
||||
SendRouter.CRYPTO_CURRENCY_KEY to currency,
|
||||
SendRouter.USER_WALLET_ID_KEY to action.userWallet.walletId.stringValue,
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
|
||||
class WalletDialogsMiddleware {
|
||||
fun handle(action: WalletAction.DialogAction) {
|
||||
when (action) {
|
||||
is WalletAction.DialogAction.SignedHashesMultiWalletDialog -> {
|
||||
store.dispatchDialogShow(WalletDialog.SignedHashesMultiWalletDialog)
|
||||
}
|
||||
is WalletAction.DialogAction.ChooseTradeActionDialog -> {
|
||||
store.state.walletState.selectedWalletData?.let {
|
||||
Analytics.send(Token.ButtonExchange(AnalyticsParam.CurrencyType.Currency(it.currency)))
|
||||
}
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.ChooseTradeActionDialog(
|
||||
buyAllowed = action.buyAllowed,
|
||||
sellAllowed = action.sellAllowed,
|
||||
swapAllowed = action.swapAllowed,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletAction.DialogAction.QrCode -> {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.AddressInfoDialog(
|
||||
currency = action.currency,
|
||||
addressData = action.selectedAddress,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletAction.DialogAction.ChooseCurrency -> {
|
||||
if (action.amounts.isEmpty()) return
|
||||
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.SelectAmountToSendDialog(
|
||||
amounts = action.amounts,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletAction.DialogAction.RussianCardholdersWarningDialog -> {
|
||||
store.dispatchDialogShow(WalletDialog.RussianCardholdersWarningDialog(action.dialogData))
|
||||
}
|
||||
is WalletAction.DialogAction.Hide -> {
|
||||
store.dispatchDialogHide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,412 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.userwallets.GetCardImageUseCase
|
||||
import com.tangem.domain.wallets.legacy.lockIfLockable
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.getSendableAmounts
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.ifActive
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class WalletMiddleware {
|
||||
private val tradeCryptoMiddleware = TradeCryptoMiddleware()
|
||||
private val warningsMiddleware = WarningsMiddleware()
|
||||
private val multiWalletMiddleware = MultiWalletMiddleware()
|
||||
private val walletDialogMiddleware = WalletDialogsMiddleware()
|
||||
private val appCurrencyMiddleware by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
AppCurrencyMiddleware(
|
||||
// TODO("After adding DI") get dependencies by DI
|
||||
walletRepository = store.state.featureRepositoryProvider.walletRepository,
|
||||
tapWalletManager = store.state.globalState.tapWalletManager,
|
||||
fiatCurrenciesPrefStorage = preferencesStorage.fiatCurrenciesPrefStorage,
|
||||
appCurrencyRepository = store.state.daggerGraphState.get(DaggerGraphState::appCurrencyRepository),
|
||||
featureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles),
|
||||
appCurrencyProvider = { store.state.globalState.appCurrency },
|
||||
)
|
||||
}
|
||||
|
||||
private val networkConnectionManager: NetworkConnectionManager
|
||||
get() = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||
|
||||
private var updateWalletStoresJob: Job? = null
|
||||
set(value) {
|
||||
field?.cancel()
|
||||
field = value
|
||||
}
|
||||
|
||||
val walletMiddleware: Middleware<AppState> = { _, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleAction(state, action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleAction(state: () -> AppState?, action: Action) {
|
||||
if (DemoHelper.tryHandle(state, action)) return
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val walletState = store.state.walletState
|
||||
|
||||
when (action) {
|
||||
is TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action)
|
||||
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
|
||||
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState)
|
||||
is WalletAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action)
|
||||
is WalletAction.DialogAction -> walletDialogMiddleware.handle(action)
|
||||
is WalletAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
when (val result = tangemSdkManager.createWallet(globalState.scanResponse?.card?.cardId)) {
|
||||
is CompletionResult.Success -> {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to create wallet, no user wallet selected")
|
||||
return@launch
|
||||
}
|
||||
val updatedScanResponse = selectedUserWallet.scanResponse.copy(
|
||||
card = result.data,
|
||||
)
|
||||
store.dispatchWithMain(GlobalAction.SaveScanResponse(updatedScanResponse))
|
||||
userWalletsListManager.update(selectedUserWallet.walletId) { userWallet ->
|
||||
userWallet.copy(scanResponse = updatedScanResponse)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.Scan -> {
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
action.scope.launch {
|
||||
delay(timeMillis = 700)
|
||||
store.dispatchOnMain(HomeAction.ReadCard(action.onScanSuccessEvent, action.scope))
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadData,
|
||||
is WalletAction.LoadData.Refresh,
|
||||
-> {
|
||||
val selectedWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to load/refresh wallets data, no user wallet selected")
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
globalState.tapWalletManager.loadData(
|
||||
userWallet = selectedWallet,
|
||||
refresh = action is WalletAction.LoadData.Refresh,
|
||||
)
|
||||
}
|
||||
|
||||
store.dispatchOnMain(WalletAction.UpdateUserWalletArtwork(selectedWallet.walletId))
|
||||
}
|
||||
is WalletAction.CopyAddress -> {
|
||||
Analytics.send(Token.Receive.ButtonCopyAddress())
|
||||
action.context.copyToClipboard(action.address)
|
||||
store.dispatch(WalletAction.CopyAddress.Success)
|
||||
}
|
||||
is WalletAction.ShareAddress -> {
|
||||
Analytics.send(Token.Receive.ButtonShareAddress())
|
||||
action.context.shareText(action.address)
|
||||
}
|
||||
is WalletAction.ExploreAddress -> {
|
||||
Analytics.send(Token.ButtonExplore())
|
||||
store.dispatchOpenUrl(action.exploreUrl)
|
||||
}
|
||||
is WalletAction.Send -> {
|
||||
val walletStore = walletState.getWalletStore(walletState.selectedCurrency)
|
||||
val selectedWalletData = walletState.selectedWalletData
|
||||
val walletManager = walletStore?.walletManager
|
||||
|
||||
if (walletStore == null || walletManager == null || selectedWalletData == null) {
|
||||
val error = TapError.UnsupportedState(
|
||||
"WalletAction.Send: walletStore or selectedWalletData or walletManager is null",
|
||||
)
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
|
||||
store.dispatchErrorNotification(error)
|
||||
return
|
||||
}
|
||||
if (!networkConnectionManager.isOnline) {
|
||||
store.dispatchErrorNotification(TapError.NoInternetConnection)
|
||||
return
|
||||
}
|
||||
|
||||
val currency = selectedWalletData.currency
|
||||
val initSendStateAction = if (action.amount == null) {
|
||||
val sendableAmounts = walletManager.wallet.getSendableAmounts()
|
||||
if (sendableAmounts.isEmpty()) {
|
||||
val error = TapError.UnsupportedState("WalletAction.Send: Nothing to send")
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
|
||||
store.dispatchErrorNotification(error)
|
||||
return
|
||||
}
|
||||
|
||||
if (walletState.isMultiwalletAllowed) {
|
||||
val amountToSend = findAmountToSend(currency = currency, amounts = sendableAmounts)
|
||||
if (amountToSend == null) {
|
||||
val error = TapError.UnsupportedState("WalletAction.Send: Amount to send is null")
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
|
||||
store.dispatchErrorNotification(error)
|
||||
return
|
||||
}
|
||||
|
||||
makeInitSendStateActionByCurrency(
|
||||
currency = currency,
|
||||
amount = amountToSend,
|
||||
walletStore = walletStore,
|
||||
walletManager = walletManager,
|
||||
selectedWalletData = selectedWalletData,
|
||||
)
|
||||
} else {
|
||||
val isSingleAmount = sendableAmounts.size == 1
|
||||
if (isSingleAmount) {
|
||||
makeInitSendStateActionByAmount(
|
||||
amount = sendableAmounts.first(),
|
||||
walletStore = walletStore,
|
||||
walletManager = walletManager,
|
||||
selectedWalletData = selectedWalletData,
|
||||
)
|
||||
} else {
|
||||
store.dispatch(WalletAction.DialogAction.ChooseCurrency(sendableAmounts))
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// action.amount received from the ChooseCurrency dialog
|
||||
makeInitSendStateActionByAmount(
|
||||
amount = action.amount,
|
||||
walletManager = walletManager,
|
||||
walletStore = walletStore,
|
||||
selectedWalletData = selectedWalletData,
|
||||
)
|
||||
}
|
||||
|
||||
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency)))
|
||||
store.dispatch(initSendStateAction)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
}
|
||||
is WalletAction.ShowSaveWalletIfNeeded -> {
|
||||
showSaveWalletIfNeeded()
|
||||
}
|
||||
is WalletAction.ChangeWallet -> {
|
||||
changeWallet(walletState, action.scope)
|
||||
}
|
||||
is WalletAction.UserWalletChanged -> Unit
|
||||
is WalletAction.WalletStoresChanged -> {
|
||||
// Cancel update job when new wallet stores received
|
||||
updateWalletStoresJob = scope.launch(Dispatchers.Default) {
|
||||
ifActive { fetchTotalFiatBalance(action.walletStores) }
|
||||
ifActive { findMissedDerivations(action.walletStores) }
|
||||
ifActive { tryToShowAppRatingWarning(action.walletStores) }
|
||||
ifActive { store.state.globalState.topUpController?.walletStoresChanged(action.walletStores) }
|
||||
}
|
||||
}
|
||||
is WalletAction.TotalFiatBalanceChanged -> Unit
|
||||
is WalletAction.PopBackToInitialScreen -> {
|
||||
userWalletsListManager.lockIfLockable()
|
||||
val screen = if (walletState.canSaveUserWallets) {
|
||||
AppScreen.Welcome
|
||||
} else {
|
||||
AppScreen.Home
|
||||
}
|
||||
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(screen))
|
||||
}
|
||||
is WalletAction.ChangeSelectedAddress -> {
|
||||
changeSelectedWalletAddress(action.type, walletState)
|
||||
}
|
||||
is WalletAction.UpdateUserWalletArtwork -> {
|
||||
scope.launch {
|
||||
userWalletsListManager
|
||||
.update(
|
||||
userWalletId = action.walletId,
|
||||
update = { userWallet ->
|
||||
userWallet.copy(
|
||||
artworkUrl = GetCardImageUseCase().invoke(
|
||||
cardId = userWallet.cardId,
|
||||
cardPublicKey = userWallet.scanResponse.card.cardPublicKey,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.doOnSuccess {
|
||||
store.dispatch(
|
||||
WalletAction.SetArtworkUrl(userWalletId = action.walletId, url = it.artworkUrl),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findAmountToSend(currency: Currency, amounts: List<Amount>): Amount? {
|
||||
return amounts.find { amount ->
|
||||
val amountType = amount.type
|
||||
if (amountType is AmountType.Token && currency is Currency.Token) {
|
||||
val token = amountType.token
|
||||
token.symbol == currency.currencySymbol && token.contractAddress == currency.token.contractAddress
|
||||
} else {
|
||||
amount.currencySymbol == currency.currencySymbol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeInitSendStateActionByAmount(
|
||||
amount: Amount,
|
||||
walletStore: WalletStoreModel,
|
||||
walletManager: WalletManager,
|
||||
selectedWalletData: WalletDataModel,
|
||||
): PrepareSendScreen = when (amount.type) {
|
||||
AmountType.Coin ->
|
||||
PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = amount,
|
||||
coinRate = selectedWalletData.fiatRate,
|
||||
)
|
||||
is AmountType.Token -> {
|
||||
PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
|
||||
coinRate = walletStore.blockchainWalletData.fiatRate,
|
||||
tokenAmount = amount,
|
||||
tokenRate = selectedWalletData.fiatRate,
|
||||
)
|
||||
}
|
||||
AmountType.Reserve -> {
|
||||
val exception = IllegalStateException("WalletAction.Send: Reserve can't be sent")
|
||||
FirebaseCrashlytics.getInstance().recordException(exception)
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeInitSendStateActionByCurrency(
|
||||
currency: Currency,
|
||||
amount: Amount,
|
||||
walletStore: WalletStoreModel,
|
||||
walletManager: WalletManager,
|
||||
selectedWalletData: WalletDataModel,
|
||||
): PrepareSendScreen = when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = amount,
|
||||
coinRate = selectedWalletData.fiatRate,
|
||||
)
|
||||
}
|
||||
is Currency.Token -> {
|
||||
PrepareSendScreen(
|
||||
walletManager = walletManager,
|
||||
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
|
||||
coinRate = walletStore.blockchainWalletData.fiatRate,
|
||||
tokenAmount = amount,
|
||||
tokenRate = selectedWalletData.fiatRate,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeSelectedWalletAddress(type: AddressType, state: WalletState) {
|
||||
val selectedUserWalletId = userWalletsListManager.selectedUserWalletSync?.walletId.guard {
|
||||
Timber.e("Unable to change selected wallet address, no user wallet selected")
|
||||
return
|
||||
}
|
||||
val selectedCurrency = state.selectedCurrency.guard {
|
||||
Timber.e("Unable to change selected wallet address, no currency selected")
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch(Dispatchers.Default) {
|
||||
walletStoresManager.updateSelectedAddress(selectedUserWalletId, selectedCurrency, type)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchTotalFiatBalance(walletStores: List<WalletStoreModel>) {
|
||||
val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull(walletStores)
|
||||
|
||||
if (totalFiatBalance != null) {
|
||||
store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance))
|
||||
}
|
||||
}
|
||||
|
||||
private fun findMissedDerivations(wallStores: List<WalletStoreModel>) {
|
||||
val missedDerivations = wallStores
|
||||
.filter { store ->
|
||||
store.walletsData.any { it.status is WalletDataModel.MissedDerivation }
|
||||
}
|
||||
.map(WalletStoreModel::blockchainNetwork)
|
||||
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(missedDerivations))
|
||||
}
|
||||
|
||||
private fun tryToShowAppRatingWarning(walletStores: List<WalletStoreModel>) {
|
||||
warningsMiddleware.tryToShowAppRatingWarning(
|
||||
hasNonZeroWallets = walletStores
|
||||
.flatMap { it.walletsData }
|
||||
.any { it.status.amount.isGreaterThan(BigDecimal.ZERO) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun showSaveWalletIfNeeded() {
|
||||
if (preferencesStorage.shouldShowSaveUserWalletScreen &&
|
||||
tangemSdkManager.canUseBiometry &&
|
||||
store.state.navigationState.backStack.lastOrNull() == AppScreen.Wallet
|
||||
) {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet))
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeWallet(state: WalletState, lifecycleScope: LifecycleCoroutineScope) {
|
||||
when {
|
||||
state.canSaveUserWallets -> {
|
||||
Analytics.send(MainScreen.ButtonMyWallets())
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector))
|
||||
}
|
||||
else -> {
|
||||
Analytics.send(MainScreen.ButtonScanCard())
|
||||
store.dispatch(
|
||||
WalletAction.Scan(
|
||||
onScanSuccessEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main),
|
||||
scope = lifecycleScope,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.SignatureCountValidator
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.extensions.hasSignedHashes
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
class WarningsMiddleware {
|
||||
fun handle(action: WalletAction.Warnings, globalState: GlobalState?) {
|
||||
when (action) {
|
||||
is WalletAction.Warnings.Update -> setWarningMessages()
|
||||
is WalletAction.Warnings.CheckIfNeeded -> {
|
||||
showCardWarningsIfNeeded(globalState)
|
||||
val readyToShow = preferencesStorage.appRatingLaunchObserver.isReadyToShow()
|
||||
if (readyToShow) addWarningMessage(warning = WarningMessagesManager.appRatingWarning, autoUpdate = true)
|
||||
}
|
||||
|
||||
is WalletAction.Warnings.CheckHashesCount.VerifyOnlineIfNeeded -> checkHashesCountOnlineIfNeeded()
|
||||
is WalletAction.Warnings.CheckHashesCount.SaveCardId -> {
|
||||
val cardId = globalState?.scanResponse?.card?.cardId
|
||||
cardId?.let { preferencesStorage.usedCardsPrefStorage.scanned(it) }
|
||||
}
|
||||
is WalletAction.Warnings.AppRating.RemindLater -> {
|
||||
preferencesStorage.appRatingLaunchObserver.applyDelayedShowing()
|
||||
}
|
||||
is WalletAction.Warnings.AppRating.SetNeverToShow -> {
|
||||
preferencesStorage.appRatingLaunchObserver.setNeverToShow()
|
||||
}
|
||||
is WalletAction.Warnings.CheckRemainingSignatures -> {
|
||||
if (action.remainingSignatures != null &&
|
||||
action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING
|
||||
) {
|
||||
// store.state.globalState.warningManager?.removeWarnings(R.string.warning_low_signatures_format)
|
||||
addWarningMessage(
|
||||
warning = WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures),
|
||||
autoUpdate = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
is WalletAction.Warnings.AppRating,
|
||||
is WalletAction.Warnings.CheckHashesCount,
|
||||
is WalletAction.Warnings.Set,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
fun tryToShowAppRatingWarning(hasNonZeroWallets: Boolean) {
|
||||
if (hasNonZeroWallets) {
|
||||
preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds()
|
||||
}
|
||||
if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) {
|
||||
addWarningMessage(WarningMessagesManager.appRatingWarning, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCardWarningsIfNeeded(globalState: GlobalState?) {
|
||||
globalState?.scanResponse?.let { scanResponse ->
|
||||
val card = scanResponse.card
|
||||
globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
|
||||
if (card.isTestCard) {
|
||||
addWarningMessage(WarningMessagesManager.testCardWarning, autoUpdate = true)
|
||||
return@let
|
||||
}
|
||||
|
||||
showWarningLowRemainingSignaturesIfNeeded(card)
|
||||
if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) {
|
||||
addWarningMessage(WarningMessagesManager.devCardWarning)
|
||||
} else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) {
|
||||
checkIfWarningNeeded(scanResponse)?.let { warning -> addWarningMessage(warning) }
|
||||
}
|
||||
if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release && !globalState.cardVerifiedOnline) {
|
||||
addWarningMessage(WarningMessagesManager.onlineVerificationFailed)
|
||||
}
|
||||
if (scanResponse.isDemoCard()) {
|
||||
addWarningMessage(WarningMessagesManager.demoCardWarning)
|
||||
}
|
||||
setWarningMessages()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showWarningLowRemainingSignaturesIfNeeded(card: CardDTO) {
|
||||
val remainingSignatures = card.wallets.firstOrNull()?.remainingSignatures
|
||||
if (remainingSignatures != null &&
|
||||
remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING
|
||||
) {
|
||||
addWarningMessage(WarningMessagesManager.remainingSignaturesNotEnough(remainingSignatures))
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfWarningNeeded(scanResponse: ScanResponse): WarningMessage? {
|
||||
if (scanResponse.cardTypesResolver.isTangemTwins() || scanResponse.isDemoCard()) return null
|
||||
|
||||
if (scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
val isBackupForbidden = with(scanResponse.card.settings) { !(isBackupAllowed || isHDWalletAllowed) }
|
||||
return if (scanResponse.card.hasSignedHashes() && isBackupForbidden) {
|
||||
WarningMessagesManager.signedHashesMultiWalletWarning
|
||||
} else {
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return if (scanResponse.card.hasSignedHashes()) {
|
||||
WarningMessagesManager.alreadySignedHashesWarning
|
||||
} else {
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkHashesCountOnlineIfNeeded() {
|
||||
val alreadySignedHashesWarning = WarningMessagesManager.alreadySignedHashesWarning
|
||||
val manager = store.state.globalState.warningManager ?: return
|
||||
if (manager.containsWarning(alreadySignedHashesWarning)) return
|
||||
|
||||
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||
if (!networkConnectionManager.isOnline) return
|
||||
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
val card = scanResponse?.card
|
||||
if (card == null || preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) return
|
||||
|
||||
if (scanResponse.cardTypesResolver.isTangemTwins() || scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
return
|
||||
}
|
||||
|
||||
val validator = store.state.walletState.walletManagers.firstOrNull()
|
||||
as? SignatureCountValidator
|
||||
scope.launch {
|
||||
val signedHashes = card.wallets.firstOrNull()?.totalSignedHashes ?: 0
|
||||
val result = validator?.validateSignatureCount(signedHashes)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
SimpleResult.Success -> {
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
}
|
||||
is SimpleResult.Failure ->
|
||||
if (signedHashes > 0 || result.error is BlockchainSdkError.SignatureCountNotMatched) {
|
||||
alreadySignedHashesWarning.isHidden = false
|
||||
addWarningMessage(alreadySignedHashesWarning, true)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addWarningMessage(warning: WarningMessage, autoUpdate: Boolean = false) {
|
||||
store.state.globalState.warningManager?.addWarning(warning)
|
||||
if (autoUpdate) setWarningMessages()
|
||||
}
|
||||
|
||||
private fun setWarningMessages() {
|
||||
store.dispatchOnMain(WalletAction.Warnings.Set(getWarnings()))
|
||||
}
|
||||
|
||||
private fun getWarnings(): List<WarningMessage> {
|
||||
val warningManager = store.state.globalState.warningManager ?: return emptyList()
|
||||
return warningManager.getWarnings(
|
||||
WarningMessage.Location.MainScreen,
|
||||
store.state.walletState.blockchains,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.models
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.wallet.R
|
||||
|
||||
sealed interface WalletDialog : StateDialog {
|
||||
data class SelectAmountToSendDialog(val amounts: List<Amount>) : WalletDialog
|
||||
object SignedHashesMultiWalletDialog : WalletDialog
|
||||
data class ChooseTradeActionDialog(
|
||||
val buyAllowed: Boolean,
|
||||
val sellAllowed: Boolean,
|
||||
val swapAllowed: Boolean,
|
||||
) : WalletDialog
|
||||
|
||||
data class CurrencySelectionDialog(
|
||||
val currenciesList: List<FiatCurrency>,
|
||||
val currentAppCurrency: FiatCurrency,
|
||||
) : WalletDialog
|
||||
|
||||
data class RemoveWalletDialog(
|
||||
val currencyTitle: String,
|
||||
val onOk: () -> Unit,
|
||||
) : WalletDialog {
|
||||
val messageRes: Int = R.string.token_details_hide_alert_message
|
||||
val titleRes: Int = R.string.token_details_hide_alert_title
|
||||
val primaryButtonRes: Int = R.string.token_details_hide_alert_hide
|
||||
}
|
||||
|
||||
data class TokensAreLinkedDialog(
|
||||
val currencyTitle: String,
|
||||
val currencySymbol: String,
|
||||
) : WalletDialog {
|
||||
val messageRes: Int = R.string.token_details_unable_hide_alert_message
|
||||
val titleRes: Int = R.string.token_details_unable_hide_alert_title
|
||||
}
|
||||
|
||||
data class RussianCardholdersWarningDialog(val data: Data?) : WalletDialog {
|
||||
data class Data(val topUpUrl: String)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
||||
class AppCurrencyReducer {
|
||||
fun reduce(action: WalletAction.AppCurrencyAction, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.AppCurrencyAction.SelectAppCurrency,
|
||||
is WalletAction.AppCurrencyAction.ChooseAppCurrency,
|
||||
-> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
||||
class MultiWalletReducer {
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
state.copy(selectedCurrency = action.currency)
|
||||
}
|
||||
|
||||
is WalletAction.MultiWallet.TryToRemoveWallet -> state
|
||||
is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(
|
||||
missingDerivations = action.blockchains,
|
||||
)
|
||||
|
||||
is WalletAction.MultiWallet.BackupWallet -> state
|
||||
is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy(state = ProgressState.Loading)
|
||||
is WalletAction.MultiWallet.CheckForBackupWarning -> state.copy(
|
||||
showBackupWarning = action.card.settings.isBackupAllowed &&
|
||||
action.card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
)
|
||||
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.userwallets.Artwork
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import org.rekotlin.Action
|
||||
|
||||
object WalletReducer {
|
||||
fun reduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState =
|
||||
internalReduce(action, state, appStateHolder)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun internalReduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState {
|
||||
val multiWalletReducer = MultiWalletReducer()
|
||||
val appCurrencyReducer = AppCurrencyReducer()
|
||||
|
||||
if (action !is WalletAction) return state.walletState
|
||||
|
||||
var newState = state.walletState
|
||||
|
||||
when (action) {
|
||||
is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState)
|
||||
is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState)
|
||||
is WalletAction.LoadData.Failure -> {
|
||||
when (action.error) {
|
||||
is TapError.NoInternetConnection -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Error,
|
||||
error = ErrorType.NoInternetConnection,
|
||||
)
|
||||
}
|
||||
is TapError.UnknownBlockchain -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Error,
|
||||
error = ErrorType.UnknownBlockchain,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Error,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadData -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadData.Refresh -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Refreshing,
|
||||
error = null,
|
||||
)
|
||||
}
|
||||
is WalletAction.AppCurrencyAction -> {
|
||||
newState = appCurrencyReducer.reduce(action, newState)
|
||||
}
|
||||
is WalletAction.UserWalletChanged -> with(action.userWallet) {
|
||||
val card = scanResponse.card
|
||||
newState = WalletState(
|
||||
isMultiwalletAllowed = isMultiCurrency,
|
||||
cardImage = Artwork(
|
||||
artworkId = artworkUrl,
|
||||
),
|
||||
isTestnet = card.isTestCard,
|
||||
state = ProgressState.Loading,
|
||||
showBackupWarning = isMultiCurrency &&
|
||||
card.settings.isBackupAllowed &&
|
||||
card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
walletCardsCount = card.findCardsCount(),
|
||||
walletsStores = newState.walletsStores,
|
||||
totalBalance = if (isMultiCurrency) {
|
||||
newState.totalBalance
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
is WalletAction.WalletStoresChanged -> {
|
||||
newState = newState.copy(
|
||||
walletsStores = action.walletStores,
|
||||
selectedCurrency = findSelectedCurrency(
|
||||
walletsStores = action.walletStores,
|
||||
currentSelectedCurrency = newState.selectedCurrency,
|
||||
isMultiWalletAllowed = newState.isMultiwalletAllowed,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletAction.TotalFiatBalanceChanged -> {
|
||||
newState = newState.copy(
|
||||
totalBalance = action.balance,
|
||||
)
|
||||
}
|
||||
is WalletAction.LoadData.Success -> {
|
||||
newState = newState.copy(state = ProgressState.Done)
|
||||
}
|
||||
is WalletAction.UpdateCanSaveUserWallets -> {
|
||||
newState = newState.copy(canSaveUserWallets = action.canSaveUserWallets)
|
||||
}
|
||||
is WalletAction.SetArtworkUrl -> {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync?.walletId
|
||||
|
||||
if (selectedUserWallet == action.userWalletId) {
|
||||
newState = newState.copy(
|
||||
cardImage = Artwork(artworkId = action.url),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
appStateHolder.walletState = newState
|
||||
return newState
|
||||
}
|
||||
|
||||
fun findSelectedCurrency(
|
||||
walletsStores: List<WalletStoreModel>,
|
||||
currentSelectedCurrency: Currency?,
|
||||
isMultiWalletAllowed: Boolean,
|
||||
): Currency? = if (isMultiWalletAllowed) {
|
||||
currentSelectedCurrency
|
||||
} else {
|
||||
walletsStores.firstOrNull()
|
||||
?.walletsData
|
||||
?.firstOrNull()
|
||||
?.currency
|
||||
}
|
||||
|
||||
private fun CardDTO.findCardsCount(): Int? {
|
||||
return (this.backupStatus as? CardDTO.BackupStatus.Active)?.cardCount?.inc()
|
||||
}
|
||||
|
||||
fun Wallet.createAddressesData(): List<WalletDataModel.AddressData> {
|
||||
val listOfAddressData = mutableListOf<WalletDataModel.AddressData>()
|
||||
// put a defaultAddress at the first place
|
||||
addresses.forEach {
|
||||
val addressData = WalletDataModel.AddressData(
|
||||
it.value,
|
||||
it.type,
|
||||
getShareUri(it.value),
|
||||
getExploreUrl(it.value),
|
||||
)
|
||||
if (it.type == AddressType.Default) {
|
||||
listOfAddressData.add(0, addressData)
|
||||
} else {
|
||||
listOfAddressData.add(addressData)
|
||||
}
|
||||
}
|
||||
return listOfAddressData
|
||||
}
|
||||
|
||||
private fun handleCheckSignedHashesActions(action: WalletAction.Warnings, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.Warnings.Set -> state.copy(mainWarningsList = action.warningList)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.redux.utils
|
||||
|
||||
const val UNKNOWN_AMOUNT_SIGN = "—"
|
||||
const val ROUGH_SIGN = "≈"
|
||||
const val CAN_BE_LOWER_SIGN = "<"
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import androidx.annotation.IdRes
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.wallet.ui.utils.getFormattedCryptoAmount
|
||||
import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.CardBalanceBinding
|
||||
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
class BalanceWidget(
|
||||
private val binding: CardBalanceBinding,
|
||||
private val fragment: WalletFragment,
|
||||
private val blockchainWalletData: WalletDataModel,
|
||||
private val tokenWalletData: WalletDataModel?,
|
||||
) {
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun setup() {
|
||||
when (blockchainWalletData.status) {
|
||||
is WalletDataModel.Loading -> {
|
||||
with(binding) {
|
||||
lBalance.root.show()
|
||||
lBalanceError.root.hide()
|
||||
lBalance.tvFiatAmount.hide()
|
||||
|
||||
lBalance.tvCurrency.text = blockchainWalletData.currency.currencyName
|
||||
lBalance.tvAmount.text = ""
|
||||
}
|
||||
|
||||
showStatus(R.id.tv_status_loading)
|
||||
|
||||
if (tokenWalletData != null) {
|
||||
showBalanceWithToken(blockchainWalletData, false)
|
||||
} else {
|
||||
showBalanceWithoutToken(blockchainWalletData, false)
|
||||
}
|
||||
}
|
||||
is WalletDataModel.VerifiedOnline,
|
||||
is WalletDataModel.TransactionInProgress,
|
||||
-> with(binding.lBalance) {
|
||||
root.show()
|
||||
binding.lBalanceError.root.hide()
|
||||
val statusView = if (blockchainWalletData.status is WalletDataModel.VerifiedOnline) {
|
||||
R.id.tv_status_verified
|
||||
} else {
|
||||
// tvStatusError.text = fragment.getText(R.string.wallet_balance_tx_in_progress)
|
||||
R.id.group_error
|
||||
}
|
||||
showStatus(statusView)
|
||||
// tvStatusErrorMessage.hide()
|
||||
|
||||
if (tokenWalletData != null) {
|
||||
showBalanceWithToken(blockchainWalletData, true)
|
||||
} else {
|
||||
showBalanceWithoutToken(blockchainWalletData, true)
|
||||
}
|
||||
}
|
||||
is WalletDataModel.Unreachable -> with(binding.lBalance) {
|
||||
root.show()
|
||||
binding.lBalanceError.root.hide()
|
||||
tvFiatAmount.hide()
|
||||
groupBaseCurrency.hide()
|
||||
|
||||
val currency = tokenWalletData?.currency?.currencySymbol
|
||||
?: blockchainWalletData.currency.currencyName
|
||||
tvCurrency.text = currency
|
||||
tvAmount.text = ""
|
||||
|
||||
// tvStatusErrorMessage.text = blockchainWalletData.status.errorMessage
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
// tvStatusError.text = fragment.getString(R.string.wallet_balance_blockchain_unreachable)
|
||||
|
||||
showStatus(R.id.group_error)
|
||||
// tvStatusErrorMessage.show(!blockchainWalletData.status.errorMessage.isNullOrBlank())
|
||||
}
|
||||
is WalletDataModel.NoAccount -> with(binding.lBalanceError) {
|
||||
binding.lBalance.root.hide()
|
||||
binding.lBalanceError.root.show()
|
||||
tvErrorTitle.text = fragment.getText(R.string.wallet_error_no_account)
|
||||
tvErrorDescriptions.text =
|
||||
fragment.getString(
|
||||
R.string.no_account_generic,
|
||||
blockchainWalletData.status.amountToCreateAccount,
|
||||
blockchainWalletData.currency.currencySymbol,
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showStatus(@IdRes viewRes: Int) = with(binding.lBalance) {
|
||||
// groupError.show(viewRes == R.id.group_error)
|
||||
tvStatusLoading.show(viewRes == R.id.tv_status_loading)
|
||||
tvStatusVerified.show(viewRes == R.id.tv_status_verified)
|
||||
}
|
||||
|
||||
private fun showBalanceWithToken(data: WalletDataModel, showAmount: Boolean) = with(binding.lBalance) {
|
||||
groupBaseCurrency.show()
|
||||
tvCurrency.text = tokenWalletData?.currency?.currencySymbol
|
||||
tvBaseCurrency.text = data.currency.currencyName
|
||||
tvAmount.text = if (showAmount) tokenWalletData?.getFormattedCryptoAmount() else ""
|
||||
tvBaseAmount.text = if (showAmount) data.getFormattedCryptoAmount() else ""
|
||||
if (showAmount) {
|
||||
tvFiatAmount.show()
|
||||
tvFiatAmount.text = tokenWalletData?.getFormattedFiatAmount(store.state.globalState.appCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBalanceWithoutToken(data: WalletDataModel, showAmount: Boolean) = with(binding.lBalance) {
|
||||
groupBaseCurrency.hide()
|
||||
tvCurrency.text = data.currency.currencyName
|
||||
tvAmount.text = if (showAmount) data.getFormattedCryptoAmount() else ""
|
||||
if (showAmount) {
|
||||
tvFiatAmount.show()
|
||||
tvFiatAmount.text = data.getFormattedFiatAmount(store.state.globalState.appCurrency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.view.View
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object MultipleAddressUiHelper {
|
||||
|
||||
private val blockchainsSupportingSplit = listOf(
|
||||
Blockchain.Bitcoin,
|
||||
Blockchain.BitcoinTestnet,
|
||||
Blockchain.Litecoin,
|
||||
Blockchain.BitcoinCash,
|
||||
Blockchain.Cardano,
|
||||
)
|
||||
|
||||
fun typeToId(type: AddressType, blockchain: Blockchain): Int {
|
||||
return if (blockchain in blockchainsSupportingSplit) {
|
||||
if (type == AddressType.Legacy) {
|
||||
R.id.chip_legacy
|
||||
} else {
|
||||
R.id.chip_default
|
||||
}
|
||||
} else {
|
||||
View.NO_ID
|
||||
}
|
||||
}
|
||||
|
||||
fun idToType(id: Int, blockchain: Blockchain): AddressType? {
|
||||
return when (blockchain) {
|
||||
in blockchainsSupportingSplit -> {
|
||||
when (id) {
|
||||
R.id.chip_default -> AddressType.Default
|
||||
R.id.chip_legacy -> AddressType.Legacy
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,509 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.badoo.mvicore.DiffStrategy
|
||||
import com.badoo.mvicore.ModelWatcher
|
||||
import com.badoo.mvicore.modelWatcher
|
||||
import com.tangem.common.doOnResult
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.sdk.extensions.dpToPx
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.common.analytics.events.DetailsScreen
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.utils.SafeStoreSubscriber
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.models.WalletWarning
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter
|
||||
import com.tangem.tap.features.wallet.ui.images.load
|
||||
import com.tangem.tap.features.wallet.ui.test.TestWallet
|
||||
import com.tangem.tap.features.wallet.ui.utils.*
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManagerSafe
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentWalletDetailsBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Wallet details fragment - use only for MultiWallet
|
||||
*/
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Suppress("LargeClass", "MagicNumber")
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
@AndroidEntryPoint
|
||||
class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), SafeStoreSubscriber<WalletState> {
|
||||
|
||||
@Inject
|
||||
lateinit var swapInteractor: SwapInteractor
|
||||
|
||||
@Inject
|
||||
lateinit var swapFeatureToggleManager: SwapFeatureToggleManager
|
||||
|
||||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||
private lateinit var warningMessagesAdapter: WalletDetailWarningMessagesAdapter
|
||||
|
||||
private val binding: FragmentWalletDetailsBinding by viewBinding(FragmentWalletDetailsBinding::bind)
|
||||
|
||||
private val walletDataWatcher: ModelWatcher<WalletDataModel> = modelWatcher {
|
||||
val addressCardStrategy: DiffStrategy<WalletDataModel> = { old, new ->
|
||||
old.currency != new.currency || old.walletAddresses != new.walletAddresses
|
||||
}
|
||||
|
||||
WalletDataModel::currency {
|
||||
handleCurrencyIcon(it)
|
||||
}
|
||||
WalletDataModel::walletAddresses { walletAddresses ->
|
||||
setupCopyAndShareButtons(walletAddresses?.selectedAddress?.address)
|
||||
}
|
||||
WalletDataModel::currency { currency ->
|
||||
setupCurrency(currency)
|
||||
}
|
||||
watch({ it }, addressCardStrategy) { walletData ->
|
||||
setupAddressCard(
|
||||
shouldShowMultipleAddress = walletData.shouldShowMultipleAddress(),
|
||||
selectedAddress = walletData.walletAddresses?.selectedAddress,
|
||||
currency = walletData.currency,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val walletStateWatcher: ModelWatcher<WalletState> = modelWatcher {
|
||||
val walletDataStrategy: DiffStrategy<WalletState> = { old, new ->
|
||||
new.walletsStores.isNotEmpty() &&
|
||||
new.selectedCurrency != null &&
|
||||
(old.selectedCurrency != new.selectedCurrency || old.walletsStores != new.walletsStores)
|
||||
}
|
||||
|
||||
watch({ it }, walletDataStrategy) { state ->
|
||||
val selectedWallet = state.selectedWalletData
|
||||
if (selectedWallet != null) {
|
||||
setupBalanceData(selectedWallet)
|
||||
setupSwipeRefresh(selectedWallet)
|
||||
walletDataWatcher.invoke(selectedWallet)
|
||||
|
||||
val walletStore = state.getWalletStore(state.selectedCurrency)
|
||||
if (walletStore != null) {
|
||||
handleWarnings(
|
||||
selectedWallet.assembleWarnings(
|
||||
blockchainAmount = walletStore.blockchainWalletData.status.amount,
|
||||
blockchainWalletRent = walletStore.walletRent,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
(WalletState::selectedWalletData or WalletState::isExchangeServiceFeatureOn) { state ->
|
||||
val selectedWallet = state.selectedWalletData
|
||||
if (selectedWallet != null) {
|
||||
val blockchainAmount: BigDecimal = state.getBlockchainAmount(selectedWallet.currency)
|
||||
setupButtonsRow(selectedWallet, state.isExchangeServiceFeatureOn, blockchainAmount)
|
||||
}
|
||||
}
|
||||
(WalletState::state or WalletState::error) { state ->
|
||||
setupNoInternetHandling(state.state, state.error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
Analytics.send(DetailsScreen.ScreenOpened())
|
||||
activity?.onBackPressedDispatcher?.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
|
||||
setupTransactionsRecyclerView()
|
||||
setupButtons()
|
||||
setupWarningsRecyclerView()
|
||||
setupTestActionButton()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state -> state.select(AppState::walletState) }
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
clearWatchers()
|
||||
}
|
||||
|
||||
private fun setupTransactionsRecyclerView() = with(binding) {
|
||||
pendingTransactionAdapter = PendingTransactionsAdapter()
|
||||
rvPendingTransaction.layoutManager = LinearLayoutManager(requireContext())
|
||||
rvPendingTransaction.adapter = pendingTransactionAdapter
|
||||
}
|
||||
|
||||
private fun setupWarningsRecyclerView() = with(binding) {
|
||||
warningMessagesAdapter = WalletDetailWarningMessagesAdapter()
|
||||
rvWarningMessages.layoutManager = LinearLayoutManager(requireContext())
|
||||
rvWarningMessages.adapter = warningMessagesAdapter
|
||||
rvWarningMessages.addItemDecoration(SpaceItemDecoration.vertical(8f))
|
||||
}
|
||||
|
||||
private fun setupButtons() {
|
||||
binding.rowButtons.onSendClick = { store.dispatch(WalletAction.Send()) }
|
||||
}
|
||||
|
||||
private fun setupTestActionButton() {
|
||||
view?.findViewById<View>(R.id.l_balance)?.let { view ->
|
||||
TestActions.initFor(view = view, actions = TestWallet.solanaRentExemptWarning())
|
||||
}
|
||||
}
|
||||
|
||||
override fun newStateOnMain(state: WalletState) {
|
||||
if (activity == null || view == null) return
|
||||
if (state.selectedWalletData == null) return
|
||||
walletStateWatcher.invoke(state)
|
||||
|
||||
updateViewMeasurements()
|
||||
}
|
||||
|
||||
private fun updateViewMeasurements() {
|
||||
val tvFiatAmount = binding.lWalletDetails.lBalance.tvFiatAmount
|
||||
val paddingStart = if (tvFiatAmount.text == UNKNOWN_AMOUNT_SIGN) 16f else 12f
|
||||
|
||||
tvFiatAmount.setPadding(
|
||||
tvFiatAmount.dpToPx(paddingStart).toInt(),
|
||||
tvFiatAmount.paddingTop,
|
||||
tvFiatAmount.paddingEnd,
|
||||
tvFiatAmount.paddingBottom,
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupCurrency(currency: Currency) = with(binding) {
|
||||
tvCurrencyTitle.text = currency.currencyName
|
||||
|
||||
if (currency is Currency.Token) {
|
||||
tvCurrencySubtitle.text = tvCurrencySubtitle.getString(
|
||||
R.string.wallet_currency_subtitle,
|
||||
currency.blockchain.fullName,
|
||||
)
|
||||
tvCurrencySubtitle.show()
|
||||
} else {
|
||||
tvCurrencySubtitle.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSwipeRefresh(walletData: WalletDataModel) {
|
||||
binding.srlWalletDetails.setOnRefreshListener {
|
||||
if (walletData.status !is WalletDataModel.Loading) {
|
||||
Analytics.send(Token.Refreshed())
|
||||
val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync.guard {
|
||||
Timber.e("Unable to refresh wallet details screen, no user wallet selected")
|
||||
return@setOnRefreshListener
|
||||
}
|
||||
binding.srlWalletDetails.isRefreshing = true
|
||||
lifecycleScope.launch(Dispatchers.Default) {
|
||||
walletCurrenciesManager.update(selectedUserWallet, walletData.currency).doOnResult {
|
||||
withMainContext {
|
||||
binding.srlWalletDetails.isRefreshing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCopyAndShareButtons(walletAddress: String?) {
|
||||
binding.lWalletDetails.btnCopy.setOnClickListener {
|
||||
if (walletAddress != null) store.dispatch(WalletAction.CopyAddress(walletAddress, requireContext()))
|
||||
}
|
||||
|
||||
binding.lWalletDetails.btnShare.setOnClickListener {
|
||||
if (walletAddress != null) store.dispatch(WalletAction.ShareAddress(walletAddress, requireContext()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtonsRow(
|
||||
selectedWallet: WalletDataModel,
|
||||
isExchangeServiceFeatureOn: Boolean,
|
||||
blockchainAmount: BigDecimal,
|
||||
) {
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
binding.rowButtons.apply {
|
||||
onBuyClick = { store.dispatch(TradeCryptoAction.Buy()) }
|
||||
onSellClick = { store.dispatch(TradeCryptoAction.Sell) }
|
||||
onSwapClick = { store.dispatch(TradeCryptoAction.Swap) }
|
||||
onTradeClick = {
|
||||
store.dispatch(
|
||||
WalletAction.DialogAction.ChooseTradeActionDialog(
|
||||
buyAllowed = selectedWallet.isAvailableToBuy(exchangeManager),
|
||||
sellAllowed = selectedWallet.isAvailableToSell(exchangeManager),
|
||||
swapAllowed = selectedWallet.isAvailableToSwap(
|
||||
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||
swapInteractor = swapInteractor,
|
||||
isSingleWallet = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
val actions = selectedWallet.getAvailableActions(
|
||||
swapInteractor = swapInteractor,
|
||||
exchangeManager = exchangeManager,
|
||||
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||
isSingleWallet = false,
|
||||
)
|
||||
binding.rowButtons.updateButtonsVisibility(
|
||||
actions = actions,
|
||||
exchangeServiceFeatureOn = isExchangeServiceFeatureOn,
|
||||
sendAllowed = selectedWallet.mainButton(blockchainAmount).enabled,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleWarnings(warnings: List<WalletWarning>) = with(binding) {
|
||||
val converter = WalletWarningConverter(requireContext())
|
||||
val warningDetails = warnings.map { converter.convert(it) }
|
||||
|
||||
warningMessagesAdapter.submitList(warningDetails)
|
||||
rvWarningMessages.show(warningDetails.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun handleCurrencyIcon(currency: Currency) = with(binding.lWalletDetails.lBalance) {
|
||||
ivCurrency.load(
|
||||
currency = currency,
|
||||
derivationStyle = store.state.globalState.scanResponse
|
||||
?.derivationStyleProvider?.getDerivationStyle(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
|
||||
val knownTransactions = pendingTransactions.filterNot { it.type == PendingTransactionType.Unknown }
|
||||
pendingTransactionAdapter.submitList(knownTransactions)
|
||||
binding.rvPendingTransaction.show(knownTransactions.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun setupAddressCard(
|
||||
shouldShowMultipleAddress: Boolean,
|
||||
selectedAddress: WalletDataModel.AddressData?,
|
||||
currency: Currency,
|
||||
) = with(binding.lWalletDetails) {
|
||||
if (selectedAddress == null) return@with
|
||||
|
||||
setupAddressTypeChips(shouldShowMultipleAddress, selectedAddress, currency)
|
||||
|
||||
tvAddress.text = selectedAddress.address
|
||||
tvExplore.setOnClickListener {
|
||||
store.dispatch(WalletAction.ExploreAddress(selectedAddress.exploreUrl, requireContext()))
|
||||
}
|
||||
ivQrCode.setImageBitmap(selectedAddress.shareUrl.toQrCode())
|
||||
|
||||
tvReceiveMessage.text = when (currency) {
|
||||
is Currency.Blockchain -> tvReceiveMessage.getString(
|
||||
id = R.string.address_qr_code_message_format,
|
||||
currency.blockchain.fullName,
|
||||
currency.currencySymbol,
|
||||
currency.blockchain.fullName,
|
||||
)
|
||||
is Currency.Token -> tvReceiveMessage.getString(
|
||||
id = R.string.address_qr_code_message_format,
|
||||
currency.token.name,
|
||||
currency.currencySymbol,
|
||||
currency.blockchain.fullName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAddressTypeChips(
|
||||
shouldShowMultipleAddress: Boolean,
|
||||
selectedAddress: WalletDataModel.AddressData,
|
||||
currency: Currency,
|
||||
) = with(binding.lWalletDetails) {
|
||||
if (shouldShowMultipleAddress && currency is Currency.Blockchain) {
|
||||
(cardBalance as? ViewGroup)?.beginDelayedTransition()
|
||||
chipGroupAddressType.show()
|
||||
chipGroupAddressType.fitChipsByGroupWidth()
|
||||
|
||||
val checkedId = MultipleAddressUiHelper.typeToId(selectedAddress.type, currency.blockchain)
|
||||
if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
|
||||
|
||||
chipGroupAddressType.setOnCheckedChangeListener { _, checkedId ->
|
||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||
val type =
|
||||
MultipleAddressUiHelper.idToType(checkedId, currency.blockchain)
|
||||
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
|
||||
}
|
||||
} else {
|
||||
chipGroupAddressType.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNoInternetHandling(progressState: ProgressState, errorType: ErrorType?) {
|
||||
if (progressState == ProgressState.Error) {
|
||||
if (errorType == ErrorType.NoInternetConnection) {
|
||||
binding.srlWalletDetails.isRefreshing = false
|
||||
(activity as? SnackbarHandler)?.showSnackbar(
|
||||
text = R.string.wallet_notification_no_internet,
|
||||
buttonTitle = R.string.common_retry,
|
||||
) { store.dispatch(WalletAction.LoadData) }
|
||||
}
|
||||
} else {
|
||||
(activity as? SnackbarHandler)?.dismissSnackbar()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupBalanceData(walletData: WalletDataModel) = with(binding.lWalletDetails) {
|
||||
when (val status = walletData.status) {
|
||||
is WalletDataModel.Loading -> {
|
||||
lBalanceError.root.hide()
|
||||
lBalance.root.show()
|
||||
lBalance.groupBalance.show()
|
||||
lBalance.tvError.hide()
|
||||
lBalance.tvAmount.text = walletData.getFormattedCryptoAmount()
|
||||
lBalance.tvFiatAmount.text = walletData.getFormattedFiatAmount(store.state.globalState.appCurrency)
|
||||
lBalance.tvStatus.setLoadingStatus(R.string.wallet_balance_loading)
|
||||
}
|
||||
is WalletDataModel.VerifiedOnline,
|
||||
is WalletDataModel.SameCurrencyTransactionInProgress,
|
||||
is WalletDataModel.TransactionInProgress,
|
||||
-> {
|
||||
lBalanceError.root.hide()
|
||||
lBalance.root.show()
|
||||
lBalance.groupBalance.show()
|
||||
lBalance.tvError.hide()
|
||||
lBalance.tvAmount.text = walletData.getFormattedCryptoAmount()
|
||||
lBalance.tvFiatAmount.text = walletData.getFormattedFiatAmount(store.state.globalState.appCurrency)
|
||||
when (status) {
|
||||
is WalletDataModel.VerifiedOnline,
|
||||
is WalletDataModel.SameCurrencyTransactionInProgress,
|
||||
-> {
|
||||
lBalance.tvStatus.setVerifiedBalanceStatus(R.string.wallet_balance_verified)
|
||||
showPendingTransactionsIfPresent(status.pendingTransactions)
|
||||
}
|
||||
|
||||
is WalletDataModel.TransactionInProgress -> {
|
||||
lBalance.tvStatus.setWarningStatus(R.string.wallet_balance_tx_in_progress)
|
||||
showPendingTransactionsIfPresent(status.pendingTransactions)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
is WalletDataModel.Unreachable -> {
|
||||
lBalanceError.root.hide()
|
||||
lBalance.root.show()
|
||||
lBalance.groupBalance.hide()
|
||||
lBalance.tvError.show()
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
// lBalance.tvError.setWarningStatus(
|
||||
// R.string.wallet_balance_blockchain_unreachable,
|
||||
// status.errorMessage,
|
||||
// )
|
||||
}
|
||||
is WalletDataModel.NoAccount -> {
|
||||
lBalance.root.hide()
|
||||
lBalanceError.root.show()
|
||||
lBalanceError.tvErrorTitle.text = getText(R.string.wallet_error_no_account)
|
||||
lBalanceError.tvErrorDescriptions.text =
|
||||
getString(
|
||||
R.string.no_account_generic,
|
||||
status.amountToCreateAccount,
|
||||
walletData.currency.currencySymbol,
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_remove -> {
|
||||
store.state.walletState.selectedWalletData?.let { walletData ->
|
||||
store.dispatch(WalletAction.MultiWallet.TryToRemoveWallet(walletData.currency))
|
||||
true
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Deprecated in Java",
|
||||
replaceWith = ReplaceWith("inflater.inflate(R.menu.menu_wallet_details, menu)", "com.tangem.wallet.R"),
|
||||
)
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_wallet_details, menu)
|
||||
}
|
||||
|
||||
private fun clearWatchers() {
|
||||
walletDataWatcher.clear()
|
||||
walletStateWatcher.clear()
|
||||
}
|
||||
|
||||
private fun TextView.setWarningStatus(mainMessage: Int, error: String? = null) {
|
||||
val text = getString(mainMessage).appendIfNotNull(error, "\nError: ")
|
||||
setStatus(text, R.color.warning, R.drawable.ic_warning_small)
|
||||
}
|
||||
|
||||
private fun TextView.setVerifiedBalanceStatus(mainMessage: Int) {
|
||||
setStatus(getString(mainMessage), R.color.accent, R.drawable.ic_ok)
|
||||
}
|
||||
|
||||
private fun TextView.setLoadingStatus(mainMessage: Int) {
|
||||
setStatus(getString(mainMessage), R.color.darkGray4, null)
|
||||
}
|
||||
|
||||
private fun TextView.setStatus(text: String, @ColorRes color: Int, @DrawableRes drawable: Int?) {
|
||||
this.text = text
|
||||
setTextColor(getColor(color))
|
||||
setCompoundDrawablesWithIntrinsicBounds(drawable ?: 0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,326 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import coil.load
|
||||
import coil.size.Scale
|
||||
import com.badoo.mvicore.modelWatcher
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.core.ui.utils.OneTouchClickListener
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.tap.MainActivity
|
||||
import com.tangem.tap.common.analytics.events.Portfolio
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.utils.SafeStoreSubscriber
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.statePrinter.printScanResponseState
|
||||
import com.tangem.tap.domain.statePrinter.printWalletState
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView
|
||||
import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView
|
||||
import com.tangem.tap.features.wallet.ui.wallet.WalletView
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentWalletBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<WalletState> {
|
||||
|
||||
@Inject
|
||||
lateinit var swapInteractor: SwapInteractor
|
||||
|
||||
@Inject
|
||||
lateinit var swapFeatureToggleManager: SwapFeatureToggleManager
|
||||
|
||||
@Inject
|
||||
lateinit var networkConnectionManager: NetworkConnectionManager
|
||||
|
||||
private lateinit var warningsAdapter: WarningMessagesAdapter
|
||||
|
||||
private val binding: FragmentWalletBinding by viewBinding(FragmentWalletBinding::bind)
|
||||
|
||||
private var walletView: WalletView = MultiWalletView()
|
||||
|
||||
private val viewModel by viewModels<WalletViewModel>()
|
||||
|
||||
private val totalBalanceWatcher = modelWatcher {
|
||||
(WalletState::totalBalance) { totalBalance ->
|
||||
totalBalance?.let {
|
||||
viewModel.onBalanceLoaded(totalBalance)
|
||||
store.state.globalState.topUpController?.totalBalanceStateChanged(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val isNetworkConnectionError = mutableStateOf(false)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
activity?.lifecycle?.addObserver(viewModel)
|
||||
|
||||
activity?.onBackPressedDispatcher?.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(WalletAction.PopBackToInitialScreen)
|
||||
}
|
||||
},
|
||||
)
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
setStatusBarColor(R.color.background_secondary)
|
||||
|
||||
subscribeOnNetworkStateChanging()
|
||||
|
||||
store.subscribe(this) { state ->
|
||||
state.select { it.walletState }
|
||||
}
|
||||
walletView.setFragment(this, binding)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
walletView.removeFragment()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
walletView.onDestroyFragment()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
|
||||
binding.toolbar.setNavigationOnClickListener(
|
||||
OneTouchClickListener {
|
||||
store.dispatch(WalletAction.ChangeWallet(scope = requireActivity().lifecycleScope))
|
||||
},
|
||||
)
|
||||
setupWarningsRecyclerView()
|
||||
walletView.changeWalletView(this, binding)
|
||||
addCustomActionOnCard()
|
||||
}
|
||||
|
||||
private fun addCustomActionOnCard() {
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
|
||||
binding.ivCard.setOnClickListener {
|
||||
printScanResponseState()
|
||||
printWalletState()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun setupWarningsRecyclerView() {
|
||||
warningsAdapter = WarningMessagesAdapter()
|
||||
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||
with(binding) {
|
||||
rvWarningMessages.layoutManager = layoutManager
|
||||
rvWarningMessages.addItemDecoration(SpaceItemDecoration.all(16f))
|
||||
rvWarningMessages.adapter = warningsAdapter
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
override fun newStateOnMain(state: WalletState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
when {
|
||||
state.isMultiwalletAllowed && walletView !is MultiWalletView -> {
|
||||
walletView.onViewDestroy()
|
||||
walletView = MultiWalletView()
|
||||
walletView.changeWalletView(this, binding)
|
||||
}
|
||||
!state.isMultiwalletAllowed && walletView !is SingleWalletView -> {
|
||||
walletView.onViewDestroy()
|
||||
walletView = SingleWalletView()
|
||||
walletView.changeWalletView(this, binding)
|
||||
}
|
||||
else -> {} // we keep the same view unless we scan a card that requires a different view
|
||||
}
|
||||
totalBalanceWatcher.invoke(state)
|
||||
|
||||
walletView.swapInteractor = swapInteractor
|
||||
walletView.swapFeatureToggleManager = swapFeatureToggleManager
|
||||
|
||||
walletView.onNewState(state)
|
||||
|
||||
if (binding.toolbar.menu.findItem(R.id.details_menu) == null) {
|
||||
binding.toolbar.inflateMenu(R.menu.menu_wallet)
|
||||
}
|
||||
|
||||
setupCardImage(state)
|
||||
|
||||
showWarningsIfPresent(state.mainWarningsList)
|
||||
|
||||
setupPullToRefreshLayout(state)
|
||||
|
||||
binding.toolbar.setNavigationIcon(
|
||||
if (state.canSaveUserWallets) R.drawable.ic_wallet_24 else R.drawable.ic_tap_card_24,
|
||||
)
|
||||
|
||||
// showLearn2earnView()
|
||||
}
|
||||
|
||||
// private fun showLearn2earnView() {
|
||||
// val isShowing = learn2earnViewModel.uiState.mainScreenState.isVisible
|
||||
// if (!isShowing) return
|
||||
//
|
||||
// binding.composeLearnToEarnContainer.show(true) { binding.llWarnings.beginDelayedTransition() }
|
||||
// binding.composeLearnToEarnContainer.apply {
|
||||
// setViewCompositionStrategy(
|
||||
// strategy = ViewCompositionStrategy.DisposeOnLifecycleDestroyed(
|
||||
// lifecycle = [REDACTED_EMAIL],
|
||||
// ),
|
||||
// )
|
||||
// setContent {
|
||||
// TangemTheme {
|
||||
// Learn2earnMainPageScreen(learn2earnViewModel.uiState)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun setupPullToRefreshLayout(state: WalletState) {
|
||||
setupErrorPullToRefreshState(state)
|
||||
|
||||
binding.pullToRefreshLayout.isRefreshing = state.state == ProgressState.Refreshing
|
||||
|
||||
binding.pullToRefreshLayout.setOnRefreshListener {
|
||||
if (state.state != ProgressState.Loading && state.state != ProgressState.Refreshing) {
|
||||
refreshWalletData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupErrorPullToRefreshState(state: WalletState) {
|
||||
if (state.state == ProgressState.Error) {
|
||||
when (state.error) {
|
||||
ErrorType.NoInternetConnection -> {
|
||||
isNetworkConnectionError.value = true
|
||||
binding.pullToRefreshLayout.isRefreshing = false
|
||||
|
||||
(activity as? MainActivity)?.showSnackbar(
|
||||
text = R.string.wallet_notification_no_internet,
|
||||
buttonTitle = R.string.common_retry,
|
||||
)
|
||||
// because was added logic of autoupdate mainscreen data, remove retry
|
||||
// TODO("remove comment after release 4.6")
|
||||
// { store.dispatch(WalletAction.LoadData) }
|
||||
}
|
||||
else -> isNetworkConnectionError.value = false
|
||||
}
|
||||
} else {
|
||||
isNetworkConnectionError.value = false
|
||||
(activity as? MainActivity)?.dismissSnackbar()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshWalletData() {
|
||||
Analytics.send(Portfolio.Refreshed())
|
||||
store.dispatch(WalletAction.LoadData.Refresh)
|
||||
// learn2earnViewModel.onMainScreenRefreshed()
|
||||
}
|
||||
|
||||
private fun showWarningsIfPresent(warnings: List<WarningMessage>) {
|
||||
warningsAdapter.submitList(warnings)
|
||||
binding.rvWarningMessages.show(warnings.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun setupCardImage(state: WalletState) {
|
||||
binding.ivCard.load(state.cardImage?.artworkId) {
|
||||
scale(Scale.FIT)
|
||||
crossfade(enable = true)
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnNetworkStateChanging() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
networkConnectionManager.isOnlineFlow
|
||||
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
|
||||
.distinctUntilChanged()
|
||||
.collect { isOnline ->
|
||||
if (isOnline) {
|
||||
(activity as? MainActivity)?.dismissSnackbar()
|
||||
} else {
|
||||
isNetworkConnectionError.value = true
|
||||
binding.pullToRefreshLayout.isRefreshing = false
|
||||
(activity as? MainActivity)?.showSnackbar(
|
||||
text = R.string.wallet_notification_no_internet,
|
||||
buttonTitle = R.string.common_retry,
|
||||
)
|
||||
}
|
||||
if (isOnline && isNetworkConnectionError.value) {
|
||||
refreshWalletData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.details_menu -> {
|
||||
store.dispatch(GlobalAction.UpdateFeedbackInfo(store.state.walletState.walletManagers))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Details))
|
||||
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Deprecated in Java",
|
||||
replaceWith = ReplaceWith(
|
||||
"if (store.state.walletState.shouldShowDetails) inflater.inflate(R.menu.menu_wallet, menu)",
|
||||
"com.tangem.tap.store",
|
||||
"com.tangem.wallet.R",
|
||||
),
|
||||
)
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_wallet, menu)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.ui.analytics.WalletAnalyticsEventsMapper
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Kill me, please
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltViewModel
|
||||
internal class WalletViewModel @Inject constructor(
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : ViewModel(), StoreSubscriber<UserWalletsListManager?>, DefaultLifecycleObserver {
|
||||
private var observeWalletStoresUpdatesJob: Job? = null
|
||||
set(value) {
|
||||
field?.cancel()
|
||||
field = value
|
||||
}
|
||||
|
||||
private val walletAnalyticsEventsMapper = WalletAnalyticsEventsMapper()
|
||||
|
||||
init {
|
||||
subscribeToUserWalletsListManagerUpdates()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: UserWalletsListManager?) {
|
||||
// Restarting observing of wallet store updates when the manager changes
|
||||
if (state != null) {
|
||||
bootstrapSelectedWalletStoresChanges(state)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
launch()
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
if (scanResponse != null) {
|
||||
val currency = ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)
|
||||
val signInType = store.state.signInState.type
|
||||
if (currency != null && signInType != null) {
|
||||
analyticsEventHandler.send(
|
||||
Basic.SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = signInType,
|
||||
walletsCount = store.state.globalState.userWalletsListManager?.walletsCount.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
analyticsEventHandler.send(MainScreen.ScreenOpened())
|
||||
}
|
||||
|
||||
fun onBalanceLoaded(totalBalance: TotalFiatBalance?) {
|
||||
if (totalBalance != null) {
|
||||
walletAnalyticsEventsMapper.convert(totalBalance)?.let { balanceParam ->
|
||||
analyticsEventHandler.send(
|
||||
Basic.BalanceLoaded(
|
||||
balance = balanceParam,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launch() {
|
||||
val manager = store.state.globalState.userWalletsListManager
|
||||
if (manager != null) {
|
||||
bootstrapSelectedWalletStoresChanges(manager)
|
||||
}
|
||||
bootstrapShowSaveWalletIfNeeded()
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) {
|
||||
observeWalletStoresUpdatesJob = manager.selectedUserWallet
|
||||
.map { it.walletId }
|
||||
.flatMapLatest(walletStoresManager::get)
|
||||
.debounce { walletStores ->
|
||||
if (walletStores.isNotEmpty()) WALLET_STORES_DEBOUNCE_TIMEOUT else 0
|
||||
}
|
||||
.onEach { walletStores ->
|
||||
store.dispatchOnMain(WalletAction.WalletStoresChanged(walletStores))
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun bootstrapShowSaveWalletIfNeeded() {
|
||||
viewModelScope.launch {
|
||||
delay(timeMillis = 1_800)
|
||||
store.dispatchOnMain(WalletAction.ShowSaveWalletIfNeeded)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToUserWalletsListManagerUpdates() {
|
||||
store.subscribe(this) { appState ->
|
||||
appState
|
||||
.skip { old, new ->
|
||||
old.globalState.userWalletsListManager == new.globalState.userWalletsListManager
|
||||
}
|
||||
.select { it.globalState.userWalletsListManager }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val WALLET_STORES_DEBOUNCE_TIMEOUT = 100L
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.common.module.ModuleMessageConverter
|
||||
import com.tangem.tap.features.wallet.models.WalletWarning
|
||||
import com.tangem.tap.features.wallet.models.WalletWarningDescription
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
class WalletWarningConverter(
|
||||
private val context: Context,
|
||||
) : ModuleMessageConverter<WalletWarning, WalletWarningDescription> {
|
||||
|
||||
override fun convert(message: WalletWarning): WalletWarningDescription {
|
||||
// val warningMessage = when (message) {
|
||||
// is WalletWarning.ExistentialDeposit -> {
|
||||
// context.getString(
|
||||
// R.string.warning_existential_deposit_message,
|
||||
// message.currencyName,
|
||||
// message.edStringValueWithSymbol,
|
||||
// )
|
||||
// }
|
||||
// is WalletWarning.BalanceNotEnoughForFee -> {
|
||||
// context.getString(
|
||||
// R.string.token_details_send_blocked_fee_format,
|
||||
// message.currencyName,
|
||||
// message.blockchainFullName,
|
||||
// message.currencyName,
|
||||
// message.blockchainFullName,
|
||||
// message.blockchainSymbol,
|
||||
// )
|
||||
// }
|
||||
// is WalletWarning.TransactionInProgress -> {
|
||||
// context.getString(
|
||||
// R.string.token_details_send_blocked_tx_format,
|
||||
// message.currencyName,
|
||||
// )
|
||||
// }
|
||||
// is WalletWarning.Rent -> {
|
||||
// context.getString(
|
||||
// R.string.solana_rent_warning,
|
||||
// message.walletRent.rent,
|
||||
// message.walletRent.exemptionAmount,
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
return WalletWarningDescription(context.getString(R.string.common_warning), "")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ItemPendingTransactionBinding
|
||||
|
||||
class PendingTransactionsAdapter :
|
||||
ListAdapter<PendingTransaction, PendingTransactionsAdapter.TransactionsViewHolder>(DiffUtilCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionsViewHolder {
|
||||
val binding = ItemPendingTransactionBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false,
|
||||
)
|
||||
return TransactionsViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TransactionsViewHolder, position: Int) {
|
||||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<PendingTransaction>() {
|
||||
override fun areContentsTheSame(oldItem: PendingTransaction, newItem: PendingTransaction) = oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(oldItem: PendingTransaction, newItem: PendingTransaction) = oldItem == newItem
|
||||
}
|
||||
|
||||
class TransactionsViewHolder(val binding: ItemPendingTransactionBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(transaction: PendingTransaction) {
|
||||
if (transaction.type == PendingTransactionType.Unknown) {
|
||||
binding.root.hide()
|
||||
}
|
||||
|
||||
val transactionDescriptionRes = when (transaction.type) {
|
||||
PendingTransactionType.Incoming -> R.string.wallet_pending_tx_receiving
|
||||
PendingTransactionType.Outgoing -> R.string.wallet_pending_tx_sending
|
||||
PendingTransactionType.Unknown -> return
|
||||
}
|
||||
val transactionAddressRes = when (transaction.type) {
|
||||
PendingTransactionType.Incoming -> R.string.wallet_pending_tx_receiving_address_format
|
||||
PendingTransactionType.Outgoing -> R.string.wallet_pending_tx_sending_address_format
|
||||
PendingTransactionType.Unknown -> return
|
||||
}
|
||||
val image = when (transaction.type) {
|
||||
PendingTransactionType.Incoming -> R.drawable.ic_arrow_left
|
||||
PendingTransactionType.Outgoing -> R.drawable.ic_arrow_right_20
|
||||
PendingTransactionType.Unknown -> return
|
||||
}
|
||||
binding.tvPendingTransaction.text =
|
||||
binding.root.getString(transactionDescriptionRes).let { "$it " }
|
||||
|
||||
transaction.amountValueUi?.let { binding.tvPendingTransactionAmount.text = "$it " }
|
||||
binding.tvPendingTransactionCurrency.text = transaction.currency
|
||||
|
||||
if (transaction.address != null) {
|
||||
binding.tvPendingTransactionAddress.text =
|
||||
binding.root.getString(transactionAddressRes, transaction.address)
|
||||
}
|
||||
binding.ivPendingTransaction.setImageDrawable(binding.root.context.getDrawableCompat(image))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.tap.common.analytics.events.Portfolio
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.ui.images.load
|
||||
import com.tangem.tap.features.wallet.ui.utils.getFormattedCryptoAmount
|
||||
import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount
|
||||
import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatRate
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ItemCurrencyWalletBinding
|
||||
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
class WalletAdapter : ListAdapter<WalletDataModel, WalletAdapter.WalletsViewHolder>(DiffUtilCallback) {
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return currentList[position].currency.currencySymbol.hashCode().toLong()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder {
|
||||
val layout = ItemCurrencyWalletBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false,
|
||||
)
|
||||
return WalletsViewHolder(layout)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: WalletsViewHolder, position: Int) {
|
||||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<WalletDataModel>() {
|
||||
override fun areContentsTheSame(oldItem: WalletDataModel, newItem: WalletDataModel) = oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(oldItem: WalletDataModel, newItem: WalletDataModel) = oldItem == newItem
|
||||
}
|
||||
|
||||
class WalletsViewHolder(val binding: ItemCurrencyWalletBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(wallet: WalletDataModel) = with(binding) {
|
||||
val status = wallet.status
|
||||
val fiatCurrency = store.state.globalState.appCurrency
|
||||
|
||||
val statusMessage = when (status) {
|
||||
is WalletDataModel.TransactionInProgress -> {
|
||||
root.getString(R.string.wallet_balance_tx_in_progress)
|
||||
}
|
||||
is WalletDataModel.Unreachable -> {
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
// root.getString(R.string.wallet_balance_blockchain_unreachable)
|
||||
}
|
||||
is WalletDataModel.MissedDerivation -> {
|
||||
root.getString(R.string.wallet_balance_missing_derivation)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (status is WalletDataModel.Loading) {
|
||||
lContent.root.hide()
|
||||
lShimmer.root.veil()
|
||||
} else {
|
||||
lShimmer.root.unVeil()
|
||||
lContent.root.show()
|
||||
}
|
||||
|
||||
ivCurrency.load(
|
||||
currency = wallet.currency,
|
||||
derivationStyle = store.state.globalState.scanResponse
|
||||
?.derivationStyleProvider?.getDerivationStyle(),
|
||||
)
|
||||
|
||||
lContent.tvCurrency.text = wallet.currency.currencyName
|
||||
lContent.tvAmountFiat.text = wallet.getFormattedFiatAmount(fiatCurrency)
|
||||
lContent.tvAmount.text = wallet.getFormattedCryptoAmount()
|
||||
|
||||
lContent.tvStatus.isVisible = statusMessage != null
|
||||
// lContent.tvStatus.text = statusMessage
|
||||
|
||||
lContent.tvExchangeRate.isVisible = statusMessage == null
|
||||
lContent.tvExchangeRate.text = wallet.getFormattedFiatRate(
|
||||
fiatCurrency = fiatCurrency,
|
||||
noRateValue = root.getString(id = R.string.token_item_no_rate),
|
||||
)
|
||||
|
||||
if (wallet.walletAddresses != null) {
|
||||
cardWallet.setOnClickListener {
|
||||
Analytics.send(Portfolio.TokenTapped())
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet.currency))
|
||||
}
|
||||
} else {
|
||||
cardWallet.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tap.common.extensions.getColor
|
||||
import com.tangem.tap.features.wallet.models.WalletWarningDescription
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutWarningCardBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WalletDetailWarningMessagesAdapter :
|
||||
ListAdapter<WalletWarningDescription, WalletDetailsWarningMessageVH>(DiffUtilCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletDetailsWarningMessageVH {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = LayoutWarningCardBinding.inflate(inflater, parent, false)
|
||||
|
||||
return WalletDetailsWarningMessageVH(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: WalletDetailsWarningMessageVH, position: Int) {
|
||||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
private class DiffUtilCallback : DiffUtil.ItemCallback<WalletWarningDescription>() {
|
||||
override fun areContentsTheSame(oldItem: WalletWarningDescription, newItem: WalletWarningDescription) =
|
||||
oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(oldItem: WalletWarningDescription, newItem: WalletWarningDescription) =
|
||||
oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
class WalletDetailsWarningMessageVH(
|
||||
val binding: LayoutWarningCardBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(warning: WalletWarningDescription) {
|
||||
binding.warningCard.setCardBackgroundColor(binding.root.getColor(R.color.darkGray2))
|
||||
setText(warning)
|
||||
}
|
||||
|
||||
private fun setText(warning: WalletWarningDescription) = with(binding.warningContentContainer) {
|
||||
tvTitle.text = warning.title
|
||||
tvMessage.text = warning.message
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.analytics
|
||||
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.extensions.isGreaterThan
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
class WalletAnalyticsEventsMapper : Converter<TotalFiatBalance, AnalyticsParam.CardBalanceState?> {
|
||||
|
||||
override fun convert(value: TotalFiatBalance): AnalyticsParam.CardBalanceState? {
|
||||
return when (value) {
|
||||
is TotalFiatBalance.Failed -> AnalyticsParam.CardBalanceState.BlockchainError
|
||||
is TotalFiatBalance.Loaded -> when {
|
||||
value.isWarning -> AnalyticsParam.CardBalanceState.CustomToken
|
||||
value.amount.isGreaterThan(BigDecimal.ZERO) -> AnalyticsParam.CardBalanceState.Full
|
||||
else -> AnalyticsParam.CardBalanceState.Empty
|
||||
}
|
||||
is TotalFiatBalance.Loading -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.recyclerview.widget.*
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.DialogWalletSendBinding
|
||||
import com.tangem.wallet.databinding.ItemWalletAmountToSendBinding
|
||||
|
||||
class AmountToSendBottomSheetDialog(
|
||||
context: Context,
|
||||
private val dialog: WalletDialog.SelectAmountToSendDialog,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
var binding: DialogWalletSendBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DialogWalletSendBinding.inflate(LayoutInflater.from(context))
|
||||
setContentView(binding!!.root)
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
|
||||
setOnDismissListener {
|
||||
binding = null
|
||||
store.dispatch(WalletAction.DialogAction.Hide)
|
||||
}
|
||||
|
||||
binding!!.rvAmountsToSend.layoutManager = LinearLayoutManager(context)
|
||||
val dividerItemDecoration = DividerItemDecoration(
|
||||
ContextThemeWrapper(binding!!.root.context, R.style.AppTheme),
|
||||
DividerItemDecoration.VERTICAL,
|
||||
)
|
||||
binding!!.rvAmountsToSend.addItemDecoration(dividerItemDecoration)
|
||||
|
||||
val viewAdapter = ChooseAmountAdapter()
|
||||
binding!!.rvAmountsToSend.adapter = viewAdapter
|
||||
|
||||
viewAdapter.submitList(dialog.amounts)
|
||||
}
|
||||
}
|
||||
|
||||
private class ChooseAmountAdapter : ListAdapter<Amount, ChooseAmountAdapter.AmountViewHolder>(DiffUtilCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AmountViewHolder {
|
||||
val binding = ItemWalletAmountToSendBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false,
|
||||
)
|
||||
return AmountViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: AmountViewHolder, position: Int) {
|
||||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<Amount>() {
|
||||
override fun areContentsTheSame(oldItem: Amount, newItem: Amount) =
|
||||
oldItem.currencySymbol == newItem.currencySymbol
|
||||
|
||||
override fun areItemsTheSame(oldItem: Amount, newItem: Amount) = oldItem == newItem
|
||||
}
|
||||
|
||||
class AmountViewHolder(val binding: ItemWalletAmountToSendBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(amount: Amount) = with(binding) {
|
||||
tvCurrencySymbol.text = amount.currencySymbol
|
||||
tvAmount.text = amount.value?.toFormattedString(amount.decimals)
|
||||
root.setOnClickListener {
|
||||
store.dispatch(WalletAction.DialogAction.Hide)
|
||||
store.dispatch(WalletAction.Send(amount))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.databinding.DialogWalletTradeBinding
|
||||
|
||||
class ChooseTradeActionBottomSheetDialog(
|
||||
context: Context,
|
||||
private val dialogData: WalletDialog.ChooseTradeActionDialog,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
var binding: DialogWalletTradeBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DialogWalletTradeBinding.inflate(LayoutInflater.from(context))
|
||||
setContentView(binding!!.root)
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
|
||||
this.setOnDismissListener {
|
||||
binding = null
|
||||
store.dispatch(WalletAction.DialogAction.Hide)
|
||||
}
|
||||
|
||||
binding?.let {
|
||||
with(it) {
|
||||
dialogBtnBuy.show(dialogData.buyAllowed)
|
||||
dialogBtnSell.show(dialogData.sellAllowed)
|
||||
dialogBtnSwap.show(dialogData.swapAllowed)
|
||||
|
||||
dialogBtnBuy.setOnClickListener {
|
||||
dismiss()
|
||||
store.dispatch(TradeCryptoAction.Buy())
|
||||
}
|
||||
dialogBtnSell.setOnClickListener {
|
||||
dismiss()
|
||||
store.dispatch(TradeCryptoAction.Sell)
|
||||
}
|
||||
dialogBtnSwap.setOnClickListener {
|
||||
dismiss()
|
||||
store.dispatch(TradeCryptoAction.Swap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.databinding.DialogRussiansCardholdersWarningBinding
|
||||
|
||||
class RussianCardholdersWarningBottomSheetDialog(
|
||||
context: Context,
|
||||
private val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data?,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
private var binding: DialogRussiansCardholdersWarningBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Analytics.send(Token.Topup.P2PScreenOpened())
|
||||
binding = DialogRussiansCardholdersWarningBinding
|
||||
.inflate(LayoutInflater.from(context))
|
||||
.also { setContentView(it.root) }
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
setOnDismissListener {
|
||||
binding = null
|
||||
store.dispatchDialogHide()
|
||||
}
|
||||
|
||||
binding?.btnYes?.setOnClickListener {
|
||||
if (dialogData != null) {
|
||||
store.dispatchOpenUrl(dialogData.topUpUrl)
|
||||
Analytics.send(Token.Topup.ScreenOpened())
|
||||
} else {
|
||||
store.dispatch(TradeCryptoAction.Buy(checkUserLocation = false))
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
binding?.btnNo?.setOnClickListener {
|
||||
store.dispatch(NavigationAction.OpenUrl(INSTRUCTION_URL))
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val INSTRUCTION_URL = "https://tangem.com/howtobuy.html"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.feedback.ScanFailsEmail
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object ScanFailsDialog {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(context.getString(R.string.common_warning))
|
||||
setMessage(R.string.alert_troubleshooting_scan_card_title)
|
||||
setPositiveButton(R.string.alert_button_request_support) { _, _ ->
|
||||
Analytics.send(IntroductionProcess.ButtonRequestSupport())
|
||||
store.dispatch(GlobalAction.SendEmail(ScanFailsEmail()))
|
||||
}
|
||||
setNeutralButton(R.string.common_cancel) { _, _ -> }
|
||||
setOnDismissListener { store.dispatchDialogHide() }
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
// TODO: Delete with WalletFeatureToggles
|
||||
@Deprecated(message = "Used only in old wallet screen")
|
||||
object SignedHashesWarningDialog {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
// setTitle(context.getString(R.string.warning_important_security_info, "\u26A0"))
|
||||
// setMessage(R.string.alert_signed_hashes_message)
|
||||
setPositiveButton(R.string.common_understand) { _, _ ->
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
store.dispatch(
|
||||
GlobalAction.HideWarningMessage(WarningMessagesManager.signedHashesMultiWalletWarning),
|
||||
)
|
||||
}
|
||||
setNegativeButton(R.string.common_cancel) { _, _ -> }
|
||||
setOnDismissListener {
|
||||
store.dispatch(WalletAction.DialogAction.Hide)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object SimpleOkDialog {
|
||||
|
||||
fun create(dialog: AppDialog.SimpleOkDialog, context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(dialog.header)
|
||||
setMessage(dialog.message)
|
||||
setPositiveButton(R.string.common_ok) { _, _ -> }
|
||||
setOnDismissListener {
|
||||
store.dispatchDialogHide()
|
||||
dialog.onOk?.invoke()
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
|
||||
fun create(dialog: AppDialog.SimpleOkDialogRes, context: Context): AlertDialog {
|
||||
val message = if (dialog.args.isEmpty()) {
|
||||
context.getString(dialog.messageId)
|
||||
} else {
|
||||
context.getString(dialog.messageId, *dialog.args.toTypedArray())
|
||||
}
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(dialog.headerId))
|
||||
setMessage(message)
|
||||
setPositiveButton(R.string.common_ok) { _, _ -> }
|
||||
setOnDismissListener {
|
||||
store.dispatchDialogHide()
|
||||
dialog.onOk?.invoke()
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
|
||||
fun create(dialog: AppDialog.SimpleOkErrorDialog, context: Context): AlertDialog = create(
|
||||
dialog = AppDialog.SimpleOkDialog(
|
||||
header = context.getString(R.string.common_error),
|
||||
message = dialog.message,
|
||||
onOk = dialog.onOk,
|
||||
),
|
||||
context = context,
|
||||
)
|
||||
|
||||
fun create(dialog: AppDialog.SimpleOkWarningDialog, context: Context): AlertDialog = create(
|
||||
dialog = AppDialog.SimpleOkDialog(
|
||||
header = context.getString(R.string.common_warning),
|
||||
message = dialog.message,
|
||||
onOk = dialog.onOk,
|
||||
),
|
||||
context = context,
|
||||
)
|
||||
|
||||
fun create(dialog: AppDialog.OkCancelDialogRes, context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(context.getString(dialog.headerId))
|
||||
setMessage(dialog.messageId)
|
||||
setPositiveButton(dialog.okButton.title) { _, _ -> dialog.okButton.action?.invoke() }
|
||||
setNegativeButton(dialog.cancelButton.title) { _, _ -> dialog.cancelButton.action?.invoke() }
|
||||
setOnDismissListener {
|
||||
store.dispatchDialogHide()
|
||||
dialog.cancelButton.action?.invoke()
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.images
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
import coil.imageLoader
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.IconsUtil
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.domain.common.extensions.toCoinId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.tap.common.extensions.getColor
|
||||
import com.tangem.tap.common.extensions.getTextColor
|
||||
import com.tangem.tap.domain.extensions.getCustomIconUrl
|
||||
import com.tangem.tap.domain.tokens.getIconUrl
|
||||
import com.tangem.wallet.R
|
||||
|
||||
private const val QCX = "QCX"
|
||||
private const val VOYR = "VOYRME"
|
||||
|
||||
class CurrencyIconRequest(
|
||||
private val currencyImageView: ImageFilterView,
|
||||
private val currencyTextView: TextView?,
|
||||
private val token: Token?,
|
||||
private val blockchain: Blockchain,
|
||||
private val getLocalImage: Boolean = false,
|
||||
) {
|
||||
fun load() {
|
||||
when {
|
||||
token == null && blockchain.isTestnet() -> loadTestnetBlockchainIcon()
|
||||
token == null -> loadBlockchainIcon()
|
||||
blockchain.isTestnet() -> loadTestnetTokenIcon()
|
||||
else -> loadTokenIcon()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadBlockchainIcon() {
|
||||
loadBlockchainIconBase(
|
||||
onStart = {
|
||||
currencyImageView.colorFilter = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadTestnetBlockchainIcon() {
|
||||
loadBlockchainIconBase(
|
||||
onStart = {
|
||||
currencyImageView.saturation = 0f
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadTokenIcon() {
|
||||
loadTokenIconBase(
|
||||
onStart = {
|
||||
currencyImageView.setColorFilter(it.getColor())
|
||||
},
|
||||
onSuccess = {
|
||||
currencyImageView.colorFilter = null
|
||||
},
|
||||
onError = {
|
||||
currencyImageView.setColorFilter(it.getColor())
|
||||
currencyTextView?.setTextColor(it.getTextColor())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadTestnetTokenIcon() {
|
||||
loadTokenIconBase(
|
||||
onStart = {
|
||||
currencyImageView.saturation = 0f
|
||||
},
|
||||
onError = {
|
||||
currencyImageView.saturation = 0f
|
||||
currencyImageView.setColorFilter(it.getColor(true))
|
||||
currencyTextView?.setTextColor(it.getTextColor(true))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun loadBlockchainIconBase(
|
||||
crossinline onStart: (Blockchain) -> Unit = {},
|
||||
crossinline onSuccess: (Blockchain) -> Unit = {},
|
||||
crossinline onError: (Blockchain) -> Unit = {},
|
||||
) {
|
||||
currencyImageView.loadIcon(
|
||||
data = getBlockchainIconData(blockchain),
|
||||
placeholderRes = getActiveIconRes(blockchain.id),
|
||||
onStart = { onStart(blockchain) },
|
||||
onSuccess = { onSuccess(blockchain) },
|
||||
onError = { onError(blockchain) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun getBlockchainIconData(blockchain: Blockchain): Any {
|
||||
return if (getLocalImage) {
|
||||
when (blockchain) {
|
||||
Blockchain.TerraV1, Blockchain.TerraV2 -> getActiveIconRes(blockchain.toCoinId())
|
||||
else -> getActiveIconRes(blockchain.id)
|
||||
}
|
||||
} else {
|
||||
when (blockchain) {
|
||||
Blockchain.TerraV1, Blockchain.TerraV2 -> getIconUrl(blockchain.toCoinId())
|
||||
else -> getIconUrl(blockchain.toNetworkId())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun loadTokenIconBase(
|
||||
crossinline onStart: (Token) -> Unit = {},
|
||||
crossinline onSuccess: (Token) -> Unit = {},
|
||||
crossinline onError: (Token) -> Unit = {},
|
||||
) {
|
||||
if (token == null) return
|
||||
|
||||
currencyImageView.loadIcon(
|
||||
data = getTokenIcon(token, blockchain),
|
||||
placeholderRes = R.drawable.shape_circle,
|
||||
onStart = {
|
||||
currencyTextView?.text = token.name.take(1)
|
||||
currencyTextView?.setTextColor(token.getTextColor())
|
||||
onStart(token)
|
||||
},
|
||||
onSuccess = {
|
||||
currencyTextView?.text = null
|
||||
onSuccess(token)
|
||||
},
|
||||
onError = {
|
||||
// for some reason the onStart doesn't call if an error occurs
|
||||
currencyTextView?.text = token.name.take(1)
|
||||
onError(token)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun ImageView.loadIcon(
|
||||
data: Any?,
|
||||
placeholderRes: Int,
|
||||
crossinline onStart: () -> Unit = {},
|
||||
crossinline onSuccess: () -> Unit = {},
|
||||
crossinline onError: () -> Unit = {},
|
||||
) {
|
||||
ImageRequest.Builder(context)
|
||||
.data(data)
|
||||
.placeholder(placeholderRes)
|
||||
.error(placeholderRes)
|
||||
.fallback(placeholderRes)
|
||||
.listener(
|
||||
onStart = { onStart() },
|
||||
onSuccess = { _, _ -> onSuccess() },
|
||||
onError = { _, _ -> onError() },
|
||||
)
|
||||
.target(imageView = this)
|
||||
.build()
|
||||
.also(context.imageLoader::enqueue)
|
||||
}
|
||||
|
||||
private fun getTokenIcon(token: Token, blockchain: Blockchain): Any? {
|
||||
return when (token.symbol) {
|
||||
QCX -> R.drawable.ic_qcx
|
||||
VOYR -> R.drawable.ic_voyr
|
||||
else -> {
|
||||
token.id?.let(::getIconUrl)
|
||||
?: token.getCustomIconUrl()
|
||||
?: IconsUtil.getTokenIconUri(blockchain, token)
|
||||
?.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.images
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||
import com.tangem.sdk.extensions.dpToPx
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.wallet.databinding.ViewCurrencyIconBinding
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class CurrencyIconView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
private val binding = ViewCurrencyIconBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
this,
|
||||
)
|
||||
|
||||
val currencyImageView: ImageFilterView
|
||||
get() = binding.ivCurrency
|
||||
|
||||
val currencyTextView: TextView
|
||||
get() = binding.tvTokenLetter
|
||||
|
||||
val blockchainBadge: ImageFilterView
|
||||
get() = binding.ivBlockchainBadge
|
||||
|
||||
var isBlockchainBadgeVisible: Boolean
|
||||
get() = binding.ivBlockchainBadge.isVisible
|
||||
set(value) = binding.ivBlockchainBadge::isVisible.set(value)
|
||||
|
||||
var isCustomCurrencyBadgeVisible: Boolean
|
||||
get() = binding.customBadge.isVisible
|
||||
set(value) = binding.customBadge::isVisible.set(value)
|
||||
|
||||
init {
|
||||
minWidth = dpToPx(48f).roundToInt()
|
||||
minHeight = dpToPx(48f).roundToInt()
|
||||
}
|
||||
}
|
||||
|
||||
fun CurrencyIconView.load(currency: Currency, derivationStyle: DerivationStyle?) {
|
||||
isCustomCurrencyBadgeVisible = currency.isCustomCurrency(derivationStyle)
|
||||
|
||||
CurrencyIconRequest(
|
||||
currencyImageView = currencyImageView,
|
||||
currencyTextView = currencyTextView,
|
||||
token = (currency as? Currency.Token)?.token,
|
||||
blockchain = currency.blockchain,
|
||||
).load()
|
||||
|
||||
if (currency.isToken()) {
|
||||
// load a blockchain icon into the blockchain badge
|
||||
isBlockchainBadgeVisible = true
|
||||
|
||||
CurrencyIconRequest(
|
||||
currencyImageView = blockchainBadge,
|
||||
currencyTextView = null,
|
||||
token = null,
|
||||
blockchain = currency.blockchain,
|
||||
getLocalImage = true,
|
||||
).load()
|
||||
} else {
|
||||
isBlockchainBadgeVisible = false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.test
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.tap.common.TestAction
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object TestWallet {
|
||||
fun solanaRentExemptWarning(): List<TestAction> {
|
||||
val checker = SolanaRentWarningActionEmitter()
|
||||
return listOf(
|
||||
"BALANCE = 0.0" to { checker.setZeroBalance() },
|
||||
"BALANCE < 0.00089088" to { checker.setLessThanRentExempt() },
|
||||
"BALANCE > 0.00089088" to { checker.setMoreThanRentExempt() },
|
||||
"BALANCE = 0.00089087" to { checker.setLessThanRentExemptByOne() },
|
||||
"BALANCE = 0.00089088 (rent exempt)" to { checker.setForRentExemptBarrier() },
|
||||
"BALANCE = 0.00089089" to { checker.setMoreThanRentExemptByOne() },
|
||||
)
|
||||
}
|
||||
|
||||
fun getBlockchainBalanceActions(blockchainNetwork: BlockchainNetwork): List<TestAction> {
|
||||
return getBlockchainBalanceActions(
|
||||
getWalletManager(blockchainNetwork),
|
||||
blockchainNetwork.blockchain.decimals(),
|
||||
)
|
||||
}
|
||||
|
||||
fun getTokenBalanceAction(blockchainNetwork: BlockchainNetwork, token: Token): List<TestAction> {
|
||||
return getTokenBalanceAction(getWalletManager(blockchainNetwork), token)
|
||||
}
|
||||
|
||||
fun getBlockchainBalanceActions(walletManager: WalletManager?, decimals: Int): List<TestAction> {
|
||||
val minValue = BigDecimal.ONE.movePointLeft(decimals)
|
||||
val averageValue = BigDecimal.ONE.movePointRight(decimals)
|
||||
.divide(BigDecimal(2)).movePointLeft(decimals)
|
||||
val maxValue = BigDecimal(2).pow(32)
|
||||
|
||||
return listOf(
|
||||
"0.0" to { setBalance(walletManager, BigDecimal.ZERO) },
|
||||
"Min value" to { setBalance(walletManager, minValue) },
|
||||
"Average value" to { setBalance(walletManager, averageValue) },
|
||||
"Max value" to { setBalance(walletManager, maxValue) },
|
||||
"1234567890123.01234567890123" to { setBalance(walletManager, BigDecimal.ZERO) },
|
||||
)
|
||||
}
|
||||
|
||||
fun getTokenBalanceAction(walletManager: WalletManager?, token: Token): List<TestAction> {
|
||||
val minValue = BigDecimal.ONE.movePointLeft(token.decimals)
|
||||
val averageValue = BigDecimal.ONE.movePointRight(token.decimals)
|
||||
.divide(BigDecimal(2)).movePointLeft(token.decimals)
|
||||
val maxValue = BigDecimal(2).pow(32)
|
||||
|
||||
return listOf(
|
||||
"0.0" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) },
|
||||
"Min value" to { setTokenBalance(walletManager, minValue, token) },
|
||||
"Average value" to { setTokenBalance(walletManager, averageValue, token) },
|
||||
"Max value" to { setTokenBalance(walletManager, maxValue, token) },
|
||||
"1234567890123.01234567890123" to { setTokenBalance(walletManager, BigDecimal.ZERO, token) },
|
||||
)
|
||||
}
|
||||
|
||||
fun setBalance(walletManager: WalletManager?, value: BigDecimal) {
|
||||
val manager = walletManager.guard {
|
||||
store.dispatchDebugErrorNotification("WalletManager not found")
|
||||
return
|
||||
}
|
||||
val amount = manager.wallet.amounts[AmountType.Coin] ?: Amount(manager.wallet.blockchain)
|
||||
setBalance(manager, amount, value)
|
||||
}
|
||||
|
||||
private fun setTokenBalance(walletManager: WalletManager?, value: BigDecimal, token: Token? = null) {
|
||||
val manager = walletManager.guard {
|
||||
store.dispatchDebugErrorNotification("WalletManager not found")
|
||||
return
|
||||
}
|
||||
val token = token.guard {
|
||||
store.dispatchDebugErrorNotification("Token not found")
|
||||
return
|
||||
}
|
||||
|
||||
val amount = manager.wallet.amounts[AmountType.Token(token)] ?: Amount(token)
|
||||
setBalance(manager, amount, value)
|
||||
}
|
||||
|
||||
private fun setBalance(walletManager: WalletManager, amount: Amount, value: BigDecimal) {
|
||||
TestActions.testAmountInjectionForWalletManagerEnabled = true
|
||||
walletManager.wallet.setAmount(amount.copy(value = value))
|
||||
store.dispatch(WalletAction.LoadData)
|
||||
}
|
||||
|
||||
private fun getWalletManager(blockchainNetwork: BlockchainNetwork): WalletManager? {
|
||||
return store.state.walletState.getWalletManager(blockchainNetwork)
|
||||
}
|
||||
}
|
||||
|
||||
private class SolanaRentWarningActionEmitter {
|
||||
|
||||
private val zero = BigDecimal.ZERO
|
||||
private val one = BigDecimal(0.00000001)
|
||||
private val rentExemptBarrier = BigDecimal(0.00089088)
|
||||
private val lessThanRentExemptByOne = rentExemptBarrier.minus(one)
|
||||
private val moreThanRentExemptByOne = rentExemptBarrier.plus(one)
|
||||
private val moreThanRentExempt = rentExemptBarrier.plus(Random.nextDouble().toBigDecimal())
|
||||
private val lessThanRentExempt = rentExemptBarrier
|
||||
.minus(Random.nextDouble(0.0, rentExemptBarrier.minus(one).toDouble()).toBigDecimal())
|
||||
|
||||
fun setZeroBalance() {
|
||||
setBalance(zero)
|
||||
}
|
||||
|
||||
fun setForRentExemptBarrier() {
|
||||
setBalance(rentExemptBarrier)
|
||||
}
|
||||
|
||||
fun setLessThanRentExemptByOne() {
|
||||
setBalance(lessThanRentExemptByOne)
|
||||
}
|
||||
|
||||
fun setMoreThanRentExemptByOne() {
|
||||
setBalance(moreThanRentExemptByOne)
|
||||
}
|
||||
|
||||
fun setMoreThanRentExempt() {
|
||||
setBalance(moreThanRentExempt)
|
||||
}
|
||||
|
||||
fun setLessThanRentExempt() {
|
||||
setBalance(lessThanRentExempt)
|
||||
}
|
||||
|
||||
private fun setBalance(value: BigDecimal) {
|
||||
val amount = Amount(getBlockchainNetwork().blockchain).copy(value = value)
|
||||
getWalletManager().apply {
|
||||
TestActions.testAmountInjectionForWalletManagerEnabled = true
|
||||
wallet.setAmount(amount)
|
||||
}
|
||||
store.dispatch(WalletAction.LoadData)
|
||||
}
|
||||
|
||||
private fun getWalletManager(): WalletManager {
|
||||
return store.state.walletState.getWalletManager(getBlockchainNetwork())!!
|
||||
}
|
||||
|
||||
private fun getBlockchainNetwork(): BlockchainNetwork {
|
||||
val currency = store.state.walletState.selectedWalletData!!.currency
|
||||
return BlockchainNetwork(currency.blockchain, currency.derivationPath, listOf())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.toFiatRateString
|
||||
import com.tangem.tap.common.extensions.toFiatValue
|
||||
import com.tangem.tap.common.extensions.toFormattedCryptoCurrencyString
|
||||
import com.tangem.tap.common.extensions.toFormattedFiatValue
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.models.WalletWarning
|
||||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal fun WalletDataModel.mainButton(blockchainAmount: BigDecimal): WalletMainButton = WalletMainButton.SendButton(
|
||||
enabled = !isEmptyAmount &&
|
||||
hasPendingTransactions() &&
|
||||
!blockchainAmount.isZero(),
|
||||
)
|
||||
|
||||
internal fun WalletDataModel.hasPendingTransactions(): Boolean {
|
||||
// for now check pending ongoing only just for BTC, later test and add other utxo networks
|
||||
val isBitcoinBlockchain =
|
||||
currency.blockchain == Blockchain.Bitcoin || currency.blockchain == Blockchain.BitcoinTestnet
|
||||
if (currency.isBlockchain() && isBitcoinBlockchain) {
|
||||
val outgoingTransactions = status.pendingTransactions.filter {
|
||||
it.type == PendingTransactionType.Outgoing
|
||||
}
|
||||
return outgoingTransactions.isEmpty()
|
||||
}
|
||||
return status.pendingTransactions.isEmpty()
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.getFormattedCryptoAmount(): String {
|
||||
return status.amount.toFormattedCryptoCurrencyString(
|
||||
decimals = currency.decimals,
|
||||
currency = currency.currencySymbol,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.getFormattedFiatAmount(
|
||||
fiatCurrency: FiatCurrency,
|
||||
unknownAmountSign: String = UNKNOWN_AMOUNT_SIGN,
|
||||
): String {
|
||||
return this.fiatRate?.let { status.amount.toFiatValue(it) }
|
||||
?.takeIf { !status.isErrorStatus }
|
||||
?.toFormattedFiatValue(fiatCurrencyName = fiatCurrency.symbol, fiatCode = fiatCurrency.code)
|
||||
?: unknownAmountSign
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.getFormattedFiatRate(fiatCurrency: FiatCurrency, noRateValue: String): String {
|
||||
return fiatRate?.toFiatRateString(fiatCurrency.symbol, fiatCurrency.code)
|
||||
?: noRateValue
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.isAvailableToBuy(exchangeManager: CurrencyExchangeManager): Boolean {
|
||||
return exchangeManager.availableForBuy(currency)
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.isAvailableToSell(exchangeManager: CurrencyExchangeManager): Boolean {
|
||||
return exchangeManager.availableForSell(currency)
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.isAvailableToSwap(
|
||||
swapFeatureToggleManager: SwapFeatureToggleManager,
|
||||
swapInteractor: SwapInteractor,
|
||||
isSingleWallet: Boolean,
|
||||
): Boolean {
|
||||
if (isSingleWallet) {
|
||||
return false
|
||||
}
|
||||
if (currency.blockchain.id == Blockchain.Optimism.id && !swapFeatureToggleManager.isOptimismSwapEnabled) {
|
||||
return false
|
||||
}
|
||||
return swapInteractor.isAvailableToSwap(currency.blockchain.toNetworkId()) &&
|
||||
!currency.isCustomCurrency(null)
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.getAvailableActions(
|
||||
swapInteractor: SwapInteractor,
|
||||
exchangeManager: CurrencyExchangeManager,
|
||||
swapFeatureToggleManager: SwapFeatureToggleManager,
|
||||
isSingleWallet: Boolean,
|
||||
): Set<CurrencyAction> {
|
||||
return setOfNotNull(
|
||||
if (isAvailableToBuy(exchangeManager)) CurrencyAction.Buy else null,
|
||||
if (isAvailableToSell(exchangeManager)) CurrencyAction.Sell else null,
|
||||
if (isAvailableToSwap(swapFeatureToggleManager, swapInteractor, isSingleWallet)) CurrencyAction.Swap else null,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.shouldShowMultipleAddress(): Boolean {
|
||||
val listOfAddresses = walletAddresses?.list.orEmpty()
|
||||
return listOfAddresses.size > 1
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.assembleWarnings(
|
||||
blockchainAmount: BigDecimal,
|
||||
blockchainWalletRent: WalletStoreModel.WalletRent?,
|
||||
): List<WalletWarning> {
|
||||
val walletWarnings = mutableListOf<WalletWarning>()
|
||||
assembleNonTypedWarnings(walletWarnings, blockchainWalletRent)
|
||||
assembleBlockchainWarnings(walletWarnings)
|
||||
assembleTokenWarnings(walletWarnings, blockchainAmount)
|
||||
|
||||
return walletWarnings.sortedBy { it.showingPosition }
|
||||
}
|
||||
|
||||
private fun WalletDataModel.assembleNonTypedWarnings(
|
||||
walletWarnings: MutableList<WalletWarning>,
|
||||
walletRent: WalletStoreModel.WalletRent?,
|
||||
) {
|
||||
if (this.status is WalletDataModel.SameCurrencyTransactionInProgress) {
|
||||
walletWarnings.add(WalletWarning.TransactionInProgress(currency.currencyName))
|
||||
}
|
||||
if (walletRent != null) {
|
||||
walletWarnings.add(WalletWarning.Rent(walletRent))
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletDataModel.assembleBlockchainWarnings(walletWarnings: MutableList<WalletWarning>) {
|
||||
with(currency) {
|
||||
if (!isBlockchain()) return
|
||||
|
||||
if (existentialDeposit != null) {
|
||||
val warning = WalletWarning.ExistentialDeposit(
|
||||
currencyName = currencyName,
|
||||
edStringValueWithSymbol = "${existentialDeposit.toPlainString()} $currencySymbol",
|
||||
)
|
||||
walletWarnings.add(warning)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletDataModel.assembleTokenWarnings(
|
||||
walletWarnings: MutableList<WalletWarning>,
|
||||
blockchainAmount: BigDecimal,
|
||||
) {
|
||||
if (!currency.isToken()) return
|
||||
|
||||
if (!this.isEmptyAmount && blockchainAmount.isZero()) {
|
||||
walletWarnings.add(
|
||||
WalletWarning.BalanceNotEnoughForFee(
|
||||
currencyName = currency.currencyName,
|
||||
blockchainFullName = currency.blockchain.fullName,
|
||||
blockchainSymbol = currency.blockchain.currency,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val WalletDataModel.isEmptyAmount: Boolean
|
||||
get() = this.status.amount.isZero()
|
||||
|
||||
enum class CurrencyAction {
|
||||
Buy, Sell, Swap
|
||||
}
|
||||
|
|
@ -1,355 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.AbstractComposeView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.wallet.R
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.text.NumberFormat
|
||||
import java.util.Currency
|
||||
import java.util.Locale
|
||||
|
||||
internal class TotalBalanceCard @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : AbstractComposeView(context, attrs, defStyleAttr) {
|
||||
private var state by mutableStateOf<TotalBalanceCardState>(TotalBalanceCardState.Empty)
|
||||
|
||||
var status: TotalFiatBalance? = null
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
updateState(value, fiatCurrency, onChangeFiatCurrencyClick)
|
||||
}
|
||||
|
||||
var onChangeFiatCurrencyClick: () -> Unit = { /* no-op */ }
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
updateState(status, fiatCurrency, value)
|
||||
}
|
||||
|
||||
var fiatCurrency: FiatCurrency = FiatCurrency.Default
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
updateState(status, value, onChangeFiatCurrencyClick)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
TangemTheme {
|
||||
TotalBalanceCardContent(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAccessibilityClassName(): CharSequence {
|
||||
return javaClass.name
|
||||
}
|
||||
|
||||
private fun updateState(status: TotalFiatBalance?, fiatCurrency: FiatCurrency, onChangeCurrencyClick: () -> Unit) {
|
||||
state = when (status) {
|
||||
null -> TotalBalanceCardState.Empty
|
||||
is TotalFiatBalance.Failed -> TotalBalanceCardState.Failure(
|
||||
fiatCurrency = fiatCurrency,
|
||||
onChangeFiatCurrencyClick = onChangeCurrencyClick,
|
||||
)
|
||||
is TotalFiatBalance.Loading -> TotalBalanceCardState.Loading(
|
||||
fiatCurrency = fiatCurrency,
|
||||
onChangeFiatCurrencyClick = onChangeCurrencyClick,
|
||||
)
|
||||
is TotalFiatBalance.Loaded -> TotalBalanceCardState.Success(
|
||||
amount = status.amount,
|
||||
showWarning = status.isWarning,
|
||||
onChangeFiatCurrencyClick = onChangeCurrencyClick,
|
||||
fiatCurrency = fiatCurrency,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TotalBalanceCardContent(state: TotalBalanceCardState, modifier: Modifier = Modifier) {
|
||||
TotalBalanceCardScaffold(
|
||||
modifier = modifier,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.main_page_balance),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
},
|
||||
amount = {
|
||||
when (state) {
|
||||
is TotalBalanceCardState.Empty,
|
||||
is TotalBalanceCardState.Loading,
|
||||
-> LoadingAmount()
|
||||
is TotalBalanceCardState.Failure,
|
||||
is TotalBalanceCardState.Success,
|
||||
-> LoadedAmount(
|
||||
amount = buildAmountString(
|
||||
amount = state.amount,
|
||||
fiatCurrency = state.fiatCurrency,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
currencySelector = {
|
||||
if (state !is TotalBalanceCardState.Empty) {
|
||||
SelectorButton(
|
||||
text = state.fiatCurrency.code,
|
||||
onClick = state.onChangeFiatCurrencyClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
warningText = {
|
||||
AnimatedVisibility(visible = state.showWarning) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.main_processing_full_amount),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.attention,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TotalBalanceCardScaffold(
|
||||
title: @Composable () -> Unit,
|
||||
amount: @Composable () -> Unit,
|
||||
currencySelector: @Composable () -> Unit,
|
||||
warningText: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
amountWeight: Float = 0.8f,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = TangemTheme.shapes.roundedCornersMedium,
|
||||
color = TangemTheme.colors.background.plain,
|
||||
elevation = TangemTheme.dimens.elevation1,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
SpacerW16()
|
||||
Column(
|
||||
modifier = Modifier.weight(amountWeight),
|
||||
) {
|
||||
SpacerH12()
|
||||
title()
|
||||
SpacerH4()
|
||||
amount()
|
||||
}
|
||||
currencySelector()
|
||||
SpacerW4()
|
||||
}
|
||||
SpacerH4()
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
warningText()
|
||||
}
|
||||
SpacerH12()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingAmount(modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier.shimmer()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(TangemTheme.dimens.size116)
|
||||
.height(TangemTheme.dimens.size32)
|
||||
.background(
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersSmall2,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadedAmount(amount: AnnotatedString, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
Text(
|
||||
text = amount,
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun buildAmountString(amount: BigDecimal?, fiatCurrency: FiatCurrency): AnnotatedString {
|
||||
if (amount == null) return AnnotatedString(text = UNKNOWN_AMOUNT_SIGN)
|
||||
|
||||
val locale = Locale.getDefault()
|
||||
val fractionDigits = 2
|
||||
val formatter = NumberFormat.getCurrencyInstance(locale) as? DecimalFormat
|
||||
?: return AnnotatedString("${amount.toPlainString()} ${fiatCurrency.symbol}")
|
||||
|
||||
val currencyToShow = "${fiatCurrency.symbol} "
|
||||
val scaledAmount = try {
|
||||
Currency.getInstance(fiatCurrency.code)?.let { currency ->
|
||||
formatter.currency = currency
|
||||
formatter.maximumFractionDigits = fractionDigits
|
||||
formatter.minimumFractionDigits = fractionDigits
|
||||
formatter.isGroupingUsed = true
|
||||
formatter.roundingMode = RoundingMode.HALF_UP
|
||||
formatter.format(amount).replace(currency.symbol, currencyToShow)
|
||||
} ?: formatter.format(amount)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e("TotalBalanceCard buildAmountString currencyCode is not a supported ISO 4217 code: $e")
|
||||
formatter.currency?.let {
|
||||
formatter.format(amount).replace(it.symbol, currencyToShow)
|
||||
} ?: formatter.format(amount)
|
||||
}
|
||||
|
||||
val integer = scaledAmount.substringBefore(formatter.decimalFormatSymbols.decimalSeparator)
|
||||
var reminder = scaledAmount.substringAfter(formatter.decimalFormatSymbols.decimalSeparator)
|
||||
|
||||
// if locale formatted currency at the end, remember it and place out of AnnotatedString
|
||||
val currency = if (reminder.endsWith(currencyToShow)) {
|
||||
reminder = reminder.dropLast(currencyToShow.length)
|
||||
currencyToShow
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
return buildAnnotatedString {
|
||||
append(integer)
|
||||
append(formatter.decimalFormatSymbols.decimalSeparator)
|
||||
append(
|
||||
AnnotatedString(
|
||||
text = reminder,
|
||||
spanStyle = TangemTheme.typography.h3.toSpanStyle(),
|
||||
),
|
||||
)
|
||||
append(currency) // it is not empty if was placed at the end after locale formatting
|
||||
}
|
||||
}
|
||||
|
||||
private sealed interface TotalBalanceCardState {
|
||||
val amount: BigDecimal?
|
||||
val showWarning: Boolean
|
||||
val fiatCurrency: FiatCurrency
|
||||
val onChangeFiatCurrencyClick: () -> Unit
|
||||
|
||||
object Empty : TotalBalanceCardState {
|
||||
override val amount: BigDecimal? = null
|
||||
override val showWarning: Boolean = false
|
||||
override val fiatCurrency: FiatCurrency = FiatCurrency.Default
|
||||
override val onChangeFiatCurrencyClick: () -> Unit = { /* no-op */ }
|
||||
}
|
||||
|
||||
data class Loading(
|
||||
override val fiatCurrency: FiatCurrency,
|
||||
override val onChangeFiatCurrencyClick: () -> Unit,
|
||||
) : TotalBalanceCardState {
|
||||
override val amount: BigDecimal = BigDecimal.ZERO
|
||||
override val showWarning: Boolean = false
|
||||
}
|
||||
|
||||
data class Failure(
|
||||
override val fiatCurrency: FiatCurrency,
|
||||
override val onChangeFiatCurrencyClick: () -> Unit,
|
||||
) : TotalBalanceCardState {
|
||||
override val amount: BigDecimal? = null
|
||||
override val showWarning: Boolean = true
|
||||
}
|
||||
|
||||
data class Success(
|
||||
override val amount: BigDecimal,
|
||||
override val showWarning: Boolean,
|
||||
override val fiatCurrency: FiatCurrency,
|
||||
override val onChangeFiatCurrencyClick: () -> Unit,
|
||||
) : TotalBalanceCardState
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun TotalBalanceCardContentSample(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(all = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
TotalBalanceCardContent(state = TotalBalanceCardState.Empty)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
TotalBalanceCardContent(
|
||||
state = TotalBalanceCardState.Loading(FiatCurrency("USD", "USD", "$")) {},
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
TotalBalanceCardContent(
|
||||
state = TotalBalanceCardState.Failure(
|
||||
onChangeFiatCurrencyClick = {},
|
||||
fiatCurrency = FiatCurrency("USD", "USD", "$"),
|
||||
),
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
TotalBalanceCardContent(
|
||||
state = TotalBalanceCardState.Success(
|
||||
amount = BigDecimal("9917.72"),
|
||||
showWarning = false,
|
||||
onChangeFiatCurrencyClick = {},
|
||||
fiatCurrency = FiatCurrency("USD", "USD", "$"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun TotalBalanceCardContentPreview_Light() {
|
||||
TangemTheme {
|
||||
TotalBalanceCardContentSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun TotalBalanceCardContentPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
TotalBalanceCardContentSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.view.isVisible
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.wallet.ui.utils.CurrencyAction
|
||||
import com.tangem.wallet.databinding.ViewWalletDetailsButtonsRowBinding
|
||||
|
||||
internal class WalletDetailsButtonsRow @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
private val binding = ViewWalletDetailsButtonsRowBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
this,
|
||||
)
|
||||
var onBuyClick: (() -> Unit)? = null
|
||||
var onSellClick: (() -> Unit)? = null
|
||||
var onTradeClick: (() -> Unit)? = null
|
||||
var onSwapClick: (() -> Unit)? = null
|
||||
var onSendClick: (() -> Unit)? = null
|
||||
|
||||
init {
|
||||
orientation = HORIZONTAL
|
||||
|
||||
with(binding) {
|
||||
btnBuy.setOnClickListener { onBuyClick?.invoke() }
|
||||
btnSell.setOnClickListener { onSellClick?.invoke() }
|
||||
btnSwap.setOnClickListener { onSwapClick?.invoke() }
|
||||
btnTrade.setOnClickListener { onTradeClick?.invoke() }
|
||||
btnSend.setOnClickListener { onSendClick?.invoke() }
|
||||
}
|
||||
}
|
||||
|
||||
fun updateButtonsVisibility(
|
||||
actions: Set<CurrencyAction>,
|
||||
exchangeServiceFeatureOn: Boolean,
|
||||
sendAllowed: Boolean,
|
||||
) = with(binding) {
|
||||
containerActionButtons.isVisible = exchangeServiceFeatureOn
|
||||
|
||||
when {
|
||||
actions.isEmpty() -> {
|
||||
containerActionButtons.hide()
|
||||
}
|
||||
actions.size == 1 -> {
|
||||
val action = actions.first()
|
||||
btnTrade.hide()
|
||||
btnBuy.show(action == CurrencyAction.Buy)
|
||||
btnSell.show(action == CurrencyAction.Sell)
|
||||
btnSwap.show(action == CurrencyAction.Swap)
|
||||
}
|
||||
else -> {
|
||||
btnBuy.hide()
|
||||
btnSell.hide()
|
||||
btnSwap.hide()
|
||||
btnTrade.show()
|
||||
}
|
||||
}
|
||||
|
||||
if (containerActionButtons.isVisible) {
|
||||
btnSend.gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
||||
btnSend.iconGravity = MaterialButton.ICON_GRAVITY_END
|
||||
} else {
|
||||
btnSend.gravity = Gravity.CENTER
|
||||
btnSend.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_END
|
||||
}
|
||||
btnSend.isEnabled = sendAllowed
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object CurrencySelectionDialog {
|
||||
fun create(dialog: WalletDialog.CurrencySelectionDialog, context: Context): AlertDialog {
|
||||
val currenciesToShow = dialog.currenciesList
|
||||
.map { it.displayName }
|
||||
.toTypedArray()
|
||||
val currentSelection = dialog.currenciesList
|
||||
.indexOfFirst { it.code == dialog.currentAppCurrency.code }
|
||||
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog)
|
||||
.setTitle(context.getString(R.string.details_row_title_currency))
|
||||
.setNegativeButton(context.getString(R.string.common_cancel)) { _, _ -> /* no-op */ }
|
||||
.setOnDismissListener {
|
||||
store.dispatch(WalletAction.DialogAction.Hide)
|
||||
}
|
||||
.setSingleChoiceItems(currenciesToShow, currentSelection) { _, which ->
|
||||
dialog.currenciesList.getOrNull(which)?.let { selectedCurrency ->
|
||||
store.dispatch(
|
||||
WalletAction.AppCurrencyAction.SelectAppCurrency(
|
||||
fiatCurrency = selectedCurrency,
|
||||
),
|
||||
)
|
||||
store.dispatch(WalletAction.DialogAction.Hide)
|
||||
}
|
||||
}
|
||||
.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.badoo.mvicore.modelWatcher
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.analytics.events.Portfolio
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.getQuantityString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WalletAdapter
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentWalletBinding
|
||||
|
||||
class MultiWalletView : WalletView() {
|
||||
|
||||
private lateinit var walletsAdapter: WalletAdapter
|
||||
|
||||
private val watcher = modelWatcher<WalletState> {
|
||||
// !!! 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)
|
||||
}
|
||||
}
|
||||
(WalletState::totalBalance or WalletState::walletsDataFromStores) { walletState ->
|
||||
binding?.let {
|
||||
handleTotalBalance(
|
||||
binding = it,
|
||||
totalBalance = walletState.totalBalance,
|
||||
walletsCount = walletState.walletsDataFromStores.size,
|
||||
appFiatCurrency = store.state.globalState.appCurrency,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
|
||||
setFragment(fragment, binding)
|
||||
onViewCreated()
|
||||
showMultiWalletView(binding)
|
||||
}
|
||||
|
||||
private fun showMultiWalletView(binding: FragmentWalletBinding) = with(binding) {
|
||||
watcher.clear()
|
||||
tvTwinCardNumber.hide()
|
||||
rvPendingTransaction.hide()
|
||||
lCardBalance.root.hide()
|
||||
lAddress.root.hide()
|
||||
rowButtons.hide()
|
||||
lSingleWalletBalance.root.hide()
|
||||
lCardTotalBalance.show()
|
||||
rvMultiwallet.show()
|
||||
btnAddToken.show()
|
||||
}
|
||||
|
||||
override fun onViewCreated() {
|
||||
setupWalletsRecyclerView()
|
||||
}
|
||||
|
||||
private fun setupWalletsRecyclerView() {
|
||||
val fragment = fragment ?: return
|
||||
walletsAdapter = WalletAdapter()
|
||||
walletsAdapter.setHasStableIds(true)
|
||||
binding?.rvMultiwallet?.layoutManager = LinearLayoutManager(fragment.requireContext())
|
||||
binding?.rvMultiwallet?.adapter = walletsAdapter
|
||||
binding?.rvMultiwallet?.itemAnimator = null
|
||||
}
|
||||
|
||||
override fun onNewState(state: WalletState) {
|
||||
val fragment = fragment ?: return
|
||||
val binding = binding ?: return
|
||||
|
||||
watcher.invoke(state)
|
||||
|
||||
binding.btnAddToken.setOnClickListener {
|
||||
Analytics.send(Portfolio.ButtonManageTokens())
|
||||
|
||||
store.dispatch(action = TokensAction.SetArgs.ManageAccess)
|
||||
store.dispatch(action = NavigationAction.NavigateTo(screen = AppScreen.ManageTokens))
|
||||
}
|
||||
handleErrorStates(state = state, binding = binding, fragment = fragment)
|
||||
}
|
||||
|
||||
private fun setupWalletCardNumber(binding: FragmentWalletBinding, walletCardsCount: Int?) = with(binding) {
|
||||
if (walletCardsCount != null) {
|
||||
tvTwinCardNumber.show()
|
||||
tvTwinCardNumber.text =
|
||||
tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, walletCardsCount)
|
||||
} else {
|
||||
tvTwinCardNumber.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackupWarning(binding: FragmentWalletBinding, showBackupWarning: Boolean) =
|
||||
with(binding.lWalletBackupWarning) {
|
||||
root.isVisible = showBackupWarning
|
||||
root.setOnClickListener {
|
||||
Analytics.send(MainScreen.NoticeBackupYourWalletTapped())
|
||||
store.dispatch(WalletAction.MultiWallet.BackupWallet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRescanWarning(binding: FragmentWalletBinding, showRescanWarning: Boolean) =
|
||||
with(binding.lWalletRescanWarning) {
|
||||
root.isVisible = showRescanWarning
|
||||
root.setOnClickListener {
|
||||
Analytics.send(MainScreen.NoticeScanYourCardTapped())
|
||||
store.dispatch(WalletAction.MultiWallet.ScanToGetDerivations)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleTotalBalance(
|
||||
binding: FragmentWalletBinding,
|
||||
totalBalance: TotalFiatBalance?,
|
||||
walletsCount: Int,
|
||||
appFiatCurrency: FiatCurrency,
|
||||
) = with(binding.lCardTotalBalance) {
|
||||
isVisible = walletsCount > 0
|
||||
|
||||
onChangeFiatCurrencyClick = {
|
||||
store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
|
||||
}
|
||||
status = totalBalance
|
||||
fiatCurrency = appFiatCurrency
|
||||
}
|
||||
|
||||
private fun handleErrorStates(state: WalletState, binding: FragmentWalletBinding, fragment: WalletFragment) {
|
||||
when (state.error) {
|
||||
ErrorType.UnknownBlockchain -> {
|
||||
showErrorState(
|
||||
binding,
|
||||
fragment.getText(R.string.wallet_error_unsupported_blockchain),
|
||||
fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle),
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun showErrorState(
|
||||
binding: FragmentWalletBinding,
|
||||
errorTitle: CharSequence,
|
||||
errorDescription: CharSequence,
|
||||
) = with(binding) {
|
||||
lCardBalance.root.show()
|
||||
with(lCardBalance) {
|
||||
lBalance.root.hide()
|
||||
lBalanceError.root.show()
|
||||
rvMultiwallet.show()
|
||||
btnAddToken.hide()
|
||||
lBalanceError.tvErrorTitle.text = errorTitle
|
||||
lBalanceError.tvErrorDescriptions.text = errorDescription
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyFragment() {
|
||||
super.onDestroyFragment()
|
||||
watcher.clear()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,235 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidget
|
||||
import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
|
||||
import com.tangem.tap.features.wallet.ui.utils.*
|
||||
import com.tangem.tap.features.wallet.ui.view.WalletDetailsButtonsRow
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentWalletBinding
|
||||
|
||||
class SingleWalletView : WalletView() {
|
||||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||
|
||||
// FIXME: Move to model watcher
|
||||
private var watchedPrimaryWalletForAddressCard: WalletDataModel? = null
|
||||
private var watchedPrimaryWalletForBalance: WalletDataModel? = null
|
||||
|
||||
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
|
||||
setFragment(fragment, binding)
|
||||
onViewCreated()
|
||||
showSingleWalletView(binding)
|
||||
}
|
||||
|
||||
private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
|
||||
watchedPrimaryWalletForAddressCard = null
|
||||
watchedPrimaryWalletForBalance = null
|
||||
tvTwinCardNumber.hide()
|
||||
rvMultiwallet.hide()
|
||||
btnAddToken.hide()
|
||||
rvPendingTransaction.hide()
|
||||
pbLoadingUserTokens.hide()
|
||||
lCardTotalBalance.hide()
|
||||
lSingleWalletBalance.root.hide()
|
||||
lWalletRescanWarning.root.hide()
|
||||
lWalletBackupWarning.root.hide()
|
||||
lCardBalance.root.show()
|
||||
lAddress.root.show()
|
||||
rowButtons.show()
|
||||
}
|
||||
|
||||
override fun onViewCreated() {
|
||||
setupTransactionsRecyclerView()
|
||||
}
|
||||
|
||||
override fun onDestroyFragment() {
|
||||
super.onDestroyFragment()
|
||||
watchedPrimaryWalletForAddressCard = null
|
||||
watchedPrimaryWalletForBalance = null
|
||||
}
|
||||
|
||||
private fun setupTransactionsRecyclerView() {
|
||||
val fragment = fragment ?: return
|
||||
pendingTransactionAdapter = PendingTransactionsAdapter()
|
||||
binding?.rvPendingTransaction?.layoutManager =
|
||||
LinearLayoutManager(fragment.requireContext())
|
||||
binding?.rvPendingTransaction?.adapter = pendingTransactionAdapter
|
||||
}
|
||||
|
||||
override fun onNewState(state: WalletState) {
|
||||
val binding = binding ?: return
|
||||
val primaryWalletData = state.primaryWalletData ?: return
|
||||
|
||||
setupTwinCards(state.twinCardsState, binding)
|
||||
setupButtons(primaryWalletData, binding, state.isExchangeServiceFeatureOn)
|
||||
setupAddressCard(state, binding)
|
||||
showPendingTransactionsIfPresent(primaryWalletData.status.pendingTransactions)
|
||||
setupBalance(state, primaryWalletData)
|
||||
}
|
||||
|
||||
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
|
||||
val knownTransactions = pendingTransactions.filterNot {
|
||||
it.type == PendingTransactionType.Unknown
|
||||
}
|
||||
pendingTransactionAdapter.submitList(knownTransactions)
|
||||
binding?.rvPendingTransaction?.show(knownTransactions.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun setupBalance(state: WalletState, primaryWallet: WalletDataModel) {
|
||||
if (watchedPrimaryWalletForBalance == primaryWallet) return
|
||||
watchedPrimaryWalletForBalance = primaryWallet
|
||||
|
||||
val fragment = fragment ?: return
|
||||
binding?.apply {
|
||||
lCardBalance.lBalance.root.show()
|
||||
BalanceWidget(
|
||||
binding = this.lCardBalance,
|
||||
fragment = fragment,
|
||||
blockchainWalletData = primaryWallet,
|
||||
tokenWalletData = state.primaryTokenData,
|
||||
).setup()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTwinCards(twinCardsState: TwinCardsState?, binding: FragmentWalletBinding) = with(binding) {
|
||||
if (twinCardsState?.cardNumber == null) {
|
||||
tvTwinCardNumber.hide()
|
||||
} else {
|
||||
tvTwinCardNumber.show()
|
||||
tvTwinCardNumber.text = tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, 2)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtons(
|
||||
walletData: WalletDataModel,
|
||||
binding: FragmentWalletBinding,
|
||||
isExchangeServiceFeatureEnabled: Boolean,
|
||||
) = with(binding) {
|
||||
setupRowButtons(walletData, rowButtons, isExchangeServiceFeatureEnabled)
|
||||
|
||||
lAddress.btnCopy.setOnClickListener {
|
||||
walletData.walletAddresses?.selectedAddress?.address?.let { addressString ->
|
||||
store.dispatch(WalletAction.CopyAddress(addressString, fragment!!.requireContext()))
|
||||
}
|
||||
}
|
||||
lAddress.btnShowQr.setOnClickListener {
|
||||
Analytics.send(Token.ShowWalletAddress)
|
||||
|
||||
walletData.walletAddresses?.selectedAddress?.let { selectedAddress ->
|
||||
store.dispatch(
|
||||
WalletAction.DialogAction.QrCode(
|
||||
currency = walletData.currency,
|
||||
selectedAddress = selectedAddress,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRowButtons(
|
||||
walletData: WalletDataModel,
|
||||
rowButtons: WalletDetailsButtonsRow,
|
||||
isExchangeServiceFeatureEnabled: Boolean,
|
||||
) {
|
||||
val swapInteractor = this.swapInteractor ?: return
|
||||
val swapFeatureToggleManager = this.swapFeatureToggleManager ?: return
|
||||
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
binding?.rowButtons?.apply {
|
||||
onBuyClick = { store.dispatch(TradeCryptoAction.Buy()) }
|
||||
onSellClick = { store.dispatch(TradeCryptoAction.Sell) }
|
||||
onSwapClick = { store.dispatch(TradeCryptoAction.Swap) }
|
||||
onTradeClick = {
|
||||
store.dispatch(
|
||||
WalletAction.DialogAction.ChooseTradeActionDialog(
|
||||
buyAllowed = walletData.isAvailableToBuy(exchangeManager),
|
||||
sellAllowed = walletData.isAvailableToSell(exchangeManager),
|
||||
swapAllowed = false, // always disable for single wallet
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
val actions = walletData.getAvailableActions(
|
||||
swapInteractor = swapInteractor,
|
||||
exchangeManager = exchangeManager,
|
||||
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||
isSingleWallet = true,
|
||||
)
|
||||
binding?.rowButtons?.updateButtonsVisibility(
|
||||
actions = actions,
|
||||
exchangeServiceFeatureOn = isExchangeServiceFeatureEnabled,
|
||||
sendAllowed = walletData.mainButton(walletData.status.amount).enabled,
|
||||
)
|
||||
|
||||
rowButtons.onSendClick = { store.dispatch(WalletAction.Send()) }
|
||||
}
|
||||
|
||||
private fun setupAddressCard(state: WalletState, binding: FragmentWalletBinding) = with(binding.lAddress) {
|
||||
val primaryWallet = state.primaryWalletData
|
||||
if (primaryWallet == watchedPrimaryWalletForAddressCard) return@with
|
||||
watchedPrimaryWalletForAddressCard = primaryWallet
|
||||
|
||||
if (primaryWallet?.walletAddresses != null && primaryWallet.currency is Currency.Blockchain) {
|
||||
binding.lAddress.root.show()
|
||||
if (primaryWallet.shouldShowMultipleAddress()) {
|
||||
(binding.lAddress.root as? ViewGroup)?.beginDelayedTransition()
|
||||
chipGroupAddressType.show()
|
||||
chipGroupAddressType.fitChipsByGroupWidth()
|
||||
val checkedId = MultipleAddressUiHelper.typeToId(
|
||||
primaryWallet.walletAddresses.selectedAddress.type,
|
||||
primaryWallet.currency.blockchain,
|
||||
)
|
||||
if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
|
||||
|
||||
chipGroupAddressType.setOnCheckedChangeListener { group, checkedId ->
|
||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||
val type = MultipleAddressUiHelper.idToType(checkedId, primaryWallet.currency.blockchain)
|
||||
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
|
||||
}
|
||||
} else {
|
||||
chipGroupAddressType.hide()
|
||||
}
|
||||
tvAddress.text = primaryWallet.walletAddresses.selectedAddress.address
|
||||
tvExplore.setOnClickListener {
|
||||
store.dispatch(
|
||||
WalletAction.ExploreAddress(
|
||||
primaryWallet.walletAddresses.selectedAddress.exploreUrl,
|
||||
fragment!!.requireContext(),
|
||||
),
|
||||
)
|
||||
}
|
||||
setupCardInfo(primaryWallet)
|
||||
} else {
|
||||
binding.lAddress.root.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCardInfo(walletData: WalletDataModel) {
|
||||
val textView = binding?.lAddress?.tvInfo
|
||||
val blockchain = walletData.currency.blockchain
|
||||
if (textView != null) {
|
||||
textView.text = textView.getString(
|
||||
id = R.string.address_qr_code_message_format,
|
||||
blockchain.fullName,
|
||||
blockchain.currency,
|
||||
blockchain.fullName,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.wallet.databinding.FragmentWalletBinding
|
||||
|
||||
abstract class WalletView {
|
||||
|
||||
var swapInteractor: SwapInteractor? = null
|
||||
var swapFeatureToggleManager: SwapFeatureToggleManager? = null
|
||||
|
||||
protected var fragment: WalletFragment? = null
|
||||
protected var binding: FragmentWalletBinding? = null
|
||||
|
||||
fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
|
||||
this.fragment = fragment
|
||||
this.binding = binding
|
||||
}
|
||||
|
||||
fun removeFragment() {
|
||||
fragment = null
|
||||
binding = null
|
||||
}
|
||||
|
||||
open fun onViewDestroy() {
|
||||
removeFragment()
|
||||
}
|
||||
|
||||
open fun onDestroyFragment() {}
|
||||
|
||||
abstract fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)
|
||||
abstract fun onViewCreated()
|
||||
abstract fun onNewState(state: WalletState)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.features.walletSelector.ui.model
|
||||
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
|
||||
|
||||
internal sealed interface UserWalletItem {
|
||||
val id: UserWalletId
|
||||
|
|
@ -11,7 +11,7 @@ internal sealed interface UserWalletItem {
|
|||
val isLocked: Boolean
|
||||
|
||||
sealed class Balance {
|
||||
open val amount: String = UNKNOWN_AMOUNT_SIGN
|
||||
open val amount: String = BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
open val showWarning: Boolean = false
|
||||
|
||||
object Loading : Balance()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.wallet.converters
|
||||
package com.tangem.tap.network.exchangeServices
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.network.exchangeServices
|
|||
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.features.wallet.converters.CryptoCurrencyConverter
|
||||
|
||||
class DefaultRampManager(private val exchangeService: ExchangeService?) : RampStateManager {
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import com.tangem.tap.common.redux.AppState
|
|||
import com.tangem.tap.domain.TangemSdkManager
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -43,7 +42,6 @@ class AppStateHolder @Inject constructor() : WalletsStateHolder, ReduxNavControl
|
|||
|
||||
@Deprecated("Use scan response from selected user wallet")
|
||||
var scanResponse: ScanResponse? = null
|
||||
var walletState: WalletState? = null
|
||||
var userTokensRepository: UserTokensRepository? = null
|
||||
var mainStore: Store<AppState>? = null
|
||||
var tangemSdkManager: TangemSdkManager? = null
|
||||
|
|
|
|||
|
|
@ -15,10 +15,8 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.models.*
|
||||
import com.tangem.lib.crypto.models.transactions.SendTxResult
|
||||
|
|
@ -39,7 +37,6 @@ class TransactionManagerImpl(
|
|||
private val analytics: AnalyticsEventHandler,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
) : TransactionManager {
|
||||
|
||||
override suspend fun sendApproveTransaction(
|
||||
|
|
@ -413,19 +410,15 @@ class TransactionManagerImpl(
|
|||
}
|
||||
|
||||
private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
|
||||
val walletManager = if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
val selectedUserWallet = requireNotNull(
|
||||
userWalletsListManager.selectedUserWalletSync,
|
||||
) { "userWallet or userWalletsListManager is null" }
|
||||
walletManagersFacade.getOrCreateWalletManager(
|
||||
selectedUserWallet.walletId,
|
||||
blockchain,
|
||||
derivationPath,
|
||||
)
|
||||
} else {
|
||||
val blockchainNetwork = BlockchainNetwork(blockchain, derivationPath, emptyList())
|
||||
appStateHolder.walletState?.getWalletManager(blockchainNetwork)
|
||||
}
|
||||
val selectedUserWallet = requireNotNull(
|
||||
userWalletsListManager.selectedUserWalletSync,
|
||||
) { "userWallet or userWalletsListManager is null" }
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
selectedUserWallet.walletId,
|
||||
blockchain,
|
||||
derivationPath,
|
||||
)
|
||||
|
||||
return requireNotNull(walletManager) { "no wallet manager found" }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,15 +11,12 @@ import com.tangem.domain.common.extensions.fromNetworkId
|
|||
import com.tangem.domain.common.extensions.toCoinId
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
import com.tangem.lib.crypto.models.Currency.NativeToken
|
||||
import com.tangem.lib.crypto.models.Currency.NonNativeToken
|
||||
import com.tangem.lib.crypto.models.ProxyAmount
|
||||
import com.tangem.lib.crypto.models.ProxyFiatCurrency
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import com.tangem.tap.walletStoresManager
|
||||
|
|
@ -31,7 +28,6 @@ import com.tangem.tap.features.wallet.models.Currency as WalletCurrency
|
|||
class UserWalletManagerImpl(
|
||||
private val appStateHolder: AppStateHolder,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
) : UserWalletManager {
|
||||
|
||||
override suspend fun getUserTokens(
|
||||
|
|
@ -216,28 +212,17 @@ class UserWalletManagerImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override fun refreshWallet() {
|
||||
// workaround, should update wallet after transaction
|
||||
appStateHolder.mainStore?.dispatchOnMain(WalletAction.LoadData.Refresh)
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
|
||||
val walletManager = if (walletFeatureToggles.isRedesignedScreenEnabled) {
|
||||
val selectedUserWallet = requireNotNull(
|
||||
userWalletsListManager.selectedUserWalletSync,
|
||||
) { "userWallet or userWalletsListManager is null" }
|
||||
walletManagersFacade.getOrCreateWalletManager(
|
||||
selectedUserWallet.walletId,
|
||||
blockchain,
|
||||
derivationPath,
|
||||
)
|
||||
} else {
|
||||
val blockchainNetwork = BlockchainNetwork(blockchain, derivationPath, emptyList())
|
||||
return requireNotNull(appStateHolder.walletState?.getWalletManager(blockchainNetwork)) {
|
||||
"No wallet manager found"
|
||||
}
|
||||
}
|
||||
val selectedUserWallet = requireNotNull(
|
||||
userWalletsListManager.selectedUserWalletSync,
|
||||
) { "userWallet or userWalletsListManager is null" }
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
selectedUserWallet.walletId,
|
||||
blockchain,
|
||||
derivationPath,
|
||||
)
|
||||
|
||||
return requireNotNull(walletManager) {
|
||||
"No wallet manager found"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
|
|
@ -39,12 +38,10 @@ class ProxyModule {
|
|||
fun provideUserWalletManager(
|
||||
appStateHolder: AppStateHolder,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
walletFeatureToggles: WalletFeatureToggles,
|
||||
): UserWalletManager {
|
||||
return UserWalletManagerImpl(
|
||||
appStateHolder = appStateHolder,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
walletFeatureToggles = walletFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -55,14 +52,12 @@ class ProxyModule {
|
|||
analytics: AnalyticsEventHandler,
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
walletFeatureToggles: WalletFeatureToggles,
|
||||
): TransactionManager {
|
||||
return TransactionManagerImpl(
|
||||
appStateHolder = appStateHolder,
|
||||
analytics = analytics,
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
walletFeatureToggles = walletFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,8 +42,6 @@ interface UserWalletManager {
|
|||
|
||||
suspend fun hideAllTokens()
|
||||
|
||||
fun refreshWallet()
|
||||
|
||||
/**
|
||||
* Returns wallet public address for token
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue