diff --git a/app/build.gradle b/app/build.gradle index 0b53a8df5e..686a683503 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,6 +38,7 @@ android { } debug_beta { initWith release + debuggable false versionNameSuffix "-beta" applicationIdSuffix ".debug" buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"prod\"' @@ -71,14 +72,13 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.3.6' implementation 'androidx.constraintlayout:constraintlayout:2.1.1' implementation 'com.google.android.material:material:1.4.0' - implementation "androidx.core:core-ktx:1.6.0" implementation 'com.google.android.play:core:1.10.2' implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-42' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-91' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-91' + implementation 'com.tangem:blockchain:develop-48' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-95' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-95' // WebView implementation "androidx.browser:browser:1.3.0" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c2d4929889..f3c400a3ec 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,13 @@ + + + + + + + + * + * + * + * + * + */ +fun Activity.sendEmail( + email: String, + subject: String, + message: String, + file: File? = null, + onFail: ((Exception) -> Unit)? = null +) { + fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent { + val builder = ShareCompat.IntentBuilder.from(this) + .setType("message/rfc822") + .setEmailTo(arrayOf(recipient)) + .setSubject(subject) + .setText(text) + file?.let { builder.setStream(FileProvider.getUriForFile(this, "${packageName}.provider", it)) } + return builder.intent + } + + val originalIntent = createEmailShareIntent(email, subject, message, file) + val emailFilterIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")) + + val originalIntentResults = packageManager.queryIntentActivities(originalIntent, 0) + val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0) + + val targetedIntents = originalIntentResults + .filter { originalResult -> + emailFilterIntentResults.any { + originalResult.activityInfo.packageName == it.activityInfo.packageName + } + } + .map { + createEmailShareIntent(email, subject, message, file).apply { + setPackage(it.activityInfo.packageName) + } + } + .toMutableList() + try { + val chooserIntent = Intent.createChooser(targetedIntents.removeAt(0), "Send mail...") + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray()) + chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ContextCompat.startActivity(this, chooserIntent, null) + } catch (ex: Exception) { + onFail?.invoke(ex) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt index d470ae2bf6..be71883ecc 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt @@ -15,9 +15,8 @@ fun Picasso.loadCurrenciesIcon( imageView: ImageView, textView: TextView, token: Token? = null, - blockchain: Blockchain?, + blockchain: Blockchain, ) { - val blockchain = blockchain ?: Blockchain.Ethereum val url = if (token != null) { IconsUtil.getTokenIconUri(blockchain, token) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 17db11e102..83aa152f76 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -18,14 +18,18 @@ fun Store<*>.dispatchOnMain(action: Action) { } } -suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) { - store.state.globalState.tapWalletManager.onCardScanned(scanResponse) +suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse, addAnalytics: Boolean = true) { + store.state.globalState.tapWalletManager.onCardScanned(scanResponse, addAnalytics) } fun Store<*>.dispatchOpenUrl(url: String) { store.dispatch(NavigationAction.OpenUrl(url)) } +fun Store<*>.dispatchShare(url: String) { + store.dispatch(NavigationAction.Share(url)) +} + fun Store<*>.dispatchNotification(resId: Int) { scope.launch(Dispatchers.Main) { store.dispatch(GlobalAction.ShowNotification(resId)) diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt index 4334d55f59..bcde7dae3b 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt @@ -1,7 +1,7 @@ package com.tangem.tap.common.redux -import com.tangem.common.extensions.VoidCallback import com.tangem.tap.features.wallet.redux.AddressData +import com.tangem.tap.features.wallet.redux.Currency /** [REDACTED_AUTHOR] @@ -11,8 +11,7 @@ interface StateDialog sealed class AppDialog : StateDialog { object ScanFailsDialog : AppDialog() data class AddressInfoDialog( + val currency: Currency, val addressData: AddressData, - val onCopyAddress: VoidCallback, - val onExploreAddress: VoidCallback ) : AppDialog() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 278672bff1..a1e1e7b2b1 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -12,7 +12,7 @@ import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.feedback.EmailData import com.tangem.tap.features.feedback.FeedbackManager -import com.tangem.tap.network.moonpay.MoonPayUserStatus +import com.tangem.tap.network.moonpay.MoonpayStatus import org.rekotlin.Action sealed class GlobalAction : Action { @@ -72,7 +72,7 @@ sealed class GlobalAction : Action { data class SendFeedback(val emailData: EmailData) : GlobalAction() data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction() - object GetMoonPayUserStatus : GlobalAction() { - data class Success(val moonPayUserStatus: MoonPayUserStatus) : GlobalAction() + object GetMoonPayStatus : GlobalAction() { + data class Success(val moonPayStatus: MoonpayStatus) : GlobalAction() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index 10aa0f1533..06cc349269 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -19,6 +19,7 @@ import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.launch import org.rekotlin.Middleware +import timber.log.Timber class GlobalMiddleware { companion object { @@ -74,15 +75,16 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState store.state.globalState.feedbackManager?.infoHolder ?.setWalletsInfo(action.walletManagers) } - is GlobalAction.GetMoonPayUserStatus -> { + is GlobalAction.GetMoonPayStatus -> { val apiKey = appState()?.globalState?.configManager?.config?.moonPayApiKey if (apiKey != null) { scope.launch { - val userStatusResponse = MoonpayService().getUserStatus(apiKey) - if (userStatusResponse is Result.Success) { - store.dispatchOnMain( - GlobalAction.GetMoonPayUserStatus.Success(userStatusResponse.data) - ) + val result = MoonpayService().getMoonpayStatus(apiKey) + when (result) { + is Result.Success -> { + store.dispatchOnMain(GlobalAction.GetMoonPayStatus.Success(result.data)) + } + is Result.Failure -> Timber.e(result.error) } } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index f5ce207390..993830329a 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.redux.global import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.onboarding.OnboardingManager +import com.tangem.tap.network.moonpay.MoonpayStatus import com.tangem.tap.preferencesStorage import org.rekotlin.Action @@ -42,20 +43,14 @@ fun globalReducer(action: Action, state: AppState): GlobalState { } is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager) is GlobalAction.UpdateWalletSignedHashes -> { - val wallet = globalState.scanResponse?.card - ?.wallet(action.walletPublicKey) - ?.copy( - totalSignedHashes = action.walletSignedHashes, - remainingSignatures = action.remainingSignatures - ) - val card = globalState.scanResponse?.card - wallet?.let { globalState.scanResponse.card.updateWallet(wallet) } + val card = globalState.scanResponse?.card ?: return globalState + val wallet = card.wallet(action.walletPublicKey) ?: return globalState - if (card != null) { - globalState.copy(scanResponse = globalState.scanResponse.copy(card = card)) - } else { - globalState - } + val newCardInstance = card.updateWallet(wallet.copy( + totalSignedHashes = action.walletSignedHashes, + remainingSignatures = action.remainingSignatures + )) + globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance)) } is GlobalAction.SetFeedbackManager -> { globalState.copy(feedbackManager = action.feedbackManager) @@ -66,8 +61,19 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.HideDialog -> { globalState.copy(dialog = null) } - is GlobalAction.GetMoonPayUserStatus.Success -> { - globalState.copy(moonPayUserStatus = action.moonPayUserStatus) + is GlobalAction.GetMoonPayStatus.Success -> { + val fiatExchangeIsEnabled = globalState.configManager?.config?.isTopUpEnabled ?: false + val moonpayStatus = if (fiatExchangeIsEnabled) { + action.moonPayStatus + } else { + MoonpayStatus( + isBuyAllowed = false, + isSellAllowed = false, + availableToBuy = emptyList(), + availableToSell = emptyList() + ) + } + globalState.copy(moonpayStatus = moonpayStatus) } is GlobalAction.SetIfCardVerifiedOnline -> globalState.copy(cardVerifiedOnline = action.verified) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index e0166e90e8..484142c611 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -10,7 +10,7 @@ import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.feedback.FeedbackManager import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.network.coinmarketcap.CoinMarketCapService -import com.tangem.tap.network.moonpay.MoonPayUserStatus +import com.tangem.tap.network.moonpay.MoonpayStatus import org.rekotlin.StateType data class GlobalState( @@ -26,7 +26,7 @@ data class GlobalState( val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, - val moonPayUserStatus: MoonPayUserStatus? = null, + val moonpayStatus: MoonpayStatus? = null, val resources: AndroidResources = AndroidResources(), ) : StateType diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt index d2147ad3c6..214a034270 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt @@ -15,6 +15,8 @@ sealed class NavigationAction : Action { data class OpenUrl(val url: String) : NavigationAction() + data class Share(val data: String) : NavigationAction() + data class ActivityCreated(val activity: WeakReference) : NavigationAction() object ActivityDestroyed : NavigationAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 31fe730615..e8e0168455 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -3,6 +3,7 @@ package com.tangem.tap.common.redux.navigation import com.tangem.tap.common.CustomTabsManager import com.tangem.tap.common.extensions.openFragment import com.tangem.tap.common.extensions.popBackTo +import com.tangem.tap.common.extensions.shareText import com.tangem.tap.common.redux.AppState import org.rekotlin.Middleware @@ -31,6 +32,11 @@ val navigationMiddleware: Middleware = { dispatch, state -> CustomTabsManager().openUrl(action.url, it) } } + is NavigationAction.Share -> { + navState?.activity?.get()?.let { + it.shareText(action.data) + } + } } } next(action) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 3a3acc1a87..27cb46c500 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -125,6 +125,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co companion object { val config = Config( + linkedTerminal = true, + allowUntrustedCards = true, filter = CardFilter( allowedCardTypes = FirmwareVersion.FirmwareType.values().toList() ) diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 14f7b50b6b..10f1a76a87 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -24,7 +24,6 @@ import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import com.tangem.tap.store import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import timber.log.Timber import java.math.BigDecimal @@ -55,7 +54,6 @@ class TapWalletManager { } suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) { - Timber.d(wallet.getTokens().toString()) val currencies = wallet.getTokens() .map { Currency.Token(it) } .plus(Currency.Blockchain(wallet.blockchain)) @@ -97,7 +95,7 @@ class TapWalletManager { } } - private fun updateConfigManager(data: ScanResponse) { + fun updateConfigManager(data: ScanResponse) { val configManager = store.state.globalState.configManager val blockchain = data.getBlockchain() if (data.card.isStart2Coin) { @@ -121,7 +119,6 @@ class TapWalletManager { return@withContext } - val config = store.state.globalState.configManager?.config ?: return@withContext val blockchain = data.getBlockchain() val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) @@ -145,11 +142,8 @@ class TapWalletManager { loadMultiWalletData(data.card, blockchain, null) } } - val moonPayUserStatus = store.state.globalState.moonPayUserStatus - store.dispatch(WalletAction.LoadWallet( - allowToBuy = config.isTopUpEnabled && moonPayUserStatus?.isBuyAllowed == true, - allowToSell = config.isTopUpEnabled && moonPayUserStatus?.isSellAllowed == true, - )) + val moonPayStatus = store.state.globalState.moonpayStatus + store.dispatch(WalletAction.LoadWallet(moonPayStatus)) store.dispatch(WalletAction.LoadFiatRate()) } } @@ -157,22 +151,22 @@ class TapWalletManager { private fun loadMultiWalletData( card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager? ) { - val primaryTokens = primaryWalletManager?.cardTokens ?: emptySet() + val primaryTokens = primaryWalletManager?.cardTokens?.toList() ?: emptyList() val savedCurrencies = currenciesRepository.loadCardCurrencies(card.cardId) if (savedCurrencies == null) { if (primaryBlockchain != null && primaryWalletManager != null) { store.dispatch(WalletAction.MultiWallet.SaveCurrencies( CardCurrencies( - blockchains = setOf(primaryBlockchain), tokens = primaryTokens + blockchains = listOf(primaryBlockchain), tokens = primaryTokens ))) store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain))) store.dispatch(WalletAction.MultiWallet.AddTokens(primaryTokens.toList())) } else { - val blockchains = setOf(Blockchain.Bitcoin, Blockchain.Ethereum) + val blockchains = listOf(Blockchain.Bitcoin, Blockchain.Ethereum) store.dispatch(WalletAction.MultiWallet.SaveCurrencies( - CardCurrencies(blockchains = blockchains, tokens = emptySet()) + CardCurrencies(blockchains = blockchains, tokens = emptyList()) )) val walletManagers = walletManagerFactory.makeWalletManagersForApp(card, blockchains.toList()) @@ -211,12 +205,8 @@ class TapWalletManager { return@withContext } - val config = store.state.globalState.configManager?.config ?: return@withContext - val moonpayUserStatus = store.state.globalState.moonPayUserStatus - store.dispatch(WalletAction.LoadWallet( - allowToBuy = config.isTopUpEnabled && moonpayUserStatus?.isBuyAllowed == true, - allowToSell = config.isTopUpEnabled && moonpayUserStatus?.isSellAllowed == true, - )) + val moonPayStatus = store.state.globalState.moonpayStatus + store.dispatch(WalletAction.LoadWallet(moonPayStatus)) store.dispatch(WalletAction.LoadFiatRate()) } } diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index 33aa1e1b4d..cae1861582 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -80,6 +80,7 @@ class ConfigManager( moonPayApiKey = values.moonPayApiKey, moonPayApiSecretKey = values.moonPayApiSecretKey, blockchainSdkConfig = BlockchainSdkConfig( + blockchairApiKey = values.blockchairApiKey, blockchairAuthorizationToken = values.blockchairAuthorizationToken, blockcypherTokens = values.blockcypherTokens, infuraProjectId = values.infuraProjectId @@ -90,6 +91,7 @@ class ConfigManager( moonPayApiKey = values.moonPayApiKey, moonPayApiSecretKey = values.moonPayApiSecretKey, blockchainSdkConfig = BlockchainSdkConfig( + blockchairApiKey = values.blockchairApiKey, blockchairAuthorizationToken = values.blockchairAuthorizationToken, blockcypherTokens = values.blockcypherTokens, infuraProjectId = values.infuraProjectId diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index d0f99ccabc..c71a4a16dd 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -15,6 +15,7 @@ class ConfigValueModel( val coinMarketCapKey: String, val moonPayApiKey: String, val moonPayApiSecretKey: String, + val blockchairApiKey: String?, val blockchairAuthorizationToken: String?, val blockcypherTokens: Set?, val infuraProjectId: String?, diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt index 4be4dda58f..5ec3ea7672 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt @@ -15,7 +15,7 @@ fun Blockchain.isNoAccountError(exception: Throwable): Boolean { fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? { return when (this) { - Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ZERO + Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ONE Blockchain.XRP -> BigDecimal(10) else -> null } diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt b/app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt new file mode 100644 index 0000000000..d204a2051d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt @@ -0,0 +1,41 @@ +package com.tangem.tap.domain.extensions + +import com.tangem.blockchain.common.Blockchain +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.network.moonpay.MoonpayStatus +import com.tangem.tap.store + +/** +[REDACTED_AUTHOR] + */ +fun MoonpayStatus.buyIsAllowed(currency: Currency): Boolean { + if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false + if (!isBuyAllowed) return false + + return when (currency) { + is Currency.Blockchain -> { + if (currency.blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC) { + false + } else { + availableToBuy.contains(currency.currencySymbol) + } + } + is Currency.Token -> false + } +} + +fun MoonpayStatus.sellIsAllowed(currency: Currency): Boolean { + if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false + if (!isSellAllowed) return false + + return when (currency) { + is Currency.Blockchain -> { + if (currency.blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC) { + false + } else { + availableToSell.contains(currency.currencySymbol) + } + } + is Currency.Token -> false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index fa6a74564b..74dc69a99c 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -11,6 +11,8 @@ import com.tangem.common.card.WalletData import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.TangemError +import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.guard import com.tangem.common.extensions.toHexString import com.tangem.operations.CommandResponse import com.tangem.operations.PreflightReadMode @@ -63,26 +65,32 @@ class ScanProductTask(val card: Card? = null) : CardSessionRunnable) -> Unit, ) { - ScanTask().run(session) { result -> - when (result) { - is CompletionResult.Success -> { - val card = this.card ?: result.data + val card = this.card ?: session.environment.card.guard { + callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) + return + } - val error = getErrorIfExcludedCard(card) - if (error != null) { - callback(CompletionResult.Failure(error)) - return@run - } + val error = getErrorIfExcludedCard(card) + if (error != null) { + callback(CompletionResult.Failure(error)) + return + } - val commandProcessor = when { - TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor() - card.isTangemTwins() -> ScanTwinProcessor() - TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor() - else -> ScanOtherCardsProcessor() + val commandProcessor = when { + TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor() + card.isTangemTwins() -> ScanTwinProcessor() + TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor() + else -> ScanOtherCardsProcessor() + } + commandProcessor.proceed(card, session) { processorResult -> + when (processorResult) { + is CompletionResult.Success -> ScanTask().run(session) { scanTaskResult -> + when (scanTaskResult) { + is CompletionResult.Success -> callback(CompletionResult.Success(processorResult.data)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(scanTaskResult.error)) } - commandProcessor.proceed(card, session, callback) } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(processorResult.error)) } } } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt index 26aa55369c..9a6735ae2e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt @@ -14,14 +14,14 @@ import com.tangem.tap.network.createMoshi class CurrenciesRepository(val context: Application) { private val moshi = createMoshi() - private val blockchainsAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(Set::class.java, Blockchain::class.java) + private val blockchainsAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, Blockchain::class.java) ) - private val tokensAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(Set::class.java, TokenDao::class.java) + private val tokensAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, TokenDao::class.java) ) - private val obsoleteTokensAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(Set::class.java, ObsoleteTokenDao::class.java) + private val obsoleteTokensAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, ObsoleteTokenDao::class.java) ) fun loadCardCurrencies(cardId: String): CardCurrencies? { @@ -42,58 +42,58 @@ class CurrenciesRepository(val context: Application) { } fun saveAddedTokens(cardId: String, tokens: Collection) { - saveTokens(cardId, loadSavedTokens(cardId) + tokens) + saveTokens(cardId, loadSavedTokens(cardId) + tokens.distinct()) } fun saveAddedBlockchain(cardId: String, blockchain: Blockchain) { val blockchains = loadSavedBlockchains(cardId) + blockchain - saveBlockchains(cardId, blockchains) + saveBlockchains(cardId, blockchains.distinct()) } fun removeToken(cardId: String, token: Token) { - val tokens = loadSavedTokens(cardId).filterNot { it == token }.toSet() + val tokens = loadSavedTokens(cardId).filterNot { it == token } saveTokens(cardId, tokens) } fun removeBlockchain(cardId: String, blockchain: Blockchain) { - val blockchains = loadSavedBlockchains(cardId).filterNot { it == blockchain }.toSet() + val blockchains = loadSavedBlockchains(cardId).filterNot { it == blockchain } saveBlockchains(cardId, blockchains) } - private fun loadSavedTokens(cardId: String): Set { + private fun loadSavedTokens(cardId: String): List { val json = try { context.readFileText(getFileNameForTokens(cardId)) } catch (exception: Exception) { - return emptySet() + return emptyList() } return try { - tokensAdapter.fromJson(json)!!.map { it.toToken() }.toSet() + tokensAdapter.fromJson(json)!!.map { it.toToken() } } catch (exception: Exception) { try { - obsoleteTokensAdapter.fromJson(json)!!.map { it.toToken() }.toSet() + obsoleteTokensAdapter.fromJson(json)!!.map { it.toToken() } } catch (exception: Exception) { - emptySet() + emptyList() } } } - private fun saveTokens(cardId: String, tokens: Set) { - val json = tokensAdapter.toJson(tokens.map { TokenDao.fromToken(it) }.toSet()) + private fun saveTokens(cardId: String, tokens: List) { + val json = tokensAdapter.toJson(tokens.distinct().map { TokenDao.fromToken(it) }) context.rewriteFile(json, getFileNameForTokens(cardId)) } - private fun loadSavedBlockchains(cardId: String): Set { + private fun loadSavedBlockchains(cardId: String): List { return try { val json = context.readFileText(getFileNameForBlockchains(cardId)) - blockchainsAdapter.fromJson(json) ?: emptySet() + blockchainsAdapter.fromJson(json) ?: emptyList() } catch (exception: Exception) { - emptySet() + emptyList() } } - private fun saveBlockchains(cardId: String, blockchains: Set) { - val json = blockchainsAdapter.toJson(blockchains) + private fun saveBlockchains(cardId: String, blockchains: List) { + val json = blockchainsAdapter.toJson(blockchains.distinct()) context.rewriteFile(json, getFileNameForBlockchains(cardId)) } @@ -120,7 +120,10 @@ class CurrenciesRepository(val context: Application) { return tokensAdapter.fromJson(ethereumTokensJson)!!.map { it.toToken() } + tokensAdapter.fromJson(bscTokensJson)!!.map { it.toToken() } + - tokensAdapter.fromJson(binanceTokensJson)!!.map { it.toToken() } + tokensAdapter.fromJson(binanceTokensJson)!!.mapNotNull { + // temporary exclude Binance BEP-8 tokens + if (it.type != null && it.type == BINANCE_TOKEN_TYPE_BEP8) null else it.toToken() + } } fun getBlockchains( @@ -144,6 +147,8 @@ class CurrenciesRepository(val context: Application) { private const val FILE_NAME_PREFIX_TOKENS = "tokens" private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains" + private const val BINANCE_TOKEN_TYPE_BEP8 = "bep8" + fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId" fun getFileNameForBlockchains(cardId: String): String = "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId" @@ -157,7 +162,8 @@ data class TokenDao( val contractAddress: String, val decimalCount: Int, @Json(name = "blockchain") - val blockchainDao: BlockchainDao + val blockchainDao: BlockchainDao, + val type: String? = null ) { fun toToken(): Token { return Token( @@ -224,12 +230,12 @@ data class ObsoleteTokenDao( @JsonClass(generateAdapter = true) data class CardCurrenciesDao( - val tokens: Set, - val blockchains: Set, + val tokens: List, + val blockchains: List, ) { fun toCardCurrencies(): CardCurrencies { return CardCurrencies( - tokens = tokens.map { it.toToken() }.toSet(), + tokens = tokens.map { it.toToken() }.distinct(), blockchains = blockchains ) } @@ -237,7 +243,7 @@ data class CardCurrenciesDao( companion object { fun fromCardCurrencies(cardCurrencies: CardCurrencies): CardCurrenciesDao { return CardCurrenciesDao( - tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }.toSet(), + tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }.distinct(), blockchains = cardCurrencies.blockchains ) } @@ -245,6 +251,6 @@ data class CardCurrenciesDao( } data class CardCurrencies( - val tokens: Set, - val blockchains: Set, + val tokens: List, + val blockchains: List, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt b/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt index f16a2591b2..c8599f6d4c 100644 --- a/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt @@ -38,15 +38,6 @@ class TradeCryptoHelper { private const val TRANSACTION_RECEIPT_PATH = "transaction_receipt?transactionId=" - val AVAILABLE_TO_BUY: Set = setOf( - "ZRX", "AAVE", "ALGO", "AXS", "BAT", "BNB", "BUSD", "BTC", "BCH", "BTT", "ADA", "CELO", "CUSD", "LINK", "CHZ", "COMP", "ATOM", "DAI", "DASH", "MANA", "DGB", "DOGE", "EGLD", - "ENJ", "EOS", "ETC", "ETH", "KETH", "RINKETH", "FIL", "HBAR", "MIOTA", "KAVA", "KLAY", "LBC", "LTC", "LUNA", "MKR", "OM", "MATIC", "NANO", "NEAR", "XEM", "NEO", "NIM", "OKB", - "OMG", "ONG", "ONT", "DOT", "QTUM", "RVN", "RFUEL", "KEY", "SRM", "SOL", "XLM", "STMX", "SNX", "KRT", "UST", "USDT", "XTZ", "RUNE", "SAND", "TOMO", "AVA", "TRX", "TUSD", "UNI", - "USDC", "UTK", "VET", "WAXP", "WBTC", "XRP", "ZEC", "ZIL" - ) - - val AVAILABLE_TO_SELL: Set = setOf("BTC", "ETH", "BCH") - fun getUrl( action: Action, blockchain: Blockchain?, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index 966fe4f1d1..24b908cb0a 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -6,12 +6,13 @@ import com.tangem.common.KeyPair import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable -import com.tangem.common.core.TangemSdkError +import com.tangem.common.core.TangemError import com.tangem.common.extensions.hexToBytes import com.tangem.operations.wallet.CreateWalletCommand import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.PurgeWalletCommand import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.wallet.R class CreateSecondTwinWalletTask( private val firstPublicKey: String, @@ -25,11 +26,11 @@ class CreateSecondTwinWalletTask( val publicKey = card?.getSingleWallet()?.publicKey if (publicKey != null) { if (!card.cardId.startsWith(TwinsHelper.getPairCardSeries(firstCardId) ?: "")) { - callback(CompletionResult.Failure(TangemSdkError.WrongCardType())) + callback(CompletionResult.Failure(WrongTwinCard())) return } - session.setInitialMessage(preparingMessage) + session.setMessage(preparingMessage) PurgeWalletCommand(publicKey).run(session) { response -> when (response) { is CompletionResult.Success -> { @@ -45,7 +46,7 @@ class CreateSecondTwinWalletTask( } private fun finishTask(session: CardSession, callback: (result: CompletionResult) -> Unit) { - session.setInitialMessage(creatingWalletMessage) + session.setMessage(creatingWalletMessage) CreateWalletCommand(EllipticCurve.Secp256k1).run(session) { result -> when (result) { is CompletionResult.Success -> { @@ -65,4 +66,10 @@ class CreateSecondTwinWalletTask( } } } + + private class WrongTwinCard : TangemError { + override val code: Int = 50005 + override var customMessage: String = code.toString() + override val messageResId = R.string.twins_wrong_card_error + } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 29143f79b9..9c7a39b172 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.details.redux import com.tangem.blockchain.common.Wallet +import com.tangem.common.card.Card import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.global.FiatCurrencyName @@ -50,7 +51,7 @@ sealed class DetailsAction : Action { } sealed class ManageSecurity : DetailsAction() { - data class CheckCurrentSecurityOption(val cardId: String?) : ManageSecurity() + data class CheckCurrentSecurityOption(val card: Card) : ManageSecurity() data class SetCurrentOption(val userCodes: CheckUserCodesResponse) : ManageSecurity() object OpenSecurity : ManageSecurity() data class SelectOption(val option: SecurityOption) : ManageSecurity() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 22bbd11511..05591c0e48 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -1,8 +1,10 @@ package com.tangem.tap.features.details.redux import com.tangem.common.CompletionResult +import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result +import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.extensions.dispatchOnMain @@ -100,7 +102,7 @@ class DetailsMiddleware { store.dispatch(NavigationAction.PopBackTo()) } is DetailsAction.EraseWallet.Confirm -> { - val card = store.state.detailsState.card ?: return + val card = store.state.detailsState.scanResponse?.card ?: return scope.launch { val result = tangemSdkManager.eraseWallet(card) withContext(Dispatchers.Main) { @@ -113,7 +115,7 @@ class DetailsMiddleware { FirebaseAnalyticsHandler.logCardSdkError( error, FirebaseAnalyticsHandler.ActionToLog.PurgeWallet, - card = store.state.detailsState.card + card = store.state.detailsState.scanResponse?.card ) } } @@ -141,16 +143,22 @@ class DetailsMiddleware { fun handle(action: DetailsAction.ManageSecurity) { when (action) { is DetailsAction.ManageSecurity.CheckCurrentSecurityOption -> { - scope.launch { - when (val response = tangemSdkManager.checkUserCodes(action.cardId)) { - is CompletionResult.Success -> { - store.dispatchOnMain( - DetailsAction.ManageSecurity.SetCurrentOption(response.data) - ) - store.dispatchOnMain(DetailsAction.ManageSecurity.OpenSecurity) - } - is CompletionResult.Failure -> { - + if (action.card.firmwareVersion >= FirmwareVersion.IsAccessCodeStatusAvailable) { + // for a card that meets this condition, we can get these statuses from it + val simulatedResponse = CheckUserCodesResponse( + action.card.isAccessCodeSet, action.card.isPasscodeSet ?: false + ) + store.dispatch(DetailsAction.ManageSecurity.SetCurrentOption(simulatedResponse)) + store.dispatch(DetailsAction.ManageSecurity.OpenSecurity) + } else { + scope.launch { + when (val response = tangemSdkManager.checkUserCodes(action.card.cardId)) { + is CompletionResult.Success -> { + store.dispatchOnMain(DetailsAction.ManageSecurity.SetCurrentOption(response.data)) + store.dispatchOnMain(DetailsAction.ManageSecurity.OpenSecurity) + } + is CompletionResult.Failure -> { + } } } } @@ -170,7 +178,7 @@ class DetailsMiddleware { } } is DetailsAction.ManageSecurity.SaveChanges -> { - val cardId = store.state.detailsState.card?.cardId + val cardId = store.state.detailsState.scanResponse?.card?.cardId val selectedOption = store.state.detailsState.securityScreenState?.selectedOption scope.launch { val result = when (selectedOption) { @@ -197,9 +205,9 @@ class DetailsMiddleware { actionToLog = FirebaseAnalyticsHandler.ActionToLog.ChangeSecOptions, parameters = mapOf( FirebaseAnalyticsHandler.AnalyticsParam.NEW_SECURITY_OPTION to - (selectedOption?.name ?: "") + (selectedOption?.name ?: "") ), - card = store.state.detailsState.card + card = store.state.detailsState.scanResponse?.card ) } store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index d2622417f7..ca2b6fb4bb 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -41,7 +41,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState { private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState { return DetailsState( - card = action.scanResponse.card, + scanResponse = action.scanResponse, wallets = action.wallets, cardInfo = action.scanResponse.card.toCardInfo(), appCurrencyState = AppCurrencyState(action.fiatCurrencyName), @@ -52,8 +52,10 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsState): DetailsState { return when (action) { DetailsAction.EraseWallet.Check -> { - val notAllowedByAnyWallet = state.card?.wallets?.any { it.settings.isPermanent } ?: false - val notAllowedByCard = notAllowedByAnyWallet || state.card?.isWalletDataSupported == true + val card = state.scanResponse?.card + val notAllowedByAnyWallet = card?.wallets?.any { it.settings.isPermanent } ?: false + val notAllowedByCard = notAllowedByAnyWallet || + (card?.isWalletDataSupported == true && !state.scanResponse.isTangemNote()) val notEmpty = state.wallets.any { it.hasPendingTransactions() || it.amounts.toSendableAmounts().isNotEmpty() } @@ -121,7 +123,9 @@ private fun handleSecurityAction( state.copy(securityScreenState = SecurityScreenState(currentOption = securityOption)) } is DetailsAction.ManageSecurity.OpenSecurity -> { - if (state.card?.isStart2Coin == true) { + if (state.scanResponse?.card?.isStart2Coin == true || + state.scanResponse?.isTangemNote() == true + ) { return state.copy(securityScreenState = state.securityScreenState?.copy( allowedOptions = EnumSet.of(SecurityOption.LongTap), selectedOption = state.securityScreenState.currentOption @@ -129,7 +133,7 @@ private fun handleSecurityAction( } val allowedSecurityOptions = prepareAllowedSecurityOptions( - state.card, state.securityScreenState?.currentOption + state.scanResponse?.card, state.securityScreenState?.currentOption ) state.copy(securityScreenState = state.securityScreenState?.copy( allowedOptions = allowedSecurityOptions, @@ -154,7 +158,7 @@ private fun handleSecurityAction( state.copy( securityScreenState = state.securityScreenState?.copy( currentOption = state.securityScreenState.selectedOption, - allowedOptions = state.card?.let { + allowedOptions = state.scanResponse?.card?.let { prepareAllowedSecurityOptions( it, state.securityScreenState.selectedOption ) @@ -189,7 +193,7 @@ private fun prepareAllowedSecurityOptions( } -private fun Card.toCardInfo(): CardInfo? { +private fun Card.toCardInfo(): CardInfo { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.issuer.name val signedHashes = this.signedHashesCount() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index 40f409c680..cad9493b63 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -2,10 +2,10 @@ package com.tangem.tap.features.details.redux import android.net.Uri import com.tangem.blockchain.common.Wallet -import com.tangem.common.card.Card import com.tangem.tap.common.entities.Button import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY import com.tangem.tap.common.redux.global.FiatCurrencyName +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.network.coinmarketcap.FiatCurrency import com.tangem.tap.store @@ -14,7 +14,7 @@ import java.util.* import kotlin.properties.ReadOnlyProperty data class DetailsState( - val card: Card? = null, + val scanResponse: ScanResponse? = null, val wallets: List = emptyList(), val cardInfo: CardInfo? = null, val appCurrencyState: AppCurrencyState = AppCurrencyState(), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt index cb51e326dd..5a1864ec3f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -10,6 +10,7 @@ import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.isMultiwalletAllowed import com.tangem.tap.domain.twins.getTwinCardIdForUser import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState @@ -66,7 +67,7 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber Unit)? = null) { + if (!this::activity.isInitialized) return + + emailData.prepare(infoHolder) val fileLog = if (emailData is ScanFailsEmail) createLogFile() else null - sendTo( + activity.sendEmail( email = getSupportEmail(), subject = emailData.subject, message = emailData.joinTogether(infoHolder), - fileLog = fileLog + file = fileLog, + onFail = onFail ) } @@ -55,42 +54,6 @@ class FeedbackManager( } } - private fun sendTo(email: String, subject: String, message: String, fileLog: File? = null) { - val emailFilterIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")) - val originalIntentResults = activity.packageManager.queryIntentActivities(emailFilterIntent, 0) - val emailFilterIntentResults = activity.packageManager.queryIntentActivities(emailFilterIntent, 0) - val targetedIntents = originalIntentResults - .filter { originalResult -> - emailFilterIntentResults.any { - originalResult.activityInfo.packageName == it.activityInfo.packageName - } - } - .map { - createEmailShareIntent(email, subject, message, fileLog).apply { - setPackage(it.activityInfo.packageName) - } - } - .toMutableList() - try { - val chooserIntent = Intent.createChooser(targetedIntents.removeAt(0), "Send mail...") - chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray()) - chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ContextCompat.startActivity(activity, chooserIntent, null) - } catch (ex: ActivityNotFoundException) { - Timber.e(ex) - } - } - - private fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent { - val builder = ShareCompat.IntentBuilder.from(activity) - .setType("message/rfc822") - .setEmailTo(arrayOf(recipient)) - .setSubject(subject) - .setText(text) - file?.let { builder.setStream(FileProvider.getUriForFile(activity, "${activity.packageName}.provider", it)) } - return builder.intent - } - private fun createLogFile(): File? { return try { val file = File(activity.filesDir, "logs.txt") @@ -134,12 +97,12 @@ class TangemLogCollector : TangemSdkLogger { class AdditionalEmailInfo { class EmailWalletInfo( - var blockchain: Blockchain = Blockchain.Unknown, - var address: String = "", - var explorerLink: String = "", - var host: String = "", - // var outputsCount: String = "" - // var transactionHex: String = "" + var blockchain: Blockchain = Blockchain.Unknown, + var address: String = "", + var explorerLink: String = "", + var host: String = "", + // var outputsCount: String = "" + // var transactionHex: String = "" ) var appVersion: String = "" @@ -151,6 +114,7 @@ class AdditionalEmailInfo { // wallets internal val walletsInfo = mutableListOf() + internal val tokens = mutableMapOf>() internal var onSendErrorWalletInfo: EmailWalletInfo? = null var signedHashesCount: String = "" @@ -178,11 +142,12 @@ class AdditionalEmailInfo { cardFirmwareVersion = card.firmwareVersion.stringValue cardIssuer = card.issuer.name signedHashesCount = card.wallets - .joinToString(";") { "${it.curve?.curve} - ${it.totalSignedHashes}" } + .joinToString(";") { "${it.curve?.curve} - ${it.totalSignedHashes}" } } fun setWalletsInfo(walletManagers: List) { walletsInfo.clear() + tokens.clear() walletManagers.forEach { manager -> walletsInfo.add( EmailWalletInfo( @@ -192,6 +157,9 @@ class AdditionalEmailInfo { host = manager.currentHost ) ) + if (manager.cardTokens.isNotEmpty()) { + tokens[manager.wallet.blockchain] = manager.cardTokens + } } } @@ -235,10 +203,21 @@ class AdditionalEmailInfo { interface EmailData { val subject: String val mainMessage: String + + fun prepare(infoHolder: AdditionalEmailInfo) {} + + fun appendDelimiter(builder: StringBuilder) { + builder.append("----------\n") + } + + fun appendBlankLine(builder: StringBuilder) { + builder.append("\n") + } + fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String fun joinTogether(infoHolder: AdditionalEmailInfo): String { - return "$mainMessage\n\n\n\n\n" + + return "$mainMessage\n\n\n\n" + "Following information is optional. You can erase it if you don’t want to share it.\n" + createOptionalMessage(infoHolder) } @@ -253,6 +232,7 @@ class RateCanBeBetterEmail : EmailData { return StringBuilder().apply { appendKeyValue("Card ID", infoHolder.cardId) appendKeyValue("Blockchain", walletInfo.blockchain.fullName) + appendBlankLine(this) appendKeyValue("Phone model", infoHolder.phoneModel) appendKeyValue("OS version", infoHolder.osVersion) appendKeyValue("App version", infoHolder.appVersion) @@ -265,6 +245,7 @@ class ScanFailsEmail : EmailData { override val mainMessage: String = "Please tell us what card do you have?" override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { return StringBuilder().apply { + appendBlankLine(this) appendKeyValue("Phone model", infoHolder.phoneModel) appendKeyValue("OS version", infoHolder.osVersion) appendKeyValue("App version", infoHolder.appVersion) @@ -275,31 +256,50 @@ class ScanFailsEmail : EmailData { class SendTransactionFailedEmail(private val error: String) : EmailData { override val subject: String = "Can’t send a transaction" override val mainMessage: String = "Please tell us more about your issue. Every small detail can help." + override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo() return StringBuilder().apply { - appendKeyValue("Error", error) appendKeyValue("Card ID", infoHolder.cardId) + appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) + appendKeyValue("Signed hashes", infoHolder.signedHashesCount) + appendDelimiter(this) appendKeyValue("Blockchain", walletInfo.blockchain.fullName) appendKeyValue("Host", walletInfo.host) appendKeyValue("Token", infoHolder.token) + appendKeyValue("Error", error) + appendDelimiter(this) appendKeyValue("Source address", walletInfo.address) appendKeyValue("Destination address", infoHolder.destinationAddress) appendKeyValue("Amount", infoHolder.amount) appendKeyValue("Fee", infoHolder.fee) + appendBlankLine(this) appendKeyValue("Phone model", infoHolder.phoneModel) appendKeyValue("OS version", infoHolder.osVersion) appendKeyValue("App version", infoHolder.appVersion) - appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) - appendKeyValue("Signed hashes", infoHolder.signedHashesCount) // appendKeyValue("Transaction HEX", infoHolder.transactionHex) }.toString() } } class FeedbackEmail : EmailData { - override val subject: String = "Tangem Tap feedback" - override val mainMessage: String = "Hi Tangem," + override val subject: String + get() = if (isS2CCard) s2cSubject else tangemSubject + override val mainMessage: String + get() = if (isS2CCard) s2cMainMessage else tangemMainMessage + + private val tangemSubject = "Tangem feedback" + private val tangemMainMessage = "Hi support team," + + private val s2cSubject = "Feedback" + private val s2cMainMessage = "Hi support team," + + private var isS2CCard = false + + override fun prepare(infoHolder: AdditionalEmailInfo) { + isS2CCard = TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer) + } + override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { val builder = StringBuilder() builder.appendKeyValue("Card ID", infoHolder.cardId) @@ -307,11 +307,22 @@ class FeedbackEmail : EmailData { builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount) infoHolder.walletsInfo.forEach { + appendDelimiter(builder) builder.appendKeyValue("Blockchain", it.blockchain.fullName) builder.appendKeyValue("Host", it.host) builder.appendKeyValue("Wallet address", it.address) builder.appendKeyValue("Explorer link", it.explorerLink) } + appendBlankLine(builder) + + infoHolder.tokens.forEach { tokens -> + appendDelimiter(builder) + builder.appendKeyValue("Blockchain", tokens.key.fullName) + builder.appendKeyValue("Tokens", tokens.value.map { "${it.name} - ${it.symbol}" }.toString()) + } + appendDelimiter(builder) + appendBlankLine(builder) + // appendKeyValue("Outputs count", infoHolder.outputsCount) builder.appendKeyValue("Phone model", infoHolder.phoneModel) builder.appendKeyValue("OS version", infoHolder.osVersion) diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index 0d337c6da9..f28523564f 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -18,6 +18,7 @@ import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_onboarding_main.* import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.* import kotlinx.android.synthetic.main.layout_onboarding_home_top.* +import kotlinx.android.synthetic.main.view_bg_home.* class HomeFragment : BaseOnboardingFragment() { @@ -39,7 +40,10 @@ class HomeFragment : BaseOnboardingFragment() { val shareTransition = FragmentShareTransition( listOf( ShareElement(imv_front_card, ShareElement.imvFrontCard), - ShareElement(imv_back_card, ShareElement.imvBackCard) + ShareElement(imv_back_card, ShareElement.imvBackCard), + ShareElement(bg_circle_large), + ShareElement(bg_circle_medium), + ShareElement(bg_circle_min), ), FrontCardEnterTransition(), FrontCardExitTransition() diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index bd5cdcffff..43346530de 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -37,7 +37,7 @@ private val homeMiddleware: Middleware = { dispatch, state -> when (action) { is HomeAction.Init -> { store.dispatch(GlobalAction.RestoreAppCurrency) - store.dispatch(GlobalAction.GetMoonPayUserStatus) + store.dispatch(GlobalAction.GetMoonPayStatus) store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted())) } is HomeAction.ShouldScanCardOnResume -> { @@ -60,6 +60,7 @@ private fun handleReadCard() { } else { changeButtonState(ButtonState.PROGRESS) store.dispatch(GlobalAction.ScanCard({ scanResponse -> + store.state.globalState.tapWalletManager.updateConfigManager(scanResponse) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) if (OnboardingHelper.isOnboardingCase(scanResponse)) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt index 0b5b229bc4..a7bafedba2 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -4,7 +4,10 @@ import android.content.Context import com.google.android.material.bottomsheet.BottomSheetDialog 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.redux.AppDialog +import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.dialog_onboarding_address_info.* @@ -35,10 +38,31 @@ class AddressInfoBottomSheetDialog( tv_address.text = data.address btn_fl_copy_address.setOnClickListener { context.copyToClipboard(data.address) - stateDialog.onCopyAddress() + store.dispatchToastNotification(R.string.copy_toast_msg) } - btn_fl_explore.setOnClickListener { - stateDialog.onExploreAddress() + btn_fl_share.setOnClickListener { + store.dispatchShare(data.shareUrl) + } + tv_recieve_message.text = getQRReceiveMessage(tv_recieve_message.context, stateDialog.currency) + } +} + +fun getQRReceiveMessage(context: Context, currency: Currency): String { + return when (currency) { + is Currency.Blockchain -> { + context.getString( + R.string.address_qr_code_message_format, + currency.blockchain.fullName, + currency.currencySymbol + ) + } + is Currency.Token -> { + context.getString( + R.string.address_qr_code_message_token_format, + currency.token.name, + currency.currencySymbol, + currency.blockchain.fullName + ) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt index 7eb8b3a5fc..d9d832d5a6 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -99,10 +99,11 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { if (state.balanceCriticalError == null) { val balanceValue = state.walletBalance.value.stripZeroPlainString() - val currency = state.walletBalance.currency.currencySymbol - tv_balance_value.text = "$balanceValue $currency" + tv_balance_value.text = balanceValue + tv_balance_currency.text = state.walletBalance.currency.currencySymbol } else { tv_balance_value.text = "–" + tv_balance_currency.text = "" } } @@ -132,17 +133,29 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle)) updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet) + + btn_alternative_action.isVisible = false // temporary } private fun setupTopUpWalletState(state: OnboardingNoteState) { - btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto) - btn_main_action.setOnClickListener { - store.dispatch(OnboardingNoteAction.TopUp) - } + if (state.isBuyAllowed) { + btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto) + btn_main_action.setOnClickListener { + store.dispatch(OnboardingNoteAction.TopUp) + } - btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address) - btn_alternative_action.setOnClickListener { - store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog) + btn_alternative_action.isVisible = true + btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address) + btn_alternative_action.setOnClickListener { + store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog) + } + } else { + btn_main_action.setText(R.string.onboarding_button_receive_crypto) + btn_main_action.setOnClickListener { + store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog) + } + + btn_alternative_action.isVisible = false } tv_header.setText(R.string.onboarding_top_up_header) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 7565ff0c9b..558f5b0e5f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -134,12 +134,8 @@ private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { } is OnboardingNoteAction.ShowAddressInfoDialog -> { val addressData = noteState.walletManager?.getAddressData() ?: return - val addressWasCopied = globalState.resources.strings.addressWasCopied - val appDialog = AppDialog.AddressInfoDialog( - addressData, - onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, - onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) } - ) + + val appDialog = AppDialog.AddressInfoDialog(noteState.walletBalance.currency, addressData) store.dispatchDialogShow(appDialog) } is OnboardingNoteAction.TopUp -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt index 5bc772fa3a..150c551f0b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt @@ -2,8 +2,11 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.blockchain.common.WalletManager import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.extensions.buyIsAllowed import com.tangem.tap.features.onboarding.OnboardingWalletBalance +import com.tangem.tap.store import org.rekotlin.StateType +import kotlin.properties.ReadOnlyProperty /** [REDACTED_AUTHOR] @@ -22,6 +25,10 @@ data class OnboardingNoteState( val progress: Int get() = steps.indexOf(currentStep) + + val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property -> + store.state.globalState.moonpayStatus?.buyIsAllowed(walletBalance.currency) ?: false + } } enum class OnboardingNoteStep { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index 0072388bcb..22e479721b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -87,6 +87,8 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment { updateScanResponse(result.data) delay(DELAY_SDK_DIALOG_CLOSE) - withMainContext { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.TopUpWallet)) } + withMainContext { + when (twinCardsState.mode) { + CreateTwinWalletMode.CreateWallet -> { + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.TopUpWallet)) + } + CreateTwinWalletMode.RecreateWallet -> { + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done)) + } + } + } } is Result.Failure -> { } @@ -230,12 +239,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { is TwinCardsAction.ShowAddressInfoDialog -> { val addressData = twinCardsState.walletManager?.getAddressData() ?: return - val addressWasCopied = globalState.resources.strings.addressWasCopied - val appDialog = AppDialog.AddressInfoDialog( - addressData, - onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, - onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) } - ) + val appDialog = AppDialog.AddressInfoDialog(twinCardsState.walletBalance.currency, addressData) store.dispatchDialogShow(appDialog) } is TwinCardsAction.TopUp -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt index 22834a920e..9d68635c08 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt @@ -2,10 +2,13 @@ package com.tangem.tap.features.onboarding.products.twins.redux import com.tangem.blockchain.common.WalletManager import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.extensions.buyIsAllowed import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.features.onboarding.OnboardingWalletBalance +import com.tangem.tap.store import org.rekotlin.StateType +import kotlin.properties.ReadOnlyProperty /** [REDACTED_AUTHOR] @@ -52,6 +55,10 @@ data class TwinCardsState( val showAlert: Boolean get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet + + val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property -> + store.state.globalState.moonpayStatus?.buyIsAllowed(walletBalance.currency) ?: false + } } enum class CreateTwinWalletMode { CreateWallet, RecreateWallet } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index 81e11f7e47..33eaf7a983 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -14,7 +14,6 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.VoidCallback import com.tangem.tap.common.extensions.* import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget -import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.redux.navigation.ShareElement @@ -38,7 +37,6 @@ import kotlinx.android.synthetic.main.layout_onboarding_container_top.* import kotlinx.android.synthetic.main.view_bg_twins_welcome.* import kotlinx.android.synthetic.main.view_onboarding_progress.* import kotlinx.android.synthetic.main.view_onboarding_tv_balance.* -import org.rekotlin.Action class TwinsCardsFragment : BaseOnboardingFragment() { @@ -102,6 +100,9 @@ class TwinsCardsFragment : BaseOnboardingFragment() { imv_twin_front_card.transitionName = ShareElement.imvFrontCard imv_twin_back_card.transitionName = ShareElement.imvBackCard + + // if don't this, the bg_circle_... is overflow the app_bar + app_bar.bringToFront() } override fun subscribeToStore() { @@ -140,35 +141,42 @@ class TwinsCardsFragment : BaseOnboardingFragment() { if (state.balanceCriticalError == null) { val balanceValue = state.walletBalance.value.stripZeroPlainString() - val currency = state.walletBalance.currency.currencySymbol - tv_balance_value.text = "$balanceValue $currency" + tv_balance_value.text = balanceValue + tv_balance_currency.text = state.walletBalance.currency.currencySymbol } else { tv_balance_value.text = "–" + tv_balance_currency.text = "" } } private fun setupWelcomeOnlyState(state: TwinCardsState) { - setupWelcomeState(state, NavigationAction.NavigateTo(AppScreen.Wallet)) + setupWelcomeState(state) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.None)) + } } private fun setupWelcomeState(state: TwinCardsState) { - setupWelcomeState(state, TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet)) + setupWelcomeState(state) { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet)) } } - private fun setupWelcomeState(state: TwinCardsState, mainAction: Action) { + private fun setupWelcomeState(state: TwinCardsState, mainAction: VoidCallback) { twinsWidget.toWelcome(false) { startPostponedEnterTransition() } + onboarding_twins_welcome_bg.show() pb_state.hide() tv_header.setText(R.string.twins_onboarding_subtitle) tv_body.text = getString(R.string.twins_onboarding_description_format, state.cardNumber?.pairIndexNumber()) btn_main_action.setText(R.string.common_continue) - btn_main_action.setOnClickListener { store.dispatch(mainAction) } + btn_main_action.setOnClickListener { mainAction() } } private fun setupWarningState(state: TwinCardsState) { twinsWidget.toWelcome(false) { startPostponedEnterTransition() } + + onboarding_twins_welcome_bg.hide() pb_state.hide() chb_understand.show() @@ -186,6 +194,7 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } private fun setupCreateFirstWalletState(state: TwinCardsState) { + onboarding_twins_welcome_bg.hide() bg_circle_large.hide() bg_circle_medium.hide() bg_circle_min.hide() @@ -283,15 +292,24 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } } - btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto) - btn_main_action.setOnClickListener { - store.dispatch(TwinCardsAction.TopUp) - } + if (state.isBuyAllowed) { + btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto) + btn_main_action.setOnClickListener { + store.dispatch(TwinCardsAction.TopUp) + } - btn_alternative_action.isVisible = true - btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address) - btn_alternative_action.setOnClickListener { - store.dispatch(TwinCardsAction.ShowAddressInfoDialog) + btn_alternative_action.isVisible = true + btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address) + btn_alternative_action.setOnClickListener { + store.dispatch(TwinCardsAction.ShowAddressInfoDialog) + } + } else { + btn_main_action.setText(R.string.onboarding_button_receive_crypto) + btn_main_action.setOnClickListener { + store.dispatch(TwinCardsAction.ShowAddressInfoDialog) + } + + btn_alternative_action.isVisible = false } tv_header.setText(R.string.onboarding_top_up_header) @@ -299,13 +317,10 @@ class TwinsCardsFragment : BaseOnboardingFragment() { btnRefreshBalanceWidget.changeState(state.walletBalance.state) if (btnRefreshBalanceWidget.isShowing != true) { - postUi(300) { - btnRefreshBalanceWidget.mainView.setOnClickListener { - store.dispatch(TwinCardsAction.Balance.Update) - } + btnRefreshBalanceWidget.mainView.setOnClickListener { + store.dispatch(TwinCardsAction.Balance.Update) } } - imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt index 0020137a82..62d98c1f69 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/CreateWalletInterruptDialog.kt @@ -15,9 +15,8 @@ class CreateWalletInterruptDialog { companion object { fun create(state: TwinCardsAction.Wallet.ShowInterruptDialog, context: Context): AlertDialog { return MaterialAlertDialogBuilder(context) - .setMessage(R.string.twins_recreate_alert) - .setPositiveButton(R.string.common_ok) { _, _ -> state.onOk() } - .setNegativeButton(R.string.common_cancel) { _, _ -> } + .setMessage(R.string.onboarding_twin_exit_warning) + .setPositiveButton(R.string.warning_button_ok) { _, _ -> } .setOnDismissListener { store.dispatchDialogHide() } .create() } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index b0b9263034..2ed9fe33e0 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -58,6 +58,10 @@ sealed class TransactionExtrasAction : SendScreenActionUi { data class HandleUserInput(val data: String) : XlmMemo() } + sealed class BinanceMemo : TransactionExtrasAction() { + data class HandleUserInput(val data: String) : BinanceMemo() + } + sealed class XrpDestinationTag : TransactionExtrasAction() { data class HandleUserInput(val data: String) : XrpDestinationTag() } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt index dcf3bf5a43..3d59d0a270 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt @@ -68,9 +68,9 @@ class AmountMiddleware { } else { val amountErrors = extractErrorsForAmountField(transactionErrors) if (amountErrors.isNotEmpty()) { + transactionErrors.removeAll(amountErrors) dispatch(AmountAction.SetAmountError(createValidateTransactionError(amountErrors, walletManager))) } - transactionErrors.removeAll(amountErrors) if (transactionErrors.isNotEmpty()) { dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager))) } @@ -84,7 +84,7 @@ class AmountMiddleware { dispatch(AmountAction.SetAmount(sendState.amountState.balanceCrypto, false)) if (sendState.amountState.isCoinAmount()) { - dispatch(FeeAction.ChangeLayoutVisibility(main = true, controls = true, chipGroup = true)) + dispatch(FeeAction.ChangeLayoutVisibility(main = true, controls = true)) dispatch(FeeActionUi.ChangeIncludeFee(true)) } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt index b2523e9e7a..04d5b420d5 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt @@ -29,7 +29,7 @@ class RequestFeeMiddleware { if (!SendState.isReadyToRequestFee()) { dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.ADDRESS_OR_AMOUNT_IS_EMPTY)) - dispatch(FeeAction.ChangeLayoutVisibility(main = false, chipGroup = true)) +// dispatch(FeeAction.ChangeLayoutVisibility(main = false, chipGroup = true)) dispatch(ReceiptAction.RefreshReceipt) dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState())) return @@ -60,7 +60,7 @@ class RequestFeeMiddleware { } is Result.Failure -> { dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.REQUEST_FAILED)) - dispatch(FeeAction.ChangeLayoutVisibility(main = false, controls = false, chipGroup = false)) + dispatch(FeeAction.ChangeLayoutVisibility(main = false)) } } dispatch(AmountActionUi.CheckAmountToSend) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 26d2f8dd27..6701ac636d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -1,16 +1,17 @@ package com.tangem.tap.features.send.redux.middlewares import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError +import com.tangem.common.services.Result import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.stripZeroPlainString +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.common.redux.navigation.NavigationAction @@ -57,12 +58,12 @@ class SendMiddleware { val transactionData = appState()?.sendState?.externalTransactionData if (transactionData != null) { store.dispatchOnMain(AddressPayIdVerifyAction.AddressVerification.SetWalletAddress( - transactionData.destinationAddress, false + transactionData.destinationAddress, false )) store.dispatchOnMain(AmountActionUi.SetMainCurrency(MainCurrencyType.CRYPTO)) store.dispatchOnMain(AmountActionUi.HandleUserInput(transactionData.amount)) store.dispatchOnMain(AmountAction.SetAmount(transactionData.amount.toBigDecimal(), - false)) + false)) } } } @@ -72,8 +73,9 @@ class SendMiddleware { } } + private fun verifyAndSendTransaction( - action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit, + action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit, ) { val sendState = appState?.sendState ?: return val walletManager = sendState.walletManager ?: return @@ -105,30 +107,47 @@ private fun verifyAndSendTransaction( } else -> { sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress, - sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch) + sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch) } } } private fun sendTransaction( - action: SendActionUi.SendAmountToRecipient, - walletManager: WalletManager, - amountToSend: Amount, - feeAmount: Amount, - destinationAddress: String, - transactionExtras: TransactionExtrasState, - card: Card, - externalTransactionData: ExternalTransactionData?, - dispatch: (Action) -> Unit, + action: SendActionUi.SendAmountToRecipient, + walletManager: WalletManager, + amountToSend: Amount, + feeAmount: Amount, + destinationAddress: String, + transactionExtras: TransactionExtrasState, + card: Card, + externalTransactionData: ExternalTransactionData?, + dispatch: (Action) -> Unit, ) { dispatch(SendAction.ChangeSendButtonState(ButtonState.PROGRESS)) var txData = walletManager.createTransaction(amountToSend, feeAmount, destinationAddress) transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) } + transactionExtras.binanceMemo?.memo?.let { txData = txData.copy(extras = BinanceTransactionExtras(it.toString())) } transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it)) } scope.launch { - walletManager.update() + val updateWalletResult = walletManager.safeUpdate() + if (updateWalletResult is Result.Failure) { + when (val error = updateWalletResult.error) { + is TapError -> store.dispatchErrorNotification(error) + else -> { + val tapError = if (error.message == null) { + TapError.UnknownError + } else { + TapError.CustomError(error.message!!) + } + store.dispatchErrorNotification(tapError) + } + } + withMainContext { dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) } + return@launch + } + val isLinkedTerminal = tangemSdk.config.linkedTerminal if (card.isStart2Coin) { tangemSdk.config.linkedTerminal = false @@ -146,9 +165,9 @@ private fun sendTransaction( } val result = (walletManager as TransactionSender).send(txData, signer) withContext(Dispatchers.Main) { + tangemSdk.config.linkedTerminal = isLinkedTerminal when (result) { is SimpleResult.Success -> { - tangemSdk.config.linkedTerminal = isLinkedTerminal FirebaseAnalyticsHandler.triggerEvent( event = AnalyticsEvent.TRANSACTION_IS_SENT, card = card, @@ -254,7 +273,7 @@ fun extractErrorsForAmountField(errors: EnumSet): EnumSet { - val notAcceptable = listOf(TransactionError.FeeExceedsBalance, TransactionError.FeeExceedsBalance) + val notAcceptable = listOf(TransactionError.AmountExceedsBalance, TransactionError.FeeExceedsBalance) if (!showIntoAmountField.containsAll(notAcceptable)) showIntoAmountField.add(it) } TransactionError.InvalidAmountValue -> showIntoAmountField.add(it) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt index f5da58ab54..d0a1a13072 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt @@ -45,7 +45,7 @@ class FeeReducer : SendInternalReducer { state.copy( mainLayoutIsVisible = getVisibility(state.mainLayoutIsVisible, action.main), controlsLayoutIsVisible = getVisibility(state.controlsLayoutIsVisible, action.controls), - feeChipGroupIsVisible = getVisibility(state.mainLayoutIsVisible, action.chipGroup) + feeChipGroupIsVisible = getVisibility(state.feeChipGroupIsVisible, action.chipGroup) ) } is FeeAction.FeeCalculation.SetFeeResult -> { diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt index 0a4a4df672..bc030f2c8d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt @@ -4,7 +4,6 @@ import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Wallet import com.tangem.tap.common.extensions.scaleToFiat import com.tangem.tap.common.extensions.stripZeroPlainString -import com.tangem.tap.domain.getFirstToken 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.* @@ -40,15 +39,15 @@ class ReceiptReducer : SendInternalReducer { val wallet = sendState.walletManager?.wallet ?: return sendState val layoutType = determineLayoutType(amountState.mainCurrency.type, amountState.typeOfAmount) - val symbols = determineSymbols(wallet) + val symbols = determineSymbols(wallet, amountState.typeOfAmount) val showBlank = !SendState.isReadyToSend() val result = state.copy( - visibleTypeOfReceipt = layoutType, - mainCurrency = amountState.mainCurrency, - fiat = createFiatType(symbols, showBlank), - crypto = createCryptoType(symbols, showBlank), - tokenFiat = createTokenFiatType(symbols, showBlank), - tokenCrypto = createTokenCryptoType(symbols, showBlank) + visibleTypeOfReceipt = layoutType, + mainCurrency = amountState.mainCurrency, + fiat = createFiatType(symbols, showBlank), + crypto = createCryptoType(symbols, showBlank), + tokenFiat = createTokenFiatType(symbols, showBlank), + tokenCrypto = createTokenCryptoType(symbols, showBlank) ) return updateLastState(sendState.copy(receiptState = result), result) } @@ -65,22 +64,22 @@ class ReceiptReducer : SendInternalReducer { val amountFiat = convertToFiatPrecision(amountState.amountToSendCrypto.minus(feeCrypto)) val totalFiat = convertToFiatPrecision(amountState.amountToSendCrypto) ReceiptFiat( - amountFiat = amountFiat, - feeFiat = feeFiat, - totalFiat = totalFiat, - willSentCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), - symbols = symbols + amountFiat = amountFiat, + feeFiat = feeFiat, + totalFiat = totalFiat, + willSentCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), + symbols = symbols ) } else { val totalAmountCrypto = amountState.amountToSendCrypto.plus(feeCrypto) val amountFiat = convertToFiatPrecision(amountState.amountToSendCrypto) val totalFiat = convertToFiatPrecision(totalAmountCrypto) ReceiptFiat( - amountFiat = amountFiat, - feeFiat = feeFiat, - totalFiat = totalFiat, - willSentCrypto = totalAmountCrypto.stripZeroPlainString(), - symbols = symbols + amountFiat = amountFiat, + feeFiat = feeFiat, + totalFiat = totalFiat, + willSentCrypto = totalAmountCrypto.stripZeroPlainString(), + symbols = symbols ) } } @@ -94,22 +93,22 @@ class ReceiptReducer : SendInternalReducer { if (feeState.feeIsIncluded) { return ReceiptCrypto( - amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripZeroPlainString(), - feeCrypto = feeCrypto.stripZeroPlainString(), - totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), - feeFiat = feeFiat, - willSentFiat = convertToFiatPrecision(amountState.amountToSendCrypto), - symbols = symbols + amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripZeroPlainString(), + feeCrypto = feeCrypto.stripZeroPlainString(), + totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), + feeFiat = feeFiat, + willSentFiat = convertToFiatPrecision(amountState.amountToSendCrypto), + symbols = symbols ) } else { val totalCrypto = amountState.amountToSendCrypto.plus(feeCrypto) return ReceiptCrypto( - amountCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), - feeCrypto = feeCrypto.stripZeroPlainString(), - totalCrypto = totalCrypto.stripZeroPlainString(), - feeFiat = feeFiat, - willSentFiat = convertToFiatPrecision(totalCrypto), - symbols = symbols + amountCrypto = amountState.amountToSendCrypto.stripZeroPlainString(), + feeCrypto = feeCrypto.stripZeroPlainString(), + totalCrypto = totalCrypto.stripZeroPlainString(), + feeFiat = feeFiat, + willSentFiat = convertToFiatPrecision(totalCrypto), + symbols = symbols ) } } @@ -128,21 +127,21 @@ class ReceiptReducer : SendInternalReducer { val amountFiat = sendState.tokenConverter!!.toFiatUnscaled(tokensToSend) val totalFiat = amountFiat.plus(feeFiat) ReceiptTokenFiat( - amountFiat = amountFiat.scaleToFiat(true).stripZeroPlainString(), - feeFiat = feeFiat.scaleToFiat(true).stripZeroPlainString(), - totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), - willSentToken = tokensToSend.stripZeroPlainString(), - willSentFeeCoin = feeCoin.stripZeroPlainString(), - symbols = symbols + amountFiat = amountFiat.scaleToFiat(true).stripZeroPlainString(), + feeFiat = feeFiat.scaleToFiat(true).stripZeroPlainString(), + totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), + willSentToken = tokensToSend.stripZeroPlainString(), + willSentFeeCoin = feeCoin.stripZeroPlainString(), + symbols = symbols ) } else { ReceiptTokenFiat( - amountFiat = EMPTY, - feeFiat = EMPTY, - totalFiat = EMPTY, - willSentToken = tokensToSend.stripZeroPlainString(), - willSentFeeCoin = feeCoin.stripZeroPlainString(), - symbols = symbols + amountFiat = EMPTY, + feeFiat = EMPTY, + totalFiat = EMPTY, + willSentToken = tokensToSend.stripZeroPlainString(), + willSentFeeCoin = feeCoin.stripZeroPlainString(), + symbols = symbols ) } } @@ -160,27 +159,30 @@ class ReceiptReducer : SendInternalReducer { val totalFiat = tokenFiat.plus(feeFiat) ReceiptTokenCrypto( - amountToken = tokensToSend.stripZeroPlainString(), - feeCoin = feeCoin.stripZeroPlainString(), - totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), - symbols = symbols + amountToken = tokensToSend.stripZeroPlainString(), + feeCoin = feeCoin.stripZeroPlainString(), + totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(), + symbols = symbols ) } else { ReceiptTokenCrypto( - amountToken = tokensToSend.stripZeroPlainString(), - feeCoin = feeCoin.stripZeroPlainString(), - totalFiat = EMPTY, - symbols = symbols + amountToken = tokensToSend.stripZeroPlainString(), + feeCoin = feeCoin.stripZeroPlainString(), + totalFiat = EMPTY, + symbols = symbols ) } } - private fun determineSymbols(wallet: Wallet): ReceiptSymbols { + private fun determineSymbols(wallet: Wallet, amountType: AmountType): ReceiptSymbols { return ReceiptSymbols( - fiat = store.state.globalState.appCurrency, - crypto = wallet.blockchain.currency, - token = wallet.getFirstToken()?.symbol + fiat = store.state.globalState.appCurrency, + crypto = wallet.blockchain.currency, + token = when (amountType) { + is AmountType.Token -> amountType.token.symbol + else -> null + } ) } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt index 6ecd4514d4..4215143301 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt @@ -14,7 +14,8 @@ class TransactionExtrasReducer : SendInternalReducer { return when (action) { is Prepare -> handleInitialization(action, sendState) Release -> handleRelease(action, sendState) - is XlmMemo -> handleMemo(action, sendState, sendState.transactionExtrasState) + is XlmMemo -> handleXlmMemo(action, sendState, sendState.transactionExtrasState) + is BinanceMemo -> handleBinanceMemo(action, sendState, sendState.transactionExtrasState) is XrpDestinationTag -> handleXrpTag(action, sendState, sendState.transactionExtrasState) else -> sendState } @@ -40,6 +41,7 @@ class TransactionExtrasReducer : SendInternalReducer { } } Blockchain.Stellar -> TransactionExtrasState(xlmMemo = XlmMemoState()) + Blockchain.Binance -> TransactionExtrasState(binanceMemo = BinanceMemoState()) else -> emptyResult } return updateLastState(sendState.copy(transactionExtrasState = result), result) @@ -50,7 +52,7 @@ class TransactionExtrasReducer : SendInternalReducer { return updateLastState(sendState.copy(transactionExtrasState = result), result) } - private fun handleMemo( + private fun handleXlmMemo( action: XlmMemo, sendState: SendState, infoState: TransactionExtrasState, @@ -94,6 +96,26 @@ class TransactionExtrasReducer : SendInternalReducer { return updateLastState(sendState.copy(transactionExtrasState = result), result) } + private fun handleBinanceMemo( + action: BinanceMemo, + sendState: SendState, + infoState: TransactionExtrasState, + ): SendState { + val result = when (action) { + is BinanceMemo.HandleUserInput -> { + val tag = action.data.toBigIntegerOrNull() + if (tag != null) { + val input = InputViewValue(action.data, true) + val tagState = BinanceMemoState(input, tag) + infoState.copy(binanceMemo = tagState) + } else { + infoState + } + } + } + return updateLastState(sendState.copy(transactionExtrasState = result), result) + } + private fun handleXrpTag( action: XrpDestinationTag, sendState: SendState, diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt index 4f2e4e1a6d..e4f816c106 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt @@ -27,6 +27,7 @@ data class AddressPayIdState( data class TransactionExtrasState( val xlmMemo: XlmMemoState? = null, + val binanceMemo: BinanceMemoState? = null, val xrpDestinationTag: XrpDestinationTagState? = null ) : IdStateHolder { override val stateId: StateId = StateId.TRANSACTION_EXTRAS @@ -54,6 +55,16 @@ data class XlmMemoState( } } +data class BinanceMemoState( + val viewFieldValue: InputViewValue = InputViewValue(""), + val memo: BigInteger? = null, + val error: TransactionExtraError? = null + ) { + companion object { + val MAX_NUMBER: BigInteger = BigInteger("FFFFFFFFFFFFFFFF", 16) + } +} + // tag must contains only digits data class XrpDestinationTagState( val viewFieldValue: InputViewValue = InputViewValue(""), @@ -67,5 +78,6 @@ data class XrpDestinationTagState( enum class TransactionExtraError { INVALID_DESTINATION_TAG, - INVALID_XLM_MEMO + INVALID_XLM_MEMO, + INVALID_BINANCE_MEMO } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 4d9528605a..1f91fb98cb 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -115,7 +115,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } private fun setupTransactionExtrasLayout() { - etMemo.inputtedTextAsFlow() + etXlmMemo.inputtedTextAsFlow() .debounce(400) .filter { val info = store.state.sendState.transactionExtrasState @@ -132,6 +132,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } .onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) } .launchIn(mainScope) + + etBinanceMemo.inputtedTextAsFlow() + .debounce(400) + .filter { + val info = store.state.sendState.transactionExtrasState + info.binanceMemo?.viewFieldValue?.value != it + } + .onEach { store.dispatch(TransactionExtrasAction.BinanceMemo.HandleUserInput(it)) } + .launchIn(mainScope) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt index cb65943200..8f4b106e97 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt @@ -5,7 +5,6 @@ import androidx.appcompat.app.AlertDialog import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.feedback.SendTransactionFailedEmail import com.tangem.tap.features.send.redux.SendAction -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import com.tangem.wallet.R @@ -23,7 +22,7 @@ class SendTransactionFailsDialog { store.dispatch(GlobalAction.SendFeedback(SendTransactionFailedEmail(dialog.errorMessage))) } setPositiveButton(R.string.common_no) { _, _ -> } - setOnDismissListener { store.dispatch(WalletAction.HideDialog) } + setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) } }.create() } } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index a8987dcd0e..b90c53e080 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -7,6 +7,7 @@ import android.text.SpannableStringBuilder import android.view.View import android.view.ViewGroup import androidx.core.text.bold +import com.tangem.common.extensions.remove import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.getMessageString import com.tangem.tap.common.text.DecimalDigitsInputFilter @@ -64,19 +65,20 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber } showView(fg.xlmMemoContainer, infoState.xlmMemo) showView(fg.xrpDestinationTagContainer, infoState.xrpDestinationTag) + showView(fg.binanceMemoContainer, infoState.binanceMemo) infoState.xlmMemo?.let { - fg.etMemo.inputType = when (it.selectedMemoType) { + fg.etXlmMemo.inputType = when (it.selectedMemoType) { XlmMemoType.TEXT -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER } - if (!it.viewFieldValue.isFromUserInput) fg.etMemo.setText(it.viewFieldValue.value) + if (!it.viewFieldValue.isFromUserInput) fg.etXlmMemo.setText(it.viewFieldValue.value) if (it.error != null) { if (it.error == TransactionExtraError.INVALID_XLM_MEMO) { - fg.tilMemo.error = fg.getText(R.string.send_error_invalid_memo_id) + fg.tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo_id) } } else { - fg.tilMemo.error = null + fg.tilXlmMemo.error = null } } infoState.xrpDestinationTag?.let { @@ -91,6 +93,18 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber fg.etDestinationTag.setText(it.viewFieldValue.value) } } + infoState.binanceMemo?.let { + if (infoState.binanceMemo.error != null) { + if (infoState.binanceMemo.error == TransactionExtraError.INVALID_BINANCE_MEMO) { + fg.tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo_id) + } + } else { + fg.tilBinanceMemo.error = null + } + if (!it.viewFieldValue.isFromUserInput) { + fg.etBinanceMemo.setText(it.viewFieldValue.value) + } + } } private fun handleSendScreen(fg: BaseStoreFragment, state: SendState) { @@ -187,9 +201,13 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber fg.tvAmountCurrency.update(state.mainCurrency.currencySymbol) (fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.type) - val balanceText = fg.getString(R.string.send_balance_subtitle_format, - state.mainCurrency.currencySymbol, - state.viewBalanceValue) + val balanceText = when (state.mainCurrency.type) { + MainCurrencyType.FIAT -> fg.getString(R.string.send_balance_subtitle_format, + state.viewBalanceValue, state.mainCurrency.currencySymbol).remove(":") + MainCurrencyType.CRYPTO -> fg.getString(R.string.send_balance_subtitle_format, + state.mainCurrency.currencySymbol, state.viewBalanceValue) + } + fg.tvBalance.update(balanceText) fg.tilAmountToSend.isEnabled = state.inputIsEnabled diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt new file mode 100644 index 0000000000..98efd84540 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt @@ -0,0 +1,66 @@ +package com.tangem.tap.features.tokens.redux + +import androidx.annotation.StringRes +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token +import com.tangem.wallet.R + +sealed class CurrencyListItem { + data class TokenListItem(val token: Token) : CurrencyListItem() + data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem() + data class TitleListItem( + @StringRes val titleResId: Int, + var isContentShown: Boolean = true, + val blockchain: Blockchain? = null, + ) : CurrencyListItem() + + companion object { + fun createListOfCurrencies( + blockchains: List, + tokens: List, + ): List { + val blockchainsTitle = R.string.add_tokens_subtitle_blockchains + val ethereumTokensTitle = R.string.add_tokens_subtitle_ethereum_tokens + val bscTokensTitle = R.string.add_tokens_subtitle_bsc_tokens + val binanceTokensTitle = R.string.add_tokens_subtitle_binance_tokens + + val ethereumTokens = tokens.filter { it.blockchain == Blockchain.Ethereum } + val bscTokens = tokens.filter { it.blockchain == Blockchain.BSC } + val binanceTokens = tokens.filter { it.blockchain == Blockchain.Binance } + return listOf(TitleListItem(blockchainsTitle)) + + blockchains.map { BlockchainListItem(it) } + + listOf(TitleListItem(ethereumTokensTitle, blockchain = Blockchain.Ethereum)) + + ethereumTokens.map { TokenListItem(it) } + + listOf(TitleListItem(bscTokensTitle, blockchain = Blockchain.BSC)) + + bscTokens.map { TokenListItem(it) } + + listOf(TitleListItem(binanceTokensTitle, blockchain = Blockchain.Binance)) + + binanceTokens.map { TokenListItem(it) } + } + } +} + +fun List.removeTokensForBlockchain(blockchain: Blockchain): List { + return filterNot { + it is CurrencyListItem.TokenListItem && it.token.blockchain == blockchain + } +} + +fun List.addTokensForBlockchain( + blockchain: Blockchain, fullCurrenciesList: List, +): List { + val tokensToAdd = fullCurrenciesList.filter { + it is CurrencyListItem.TokenListItem && it.token.blockchain == blockchain + } + val indexToInsert = indexOfFirst { + it is CurrencyListItem.TitleListItem && it.blockchain == blockchain + } + 1 + return this.toMutableList().apply { addAll(indexToInsert, tokensToAdd) } +} + +fun List.toggleHeaderContentShownValue(blockchain: Blockchain) { + map { + if (it is CurrencyListItem.TitleListItem && it.blockchain == blockchain) { + it.isContentShown = !it.isContentShown + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt index e0c9abe119..1be9e7c701 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt @@ -1,7 +1,7 @@ package com.tangem.tap.features.tokens.redux +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token -import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem import com.tangem.tap.features.wallet.redux.WalletData import org.rekotlin.Action @@ -17,5 +17,6 @@ sealed class TokensAction : Action { data class SetAddedCurrencies(val wallets: List) : TokensAction() + data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index e8afbe2d62..6ecff3f595 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -3,7 +3,6 @@ package com.tangem.tap.features.tokens.redux import com.tangem.tap.common.redux.AppState import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.TapWorkarounds.isTestCard -import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem import com.tangem.tap.store import org.rekotlin.Middleware diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt index 66a7dd928b..f221424d67 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt @@ -18,7 +18,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState { val tokensState = state.tokensState return when (action) { is TokensAction.LoadCurrencies.Success -> { - tokensState.copy(currencies = action.currencies) + tokensState.copy(currencies = action.currencies, shownCurrencies = action.currencies) } is TokensAction.SetAddedCurrencies -> { tokensState.copy(addedCurrencies = action.wallets.toCardCurrencies()) @@ -28,12 +28,26 @@ private fun internalReduce(action: Action, state: AppState): TokensState { action.tokens.map { TokenWithAmount(it, null) } )) } + + is TokensAction.ToggleShowTokensForBlockchain -> { + if (action.isShown) { + val shownCurrencies = tokensState.shownCurrencies + .removeTokensForBlockchain(action.blockchain) + shownCurrencies.toggleHeaderContentShownValue(action.blockchain) + tokensState.copy(shownCurrencies = shownCurrencies) + } else { + val shownCurrencies = tokensState.shownCurrencies + .addTokensForBlockchain(action.blockchain, tokensState.currencies) + shownCurrencies.toggleHeaderContentShownValue(action.blockchain) + tokensState.copy(shownCurrencies = shownCurrencies) + } + } else -> tokensState } } private fun List.toCardCurrencies(): CardCurrencies { - val tokens = mapNotNull { (it.currency as? Currency.Token)?.token }.toSet() - val blockchains = mapNotNull { (it.currency as? Currency.Blockchain)?.blockchain }.toSet() + val tokens = mapNotNull { (it.currency as? Currency.Token)?.token }.distinct() + val blockchains = mapNotNull { (it.currency as? Currency.Blockchain)?.blockchain }.distinct() return CardCurrencies(tokens = tokens, blockchains = blockchains) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt index b3d528fe5f..ef30a92229 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt @@ -2,15 +2,14 @@ package com.tangem.tap.features.tokens.redux import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Token -import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.domain.tokens.CardCurrencies -import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem import org.rekotlin.StateType data class TokensState( val addedTokens: LinkedHashSet = LinkedHashSet(), val addedCurrencies: CardCurrencies? = null, val currencies: List = emptyList(), + val shownCurrencies: List = emptyList(), ) : StateType data class TokenWithAmount( diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 4b88b06ca5..b5d09b7074 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -73,7 +73,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), override fun newState(state: TokensState) { if (activity == null) return viewAdapter.addedCurrencies = state.addedCurrencies - viewAdapter.submitUnfilteredList(state.currencies) + viewAdapter.submitUnfilteredList(state.shownCurrencies) } override fun onOptionsItemSelected(item: MenuItem): Boolean { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt index 918dd2916a..85a8462b81 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt @@ -3,18 +3,17 @@ package com.tangem.tap.features.tokens.ui.adapters import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.annotation.StringRes import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.squareup.picasso.Picasso -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.loadCurrenciesIcon import com.tangem.tap.common.extensions.show import com.tangem.tap.domain.tokens.CardCurrencies +import com.tangem.tap.features.tokens.redux.CurrencyListItem +import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import com.tangem.wallet.R @@ -68,11 +67,11 @@ class CurrenciesAdapter : ListAdapter object DiffUtilCallback : DiffUtil.ItemCallback() { override fun areContentsTheSame( - oldItem: CurrencyListItem, newItem: CurrencyListItem + oldItem: CurrencyListItem, newItem: CurrencyListItem, ) = oldItem == newItem override fun areItemsTheSame( - oldItem: CurrencyListItem, newItem: CurrencyListItem + oldItem: CurrencyListItem, newItem: CurrencyListItem, ) = oldItem == newItem } @@ -161,38 +160,21 @@ class CurrenciesAdapter : ListAdapter class TitleViewHolder(val view: View) : RecyclerView.ViewHolder(view) { fun bind(title: CurrencyListItem.TitleListItem) { - view.tv_subtitle.text = view.getString(title.titleResId).toUpperCase(Locale.US) - } - } -} - - -sealed class CurrencyListItem { - data class TokenListItem(val token: Token) : CurrencyListItem() - data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem() - data class TitleListItem(@StringRes val titleResId: Int) : CurrencyListItem() - - companion object { - fun createListOfCurrencies( - blockchains: List, - tokens: List - ): List { - val blockchainsTitle = R.string.add_tokens_subtitle_blockchains - val ethereumTokensTitle = R.string.add_tokens_subtitle_ethereum_tokens - val bscTokensTitle = R.string.add_tokens_subtitle_bsc_tokens - val binanceTokensTitle = R.string.add_tokens_subtitle_binance_tokens - - val ethereumTokens = tokens.filter { it.blockchain == Blockchain.Ethereum } - val bscTokens = tokens.filter { it.blockchain == Blockchain.BSC } - val binanceTokens = tokens.filter { it.blockchain == Blockchain.Binance } - return listOf(TitleListItem(blockchainsTitle)) + - blockchains.map { BlockchainListItem(it) } + - listOf(TitleListItem(ethereumTokensTitle)) + - ethereumTokens.map { TokenListItem(it) } + - listOf(TitleListItem(bscTokensTitle)) + - bscTokens.map { TokenListItem(it) } + - listOf(TitleListItem(binanceTokensTitle)) + - binanceTokens.map { TokenListItem(it) } + view.tv_subtitle.text = view.getString(title.titleResId).uppercase() + + if (title.blockchain != null) { + view.cl_subtitle_container.setOnClickListener { + val rotation = if (title.isContentShown) -90f else 0f + store.dispatch(TokensAction.ToggleShowTokensForBlockchain( + title.isContentShown, title.blockchain + )) + view.iv_toggle_sublist_visibility.animate().rotation(rotation) + } + view.iv_toggle_sublist_visibility.show() + view.iv_toggle_sublist_visibility.setImageResource(R.drawable.ic_arrow_angle_down) + } else { + view.iv_toggle_sublist_visibility.hide() + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index ae0a273c14..149550727b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -9,6 +9,7 @@ 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.tokens.CardCurrencies +import com.tangem.tap.network.moonpay.MoonpayStatus import com.tangem.wallet.R import org.rekotlin.Action import java.math.BigDecimal @@ -25,7 +26,7 @@ sealed class WalletAction : Action { data class LoadWallet( - val allowToSell: Boolean? = null, val allowToBuy: Boolean? = null, + val moonpayStatus: MoonpayStatus? = null, val blockchain: Blockchain? = null, ) : WalletAction() { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 54e7f12586..96044f0e1c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -10,14 +10,16 @@ import com.tangem.tap.common.redux.StateDialog 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.extensions.buyIsAllowed +import com.tangem.tap.domain.extensions.sellIsAllowed import com.tangem.tap.domain.extensions.toSendableAmounts -import com.tangem.tap.domain.topup.TradeCryptoHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.models.toPendingTransactions import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import com.tangem.tap.network.moonpay.MoonpayStatus import com.tangem.tap.store import org.rekotlin.StateType import java.math.BigDecimal @@ -38,7 +40,6 @@ data class WalletState( val primaryBlockchain: Blockchain? = null, val primaryToken: Token? = null, val isTestnet: Boolean = false, - val tradeCryptoAllowed: TradeCryptoAvailability = TradeCryptoAvailability() ) : StateType { // if you do not delegate - the application crashes on startup, @@ -132,12 +133,39 @@ data class WalletState( fun replaceWalletInWallets(walletData: WalletData?): List { if (walletData == null) return wallets - return wallets.filter { it.currencyData.currency != walletData.currencyData.currency } + walletData + var changed = false + val updatedWallets = wallets.map { + if (it.currency == walletData.currency) { + changed = true + walletData + } else { + it + } + } + return if (changed) updatedWallets else wallets + walletData } fun replaceSomeWallets(newWallets: List): List { - val currencies = newWallets.map { it.currencyData.currency } - return wallets.filter { !currencies.contains(it.currencyData.currency) } + newWallets + val remainingWallets: MutableList = newWallets.toMutableList() + val updatedWallets = wallets.map { wallet -> + val newWallet = newWallets + .firstOrNull { wallet.currency == it.currency } + if (newWallet == null) { + wallet + } else { + remainingWallets.remove(newWallet) + newWallet + } + } + return updatedWallets + remainingWallets + } + + fun updateTradeCryptoState(moonpayStatus: MoonpayStatus?, walletData: WalletData): WalletData { + return walletData.copy(tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletData)) + } + + fun updateTradeCryptoState(moonpayStatus: MoonpayStatus?, walletDataList: List): List { + return walletDataList.map { it.copy(tradeCryptoState = TradeCryptoState.from(moonpayStatus, it)) } } fun addWalletManagers(newWalletManagers: List): WalletState { @@ -148,10 +176,6 @@ data class WalletState( } sealed class WalletDialog : StateDialog { - data class QrDialog( - val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName? - ) : WalletDialog() - data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog() object SignedHashesMultiWalletDialog : WalletDialog() object ChooseTradeActionDialog : WalletDialog() @@ -200,13 +224,16 @@ data class Artwork( data class TradeCryptoState( val sellingAllowed: Boolean = false, val buyingAllowed: Boolean = false, -) +) { + companion object { + fun from(moonpayStatus: MoonpayStatus?, walletData: WalletData): TradeCryptoState { + val status = moonpayStatus ?: return walletData.tradeCryptoState + val currency = walletData.currency -data class TradeCryptoAvailability( - val sellingAllowed: Boolean = false, - val buyingAllowed: Boolean = false, - val availableToSell: Set = TradeCryptoHelper.AVAILABLE_TO_SELL, -) + return TradeCryptoState(status.sellIsAllowed(currency), status.buyIsAllowed(currency)) + } + } +} data class WalletData( val pendingTransactions: List = emptyList(), @@ -219,7 +246,7 @@ data class WalletData( val fiatRateString: String? = null, val fiatRate: BigDecimal? = null, val mainButton: WalletMainButton = WalletMainButton.SendButton(false), - val currency: Currency? = null + val currency: Currency ) { fun shouldShowMultipleAddress(): Boolean { val listOfAddresses = walletAddresses?.list ?: return false diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 026cc26e4d..8397f148e0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -54,7 +54,11 @@ class MultiWalletMiddleware { } } store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Blockchain(action.blockchain))) - store.dispatch(WalletAction.LoadWallet(blockchain = action.blockchain)) + store.dispatch(WalletAction.LoadWallet( + moonpayStatus = globalState?.moonpayStatus, + blockchain = action.blockchain + ) + ) } is WalletAction.MultiWallet.SaveCurrencies -> { val cardId = globalState?.scanResponse?.card?.cardId diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 9ec6e10f13..a33d9dbb41 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -12,6 +12,7 @@ import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.* import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -21,6 +22,8 @@ import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.network.NetworkStateChanged +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.DispatchFunction @@ -51,9 +54,9 @@ class WalletMiddleware { is WalletAction.LoadWallet -> { scope.launch { if (action.blockchain == null) { - walletState.walletManagers.map { walletManager -> - globalState.tapWalletManager.loadWalletData(walletManager) - } + walletState.walletManagers.map { walletManager -> + async { globalState.tapWalletManager.loadWalletData(walletManager) } + }.awaitAll() } else { val walletManager = walletState.getWalletManager(action.blockchain) walletManager?.let { globalState.tapWalletManager.loadWalletData(it) } @@ -101,14 +104,14 @@ class WalletMiddleware { when (result) { is CompletionResult.Success -> { val scanNoteResponse = globalState.scanResponse?.copy(card = result.data) - scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) } + scanNoteResponse?.let { store.onCardScanned(scanNoteResponse, false) } } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> FirebaseAnalyticsHandler.logCardSdkError( error, FirebaseAnalyticsHandler.ActionToLog.CreateWallet, - card = store.state.detailsState.card + card = store.state.detailsState.scanResponse?.card ) } } @@ -191,6 +194,13 @@ class WalletMiddleware { store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) } } + is WalletAction.ShowDialog.QrCode -> { + val selectedWalletData = walletState.getWalletData(walletState.selectedWallet) ?: return + val selectedAddressData = selectedWalletData.walletAddresses?.selectedAddress ?: return + + val currency = selectedWalletData.currency + store.dispatchDialogShow(AppDialog.AddressInfoDialog(currency, selectedAddressData)) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 030c2e2ab6..e0f1fec69b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -38,11 +38,6 @@ class MultiWalletReducer { walletAddresses = createAddressList(wallet), mainButton = WalletMainButton.SendButton(false), currency = Currency.Blockchain(blockchain), - tradeCryptoState = TradeCryptoState( - sellingAllowed = state.tradeCryptoAllowed.sellingAllowed && - state.tradeCryptoAllowed.availableToSell.contains(blockchain.currency), - buyingAllowed = state.tradeCryptoAllowed.buyingAllowed - ) ) } @@ -67,11 +62,6 @@ class MultiWalletReducer { walletAddresses = createAddressList(wallet), mainButton = WalletMainButton.SendButton(false), currency = Currency.Blockchain(action.blockchain), - tradeCryptoState = TradeCryptoState( - sellingAllowed = state.tradeCryptoAllowed.sellingAllowed && - state.tradeCryptoAllowed.availableToSell.contains(action.blockchain.currency), - buyingAllowed = state.tradeCryptoAllowed.buyingAllowed - ) ) val newState = state.copy(wallets = state.replaceWalletInWallets(walletData)) if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) { @@ -115,7 +105,8 @@ class MultiWalletReducer { } ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled) + mainButton = WalletMainButton.SendButton(sendButtonEnabled), + currency = Currency.Token(action.token) ) val wallets = state.replaceWalletInWallets(newTokenWalletData) state.copy(wallets = wallets) @@ -173,10 +164,5 @@ fun Token.toWallet(state: WalletState): WalletData? { walletAddresses = walletAddresses, mainButton = WalletMainButton.SendButton(false), currency = Currency.Token(this), - tradeCryptoState = TradeCryptoState( - sellingAllowed = state.tradeCryptoAllowed.sellingAllowed && - state.tradeCryptoAllowed.availableToSell.contains(this.symbol), - buyingAllowed = state.tradeCryptoAllowed.buyingAllowed - ) ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index 145bcbcbc2..1cad2a84c4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -3,14 +3,14 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Wallet import com.tangem.common.extensions.isZero -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.toFiatString +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.domain.getFirstToken import com.tangem.tap.features.wallet.models.removeUnknownTransactions import com.tangem.tap.features.wallet.models.toPendingTransactions -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletMainButton -import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData @@ -19,39 +19,39 @@ import java.math.RoundingMode class OnWalletLoadedReducer { - fun reduce(wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null): WalletState { + fun reduce(wallet: Wallet, walletState: WalletState): WalletState { return if (!walletState.isMultiwalletAllowed) { - onSingleWalletLoaded(wallet, walletState, topUpAllowed) + onSingleWalletLoaded(wallet, walletState) } else { - onMultiWalletLoaded(wallet, walletState, topUpAllowed) + onMultiWalletLoaded(wallet, walletState) } } - private fun onMultiWalletLoaded( - wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null - ): WalletState { + private fun onMultiWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { val fiatCurrencySymbol = store.state.globalState.appCurrency - val amount = wallet.amounts[AmountType.Coin]?.value + val moonpayStatus = store.state.globalState.moonpayStatus + + val coinAmountValue = wallet.amounts[AmountType.Coin]?.value if (walletState.getWalletData(wallet.blockchain) == null) { return walletState } - val formattedAmount = amount?.toFormattedCurrencyString( + val formattedAmount = coinAmountValue?.toFormattedCurrencyString( wallet.blockchain.decimals(), wallet.blockchain.currency) val pendingTransactions = wallet.recentTransactions .toPendingTransactions(wallet.address) - val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty() + val coinSendButton = coinAmountValue?.isZero() == false && pendingTransactions.isEmpty() val balanceStatus = if (pendingTransactions.isNotEmpty()) { BalanceStatus.TransactionInProgress } else { BalanceStatus.VerifiedOnline } val walletData = walletState.getWalletData(wallet.blockchain) - ?: WalletData() - val fiatAmount = walletData.fiatRate?.let { amount?.toFiatValue(it) } - val newWalletData = walletData.copy( + + val fiatAmount = walletData?.fiatRate?.let { coinAmountValue?.toFiatValue(it) } + val newWalletData = walletData?.copy( currencyData = walletData.currencyData.copy( status = balanceStatus, currency = wallet.blockchain.fullName, currencySymbol = wallet.blockchain.currency, @@ -60,7 +60,9 @@ class OnWalletLoadedReducer { fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(fiatCurrencySymbol) ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled) + mainButton = WalletMainButton.SendButton(coinSendButton), + currency = Currency.Blockchain(wallet.blockchain), + tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletData) ) val tokens = wallet.getTokens().mapNotNull { token -> @@ -71,23 +73,24 @@ class OnWalletLoadedReducer { pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress else -> BalanceStatus.VerifiedOnline } - val tokenFiatAmount = tokenWalletData?.fiatRate?.let { rate -> - wallet.getTokenAmount(token)?.value?.toFiatValue(rate) - } + val tokenAmountValue = wallet.getTokenAmount(token)?.value + val tokenFiatAmount = tokenWalletData?.fiatRate?.let { rate -> tokenAmountValue?.toFiatValue(rate)} + + val tokenSendButton = tokenAmountValue?.isZero() == false && tokenPendingTransactions.isEmpty() tokenWalletData?.copy( currencyData = tokenWalletData.currencyData.copy( status = tokenBalanceStatus, - amount = wallet.getTokenAmount(token)?.value?.toFormattedCurrencyString( - token.decimals, token.symbol - ), + amount = tokenAmountValue?.toFormattedCurrencyString(token.decimals, token.symbol), fiatAmount = tokenFiatAmount, fiatAmountFormatted = tokenFiatAmount?.toFormattedFiatValue(fiatCurrencySymbol) ), pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled) + mainButton = WalletMainButton.SendButton(tokenSendButton), + tradeCryptoState = TradeCryptoState.from(moonpayStatus, tokenWalletData) ) } - val wallets = walletState.replaceSomeWallets((tokens + newWalletData)) + val newWallets = (tokens + newWalletData).mapNotNull { it } + val wallets = walletState.replaceSomeWallets((newWallets)) val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { ProgressState.Loading @@ -99,12 +102,12 @@ class OnWalletLoadedReducer { ) } - private fun onSingleWalletLoaded( - wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null - ): WalletState { + private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { if (wallet.blockchain != walletState.primaryBlockchain) return walletState val fiatCurrencySymbol = store.state.globalState.appCurrency + val moonpayStatus = store.state.globalState.moonpayStatus + val token = wallet.getFirstToken() val tokenData = if (token != null) { val tokenAmount = wallet.getTokenAmount(token) @@ -150,7 +153,8 @@ class OnWalletLoadedReducer { fiatAmount = fiatAmountRaw ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled) + mainButton = WalletMainButton.SendButton(sendButtonEnabled), + tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletState.primaryWallet) ) val wallets = walletData?.let { listOf(walletData) } ?: emptyList() return walletState.copy( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index ba5d6c7d56..097fbf28e3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -1,8 +1,12 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.toFiatString +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.domain.TapError @@ -12,6 +16,7 @@ import com.tangem.tap.domain.twins.TwinCardNumber import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import com.tangem.tap.store import org.rekotlin.Action import java.math.BigDecimal import java.math.RoundingMode @@ -29,6 +34,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { if (action !is WalletAction) return state.walletState + val moonpayStatus = store.state.globalState.moonpayStatus var newState = state.walletState when (action) { @@ -44,6 +50,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { WalletData( currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), mainButton = WalletMainButton.CreateWalletButton(true), + currency = Currency.Blockchain(Blockchain.Unknown) ) ) ) @@ -71,6 +78,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { wallets = listOf( WalletData( currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain), + currency = Currency.Blockchain(Blockchain.Unknown) ) ) ) @@ -95,28 +103,18 @@ private fun internalReduce(action: Action, state: AppState): WalletState { currencySymbol = wallet.currencyData.currencySymbol, ), mainButton = WalletMainButton.SendButton(false), - tradeCryptoState = TradeCryptoState( - sellingAllowed = action.allowToSell ?: wallet.tradeCryptoState.sellingAllowed && - state.walletState.tradeCryptoAllowed.availableToSell.contains(wallet.currencyData.currencySymbol), - buyingAllowed = action.allowToBuy - ?: wallet.tradeCryptoState.buyingAllowed - ) + tradeCryptoState = TradeCryptoState.from(action.moonpayStatus, wallet) ) } - val tradeCryptoAllowed = TradeCryptoAvailability( - sellingAllowed = action.allowToSell ?: false, - buyingAllowed = action.allowToBuy ?: false - ) newState = newState.copy( state = ProgressState.Loading, wallets = wallets, - tradeCryptoAllowed = tradeCryptoAllowed ) } else { val walletManager = newState.getWalletManager(action.blockchain) ?: return newState val blockchain = walletManager.wallet.blockchain val currencies = listOf(Currency.Blockchain(blockchain)) + - walletManager.cardTokens.map { Currency.Token(it) } + walletManager.cardTokens.map { Currency.Token(it) } val newWallets = newState.wallets.filter { currencies.contains(it.currency) } .map { wallet -> wallet.copy( @@ -126,16 +124,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState { currencySymbol = wallet.currencyData.currencySymbol, ), mainButton = WalletMainButton.SendButton(false), - tradeCryptoState = TradeCryptoState( - sellingAllowed = action.allowToSell ?: wallet.tradeCryptoState.sellingAllowed && - state.walletState.tradeCryptoAllowed.availableToSell.contains(wallet.currencyData.currencySymbol), - buyingAllowed = action.allowToBuy - ?: wallet.tradeCryptoState.buyingAllowed - ) + tradeCryptoState = TradeCryptoState.from(action.moonpayStatus, wallet) ) } val wallets = newState.replaceSomeWallets(newWallets) - newState = newState.copy(wallets = wallets) + newState = newState.copy(wallets = newState.updateTradeCryptoState(moonpayStatus, wallets)) } } is WalletAction.LoadWallet.Success -> newState = @@ -160,7 +153,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } newState = newState.copy( state = progressState, - wallets = wallets + wallets = newState.updateTradeCryptoState(moonpayStatus, wallets) ) } is WalletAction.LoadWallet.Failure -> { @@ -194,7 +187,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ProgressState.Done } newState = newState.copy( - state = progressState, wallets = wallets + state = progressState, + wallets = newState.updateTradeCryptoState(moonpayStatus, wallets) ) } is WalletAction.SetArtworkId -> { @@ -210,23 +204,13 @@ private fun internalReduce(action: Action, state: AppState): WalletState { newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState) is WalletAction.LoadArtwork -> { val artworkUrl = action.card.getArtworkUrl(action.artworkId) - ?: when (state.twinCardsState.cardNumber) { - TwinCardNumber.First -> Artwork.TWIN_CARD_1 - TwinCardNumber.Second -> Artwork.TWIN_CARD_2 - else -> Artwork.DEFAULT_IMG_URL - } + ?: when (state.twinCardsState.cardNumber) { + TwinCardNumber.First -> Artwork.TWIN_CARD_1 + TwinCardNumber.Second -> Artwork.TWIN_CARD_2 + else -> Artwork.DEFAULT_IMG_URL + } newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl)) } - is WalletAction.ShowDialog.QrCode -> { - val selectedWalletData = newState.getWalletData(newState.selectedWallet) - newState = newState.copy( - walletDialog = WalletDialog.QrDialog( - selectedWalletData?.walletAddresses?.selectedAddress?.shareUrl?.toQrCode(), - selectedWalletData?.walletAddresses?.selectedAddress?.shareUrl, - selectedWalletData?.currencyData?.currency - ) - ) - } is WalletAction.ShowDialog.SignedHashesMultiWalletDialog -> { newState = newState.copy(walletDialog = WalletDialog.SignedHashesMultiWalletDialog) } @@ -273,7 +257,7 @@ fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null) var indexOfSelectedWallet = 0 walletAddresses?.let { val index = - listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address } + listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address } if (index != -1) indexOfSelectedWallet = index } return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData) @@ -284,10 +268,10 @@ fun Wallet.createAddressesData(): List { // put a defaultAddress at the first place addresses.forEach { val addressData = AddressData( - it.value, - it.type, - getShareUri(it.value), - getExploreUrl(it.value) + it.value, + it.type, + getShareUri(it.value), + getExploreUrl(it.value) ) if (it.type == blockchain.defaultAddressType()) { listOfAddressData.add(0, addressData) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt index 66425c74a7..ecbad2650a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt @@ -23,14 +23,14 @@ class MultipleAddressUiHelper { return when (id) { R.id.chip_default -> { when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> BitcoinAddressType.Segwit + Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Segwit Blockchain.CardanoShelley -> CardanoAddressType.Shelley else -> null } } R.id.chip_legacy -> { when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> BitcoinAddressType.Legacy + Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Legacy Blockchain.CardanoShelley -> CardanoAddressType.Byron else -> null } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index f2072f7cf3..41a3dc9258 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -14,6 +14,7 @@ import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter @@ -84,7 +85,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS btn_share.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) } - btn_top_up.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Buy) } + btn_trade.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Buy) } btn_sell.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Sell) } } @@ -127,8 +128,6 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS srl_wallet_details.setOnRefreshListener { if (selectedWallet.currencyData.status != BalanceStatus.Loading) { store.dispatch(WalletAction.LoadWallet( - allowToBuy = selectedWallet.tradeCryptoState.buyingAllowed, - allowToSell = selectedWallet.tradeCryptoState.sellingAllowed, blockchain = selectedWallet.currency?.blockchain )) } @@ -138,7 +137,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS srl_wallet_details.isRefreshing = false } - btn_top_up.isEnabled = selectedWallet.tradeCryptoState.buyingAllowed + btn_trade.isEnabled = selectedWallet.tradeCryptoState.buyingAllowed btn_sell.show(selectedWallet.tradeCryptoState.sellingAllowed) } @@ -146,7 +145,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS Picasso.get().loadCurrenciesIcon( imageView = iv_currency, textView = tv_token_letter, - blockchain = wallet.currency?.blockchain, + blockchain = wallet.currency.blockchain, token = (wallet.currency as? Currency.Token)?.token ) } @@ -182,6 +181,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS requireContext())) } iv_qr_code.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode()) + tv_recieve_message.text = getQRReceiveMessage(tv_recieve_message.context, state.currency) } } @@ -286,7 +286,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { - R.id.meny_remove -> { + R.id.menu_remove -> { store.state.walletState.getSelectedWalletData()?.let { walletData -> store.dispatch(WalletAction.MultiWallet.RemoveWallet(walletData)) store.dispatch(WalletAction.MultiWallet.SelectWallet(null)) @@ -300,9 +300,11 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - if (store.state.walletState.canBeRemoved(store.state.walletState.getSelectedWalletData())) { - inflater.inflate(R.menu.wallet_details, menu) - } + inflater.inflate(R.menu.wallet_details, menu) + val walletCanBeRemoved = store.state.walletState.canBeRemoved( + store.state.walletState.getSelectedWalletData() + ) + menu.getItem(0).isEnabled = walletCanBeRemoved } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index b90adc2cb6..4d96d05004 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -18,7 +18,6 @@ import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.item_currency_wallet.view.* -import java.math.BigDecimal class WalletAdapter : ListAdapter(DiffUtilCallback) { @@ -28,36 +27,8 @@ class WalletAdapter } fun submitList(list: List, primaryBlockchain: Blockchain?, primaryToken: Token? = null) { - val listModified = list.toMutableList() - val primaryBlockchainWallet = when ( - val index = listModified.indexOfFirst { - (it.currency as? Currency.Blockchain)?.blockchain == primaryBlockchain - } - ) { - -1 -> null - else -> listModified.removeAt(index) - } - - - val primaryTokenWallet = if (primaryToken == null) null else when ( - val index = listModified.indexOfFirst { (it.currency as? Currency.Token)?.token == primaryToken } - ) { - -1 -> null - else -> listModified.removeAt(index) - } - - if (list.all { it.currencyData.fiatAmountFormatted == null }) { - val sortedList = listOfNotNull(primaryBlockchainWallet, primaryTokenWallet) + listModified - super.submitList(sortedList) - return - } - - val sorted = listModified.sortedWith( - compareByDescending { it.currencyData.fiatAmount ?: BigDecimal.ZERO } - .thenBy { it.currencyData.currency } - ) - val sortedList = listOfNotNull(primaryBlockchainWallet, primaryTokenWallet) + sorted - super.submitList(sortedList) + // We used this method to sort the list of currencies. Sorting is disabled for now. + super.submitList(list) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder { @@ -92,7 +63,7 @@ class WalletAdapter view.card_wallet.setOnClickListener { store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) } - val blockchain = wallet.currency?.blockchain + val blockchain = wallet.currency.blockchain val token = (wallet.currency as? Currency.Token)?.token Picasso.get().loadCurrenciesIcon( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt deleted file mode 100644 index c3c866d867..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.tangem.tap.features.wallet.ui.dialogs - -import android.app.Dialog -import android.content.Context -import android.graphics.Bitmap -import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.redux.global.CryptoCurrencyName -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.store -import com.tangem.wallet.R -import kotlinx.android.synthetic.main.dialog_qrcode.* - -class QrDialog(context: Context) : Dialog(context) { - - init { - this.setContentView(R.layout.dialog_qrcode) - } - - fun showQr(qrCode: Bitmap, shareUrl: String, currencyName: CryptoCurrencyName?) { - this.setOnDismissListener { store.dispatch(WalletAction.HideDialog) } - this.btn_done?.setOnClickListener { store.dispatch(WalletAction.HideDialog) } - - this.tv_qr_dialog_address?.text = shareUrl - this.iv_qrcode?.setImageBitmap(qrCode) - if (currencyName == null) { - this.tv_qr_dialog_header.hide() - } else { - this.tv_qr_dialog_header.show() - this.tv_qr_dialog_header.text = context.getString( - R.string.wallet_qr_title_format, currencyName - ) - } - super.show() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index 0f164965d4..496a8ff731 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -21,7 +21,7 @@ class ScanFailsDialog { setPositiveButton(R.string.alert_button_request_support) { _, _ -> store.dispatch(GlobalAction.SendFeedback(ScanFailsEmail())) } - setNegativeButton(R.string.common_cancel) { _, _ -> } + setNeutralButton(R.string.alert_troubleshooting_scan_card_ok) { _, _ -> } setOnDismissListener { store.dispatchDialogHide() } }.create() } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index 2522ba7135..cb58832ebb 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -140,14 +140,14 @@ class SingleWalletView : WalletView { store.dispatch(WalletAction.CopyAddress(addressString, fragment.requireContext())) } } - btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) } - - btn_top_up.setOnClickListener { - tradeCryptoAction(state.tradeCryptoState) + btn_show_qr.setOnClickListener { + store.dispatch(WalletAction.ShowDialog.QrCode) } + + setupTradeButton(fragment, state.tradeCryptoState) } - private fun tradeCryptoAction(tradeCryptoState: TradeCryptoState) { + private fun setupTradeButton(fragment: WalletFragment, tradeCryptoState: TradeCryptoState) { val allowedToBuy = tradeCryptoState.buyingAllowed val allowedToSell = tradeCryptoState.sellingAllowed val action = when { @@ -156,7 +156,23 @@ class SingleWalletView : WalletView { allowedToBuy && allowedToSell -> WalletAction.ShowDialog.ChooseTradeActionDialog else -> null } - if (action != null) store.dispatch(action) + val text = when { + allowedToBuy && !allowedToSell -> R.string.wallet_button_buy + !allowedToBuy && allowedToSell -> R.string.wallet_button_sell + allowedToBuy && allowedToSell -> R.string.wallet_button_trade + else -> R.string.wallet_button_trade + } + val icon = when { + allowedToBuy && !allowedToSell -> R.drawable.ic_arrow_up_short_btn + !allowedToBuy && allowedToSell -> R.drawable.ic_arrow_down_short_button + allowedToBuy && allowedToSell -> R.drawable.ic_arrows_up_down_short_btn + else -> null + } + with(fragment) { + btn_trade.text = getText(text) + btn_trade.setCompoundDrawablesWithIntrinsicBounds(0, icon ?: 0, 0, 0) + btn_trade.setOnClickListener { if (action != null) store.dispatch(action) } + } } private fun setupButtonsType(state: WalletData, fragment: WalletFragment) = with(fragment) { @@ -228,15 +244,6 @@ class SingleWalletView : WalletView { private fun handleDialogs(walletDialog: StateDialog?, fragment: WalletFragment) { val context = fragment.context ?: return when (walletDialog) { - is WalletDialog.QrDialog -> { - if (walletDialog.qrCode != null && walletDialog.shareUrl != null) { - if (dialog == null) dialog = QrDialog(context).apply { - this.showQr( - walletDialog.qrCode, walletDialog.shareUrl, walletDialog.currencyName - ) - } - } - } is WalletDialog.SelectAmountToSendDialog -> { if (dialog == null) dialog = AmountToSendDialog(context).apply { this.show(walletDialog.amounts) diff --git a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt index f3e062abf3..5d1bfbb5c2 100644 --- a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt +++ b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt @@ -4,13 +4,20 @@ import retrofit2.http.GET import retrofit2.http.Query interface MoonpayApi { - @GET( MOOONPAY_IP_ADDRESS_REQUEST_URL) + @GET(MOOONPAY_IP_ADDRESS_REQUEST_URL) suspend fun getUserStatus( - @Query("apiKey") moonpayApiKey: String + @Query("apiKey") moonpayApiKey: String, ): MoonPayUserStatus + @GET(MOOONPAY_CURRENCIES_REQUEST_URL) + suspend fun getCurrencies( + @Query("apiKey") moonpayApiKey: String, + ): List + + companion object { - const val MOOONPAY_BASE_URL = "https://api.moonpay.com/v4/" - const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "ip_address/" + const val MOOONPAY_BASE_URL = "https://api.moonpay.com/" + const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "v4/ip_address/" + const val MOOONPAY_CURRENCIES_REQUEST_URL = "v3/currencies/" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt index 9ef4b8b678..3e9696700d 100644 --- a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt +++ b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt @@ -1,24 +1,90 @@ package com.tangem.tap.network.moonpay +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import com.tangem.common.services.Result import com.tangem.common.services.performRequest import com.tangem.tap.network.createRetrofitInstance +import kotlinx.coroutines.coroutineScope class MoonpayService { private val moonpayApi: MoonpayApi by lazy { createRetrofitInstance(MoonpayApi.MOOONPAY_BASE_URL) - .create(MoonpayApi::class.java) + .create(MoonpayApi::class.java) } - suspend fun getUserStatus(moonpayApiKey: String): Result { - return performRequest { moonpayApi.getUserStatus(moonpayApiKey) } + suspend fun getMoonpayStatus(moonpayApiKey: String): Result { + return try { + coroutineScope { + val userStatusResult = performRequest { moonpayApi.getUserStatus(moonpayApiKey) } + if (userStatusResult is Result.Failure) return@coroutineScope userStatusResult + + val currenciesResult = performRequest { moonpayApi.getCurrencies(moonpayApiKey) } + if (currenciesResult is Result.Failure) return@coroutineScope currenciesResult + + val userStatus = (userStatusResult as Result.Success).data + val currencies = (currenciesResult as Result.Success).data + + val currenciesToBuy = mutableListOf() + val currenciesToSell = mutableListOf() + + currencies.forEach { currencyStatus -> + if (currencyStatus.type != "crypto" || currencyStatus.isSuspended || + !currencyStatus.supportsLiveMode + ) { + return@forEach + } + + if (userStatus.countryCode == "USA") { + if (!currencyStatus.isSupportedInUS) return@forEach + if (currencyStatus.notAllowedUSStates.contains(userStatus.stateCode)) return@forEach + } + val currencyCode = currencyStatus.code.uppercase() + currenciesToBuy.add(currencyCode) + + if (currencyStatus.isSellSupported) currenciesToSell.add(currencyCode) + } + + Result.Success(MoonpayStatus( + isBuyAllowed = userStatus.isBuyAllowed, + isSellAllowed = userStatus.isSellAllowed, + availableToBuy = if (userStatus.isBuyAllowed) currenciesToBuy else emptyList(), + availableToSell = if (userStatus.isSellAllowed) currenciesToSell else emptyList() + )) + } + } catch (error: Error) { + Result.Failure(error) + } } } +data class MoonpayStatus( + val isBuyAllowed: Boolean, + val isSellAllowed: Boolean, + val availableToBuy: List, + val availableToSell: List, +) + @JsonClass(generateAdapter = true) data class MoonPayUserStatus( val isBuyAllowed: Boolean, - val isSellAllowed: Boolean + val isSellAllowed: Boolean, + @Json(name = "isAllowed") + val isMoonpayAllowed: Boolean, + @Json(name = "alpha3") + val countryCode: String, + @Json(name = "state") + val stateCode: String, +) + +@JsonClass(generateAdapter = true) +data class MoonpayCurrencies( + val type: String, + val code: String, + val supportsLiveMode: Boolean = false, + val isSuspended: Boolean = true, + val isSupportedInUS: Boolean = false, + val isSellSupported: Boolean = false, + val notAllowedUSStates: List = emptyList(), ) \ No newline at end of file diff --git a/app/src/main/res/color/menu_item_color.xml b/app/src/main/res/color/menu_item_color.xml new file mode 100644 index 0000000000..2b59ebcebe --- /dev/null +++ b/app/src/main/res/color/menu_item_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_placeholder_black.xml b/app/src/main/res/drawable/card_placeholder_black.xml index 0fef87ee39..83fab8e0f7 100644 --- a/app/src/main/res/drawable/card_placeholder_black.xml +++ b/app/src/main/res/drawable/card_placeholder_black.xml @@ -1,126 +1,88 @@ - - - - - - - - - + android:width="214dp" + android:height="130dp" + android:viewportWidth="214" + android:viewportHeight="130"> + android:pathData="M1.043,8.273C1.019,4.256 4.265,1 8.294,1H205.706C209.735,1 213.019,4.256 213.043,8.273L213.709,121.725C213.733,125.742 210.487,128.998 206.459,128.998H9.046C5.018,128.998 1.733,125.742 1.709,121.725L1.043,8.273Z" /> - - - - - - - + android:pathData="M1.043,8.273C1.019,4.256 4.265,1 8.294,1H205.706C209.735,1 213.019,4.256 213.043,8.273L213.709,121.725C213.733,125.742 210.487,128.998 206.459,128.998H9.046C5.018,128.998 1.733,125.742 1.709,121.725L1.043,8.273Z" + android:strokeWidth="0.5" /> + android:pathData="M25.274,26.4L25.274,32.64L28.079,32.64C28.188,32.639 28.292,32.596 28.369,32.522C28.446,32.447 28.49,32.346 28.491,32.241L28.491,26.4L25.274,26.4Z" /> + android:pathData="M19,32.241C19.001,32.346 19.045,32.447 19.122,32.522C19.199,32.596 19.303,32.639 19.412,32.64L22.217,32.64L22.217,26.4L19,26.4L19,32.241Z" /> + android:pathData="M28.491,23.2L28.491,20.409C28.491,20.301 28.448,20.197 28.372,20.12C28.296,20.043 28.194,20 28.086,20L19.405,20C19.297,20 19.195,20.043 19.119,20.12C19.043,20.197 19,20.301 19,20.409L19,23.2L28.491,23.2Z" /> + android:pathData="M43.503,24.163C42.922,23.921 42.292,23.812 41.661,23.846C40.827,23.844 39.996,23.934 39.183,24.113L39.183,25.532C39.832,25.455 40.483,25.412 41.136,25.4C41.486,25.385 41.836,25.418 42.177,25.5C42.286,25.532 42.385,25.59 42.465,25.669C42.546,25.748 42.605,25.846 42.637,25.953C42.74,26.334 42.783,26.728 42.766,27.122L42.766,27.539C41.997,27.498 41.385,27.476 40.965,27.476C40.519,27.454 40.073,27.539 39.667,27.725C39.347,27.903 39.109,28.195 38.999,28.54C38.847,29.044 38.779,29.569 38.796,30.094C38.74,30.771 38.901,31.448 39.257,32.029C39.445,32.243 39.683,32.409 39.95,32.513C40.216,32.618 40.505,32.659 40.791,32.632C41.127,32.632 42.172,32.605 42.633,32.007C42.709,31.912 42.774,31.808 42.826,31.698L42.877,32.496L44.572,32.496L44.572,27.222C44.596,26.539 44.52,25.857 44.346,25.196C44.284,24.977 44.178,24.772 44.033,24.594C43.888,24.417 43.708,24.27 43.503,24.163ZM42.748,29.256C42.796,29.736 42.713,30.219 42.508,30.656C42.409,30.782 42.272,30.876 42.117,30.924C41.942,30.972 41.76,30.995 41.578,30.992C41.294,31.028 41.006,30.953 40.777,30.783C40.623,30.523 40.558,30.221 40.592,29.922C40.582,29.681 40.607,29.44 40.666,29.206C40.684,29.139 40.716,29.075 40.761,29.021C40.805,28.966 40.861,28.921 40.924,28.889C41.098,28.821 41.285,28.791 41.472,28.803L42.748,28.803L42.748,29.256Z" /> + android:pathData="M58.169,24.645C58.133,24.584 58.095,24.526 58.053,24.471C57.494,23.736 56.162,23.846 56.162,23.846C55.722,23.831 55.29,23.964 54.929,24.222C54.564,24.573 54.329,25.042 54.263,25.55C54.001,27.328 54.001,29.136 54.263,30.914C54.326,31.417 54.555,31.882 54.911,32.233C55.299,32.52 55.774,32.659 56.251,32.623C56.251,32.623 56.278,32.623 56.296,32.623C56.296,32.623 57.498,32.72 58.084,32.104C58.095,32.607 58.062,33.11 57.985,33.606C57.966,33.738 57.915,33.864 57.837,33.971C57.759,34.078 57.656,34.164 57.539,34.222C57.166,34.351 56.773,34.406 56.381,34.382L54.535,34.35L54.535,35.788C54.876,35.853 55.219,35.9 55.563,35.93C55.988,35.972 56.39,35.99 56.774,35.99C57.49,36.044 58.205,35.884 58.834,35.53C59.055,35.374 59.242,35.174 59.386,34.943C59.529,34.71 59.626,34.451 59.67,34.18C59.813,33.297 59.875,32.402 59.853,31.507L59.853,23.979L58.209,23.979L58.169,24.645ZM57.963,30.221C57.936,30.452 57.824,30.664 57.65,30.813C57.447,30.94 57.212,30.999 56.975,30.983C56.711,31.006 56.446,30.95 56.211,30.823C56.028,30.636 55.923,30.385 55.916,30.12C55.815,28.876 55.815,27.625 55.916,26.381C55.923,26.125 56.025,25.882 56.202,25.701C56.442,25.579 56.709,25.526 56.975,25.55C57.227,25.529 57.479,25.599 57.686,25.747C57.87,25.947 57.981,26.207 57.999,26.482C58.066,27.072 58.093,27.666 58.079,28.26C58.101,28.916 58.062,29.573 57.963,30.221Z" /> + android:pathData="M66.713,24.124C66.131,23.787 65.461,23.636 64.793,23.691C64.098,23.642 63.404,23.802 62.799,24.152C62.358,24.473 62.062,24.959 61.975,25.502C61.831,26.388 61.77,27.286 61.793,28.184C61.769,29.083 61.833,29.983 61.984,30.87C62.031,31.139 62.131,31.395 62.278,31.623C62.425,31.851 62.616,32.046 62.84,32.197C63.478,32.531 64.194,32.681 64.911,32.631C65.367,32.63 65.823,32.603 66.276,32.552C66.653,32.517 67.025,32.453 67.392,32.359L67.392,30.916C66.554,31.004 65.867,31.05 65.33,31.05C64.933,31.074 64.535,31.025 64.155,30.907C64.038,30.857 63.936,30.778 63.856,30.677C63.777,30.577 63.723,30.458 63.7,30.331C63.618,29.823 63.585,29.307 63.6,28.792L67.574,28.792L67.574,28.165C67.596,27.241 67.548,26.317 67.433,25.4C67.368,24.896 67.109,24.438 66.713,24.124ZM65.849,27.428L63.6,27.428C63.6,26.95 63.632,26.473 63.696,26C63.704,25.887 63.736,25.777 63.79,25.678C63.843,25.579 63.917,25.492 64.005,25.424C64.473,25.233 64.994,25.233 65.462,25.424C65.548,25.494 65.619,25.583 65.671,25.683C65.723,25.782 65.754,25.892 65.762,26.004C65.833,26.475 65.862,26.952 65.849,27.428Z" /> + android:pathData="M78.824,25.12C78.747,24.744 78.542,24.406 78.243,24.161C77.886,23.928 77.46,23.817 77.032,23.844C77.032,23.844 76.991,23.844 76.963,23.844C76.963,23.844 75.581,23.767 75.01,24.464C74.948,24.542 74.892,24.625 74.844,24.712C74.752,24.478 74.592,24.275 74.383,24.129C74.024,23.923 73.61,23.826 73.195,23.848C73.195,23.848 71.813,23.767 71.242,24.468C71.178,24.545 71.122,24.629 71.076,24.717L71.03,23.984L69.349,23.984L69.349,32.48L71.191,32.48L71.191,28.051C71.182,27.52 71.201,26.988 71.251,26.459C71.263,26.198 71.362,25.949 71.532,25.749C71.736,25.584 71.997,25.506 72.26,25.531C72.493,25.513 72.727,25.557 72.937,25.658C73.015,25.731 73.076,25.819 73.119,25.916C73.162,26.013 73.185,26.118 73.186,26.223C73.234,26.747 73.254,27.272 73.245,27.798L73.245,32.48L75.088,32.48L75.088,28.051C75.088,27.314 75.088,26.785 75.143,26.459C75.156,26.197 75.258,25.946 75.434,25.749C75.642,25.586 75.906,25.508 76.171,25.531C76.405,25.513 76.641,25.557 76.852,25.658C76.929,25.732 76.99,25.82 77.032,25.917C77.074,26.014 77.096,26.118 77.096,26.223C77.147,26.747 77.167,27.272 77.156,27.798L77.156,32.48L78.999,32.48L78.999,26.884C79.009,26.291 78.951,25.699 78.824,25.12Z" /> + android:pathData="M51.691,24.171C51.307,23.928 50.851,23.814 50.394,23.845C50.394,23.845 50.356,23.845 50.338,23.845C50.338,23.845 48.938,23.773 48.364,24.465C48.323,24.514 48.286,24.565 48.252,24.619L48.21,23.981L46.507,23.981L46.507,32.48L48.373,32.48L48.373,28.049C48.363,27.521 48.384,26.992 48.434,26.465C48.448,26.203 48.553,25.952 48.733,25.755C48.958,25.584 49.242,25.503 49.526,25.528C49.774,25.511 50.023,25.555 50.249,25.655C50.333,25.728 50.401,25.816 50.448,25.915C50.496,26.014 50.521,26.121 50.524,26.23C50.578,26.75 50.6,27.273 50.59,27.796L50.59,32.48L52.456,32.48L52.456,26.963C52.471,26.356 52.419,25.749 52.302,25.153C52.226,24.765 52.009,24.417 51.691,24.171Z" /> + android:pathData="M36.543,21.44L34.653,21.44L34.653,23.877L33.477,23.877L33.477,25.434L34.653,25.434L34.653,32.48L36.543,32.48L36.543,25.434L37.981,25.434L37.981,23.877L36.543,23.877L36.543,21.44Z" /> + android:pathData="M23.463,105.118L21.826,104.112L21.904,106H21.024L21.087,104.112L19.441,105.125L19.02,104.335L20.707,103.493L19.016,102.645L19.428,101.882L21.08,102.875L21.001,101H21.874L21.818,102.882L23.451,101.882L23.885,102.645L22.184,103.5L23.895,104.342L23.463,105.118Z" /> + android:pathData="M32.161,105.118L30.524,104.112L30.602,106H29.722L29.785,104.112L28.139,105.125L27.718,104.335L29.405,103.493L27.715,102.645L28.126,101.882L29.778,102.875L29.699,101H30.573L30.517,102.882L32.149,101.882L32.583,102.645L30.883,103.5L32.593,104.342L32.161,105.118Z" /> + android:pathData="M40.86,105.118L39.222,104.112L39.3,106H38.42L38.483,104.112L36.837,105.125L36.416,104.335L38.104,103.493L36.413,102.645L36.825,101.882L38.476,102.875L38.398,101H39.271L39.215,102.882L40.847,101.882L41.282,102.645L39.581,103.5L41.292,104.342L40.86,105.118Z" /> + android:pathData="M49.558,105.118L47.92,104.112L47.998,106H47.119L47.181,104.112L45.535,105.125L45.114,104.335L46.802,103.493L45.111,102.645L45.523,101.882L47.174,102.875L47.096,101H47.969L47.913,102.882L49.546,101.882L49.98,102.645L48.279,103.5L49.99,104.342L49.558,105.118Z" /> + android:pathData="M63.14,105.118L61.502,104.112L61.58,106H60.701L60.763,104.112L59.117,105.125L58.696,104.335L60.384,103.493L58.693,102.645L59.105,101.882L60.756,102.875L60.678,101H61.551L61.495,102.882L63.128,101.882L63.562,102.645L61.861,103.5L63.572,104.342L63.14,105.118Z" /> + android:pathData="M71.838,105.118L70.2,104.112L70.279,106H69.399L69.462,104.112L67.815,105.125L67.394,104.335L69.082,103.493L67.391,102.645L67.803,101.882L69.454,102.875L69.376,101H70.249L70.193,102.882L71.826,101.882L72.26,102.645L70.559,103.5L72.27,104.342L71.838,105.118Z" /> + android:pathData="M80.536,105.118L78.898,104.112L78.977,106H78.097L78.16,104.112L76.514,105.125L76.093,104.335L77.78,103.493L76.089,102.645L76.501,101.882L78.152,102.875L78.074,101H78.947L78.891,102.882L80.524,101.882L80.958,102.645L79.257,103.5L80.968,104.342L80.536,105.118Z" /> + android:fillColor="#A5A5A5" + android:pathData="M89.234,105.118L87.597,104.112L87.675,106H86.795L86.858,104.112L85.212,105.125L84.791,104.335L86.478,103.493L84.788,102.645L85.2,101.882L86.851,102.875L86.772,101H87.646L87.59,102.882L89.222,101.882L89.657,102.645L87.956,103.5L89.666,104.342L89.234,105.118Z" /> + android:fillColor="#A5A5A5" + android:pathData="M102.816,105.118L101.179,104.112L101.257,106H100.377L100.44,104.112L98.794,105.125L98.373,104.335L100.06,103.493L98.369,102.645L98.781,101.882L100.433,102.875L100.354,101H101.227L101.171,102.882L102.804,101.882L103.238,102.645L101.538,103.5L103.248,104.342L102.816,105.118Z" /> + android:fillColor="#A5A5A5" + android:pathData="M111.515,105.118L109.877,104.112L109.955,106H109.075L109.138,104.112L107.492,105.125L107.071,104.335L108.758,103.493L107.068,102.645L107.48,101.882L109.131,102.875L109.053,101H109.926L109.87,102.882L111.502,101.882L111.937,102.645L110.236,103.5L111.947,104.342L111.515,105.118Z" /> + android:fillColor="#A5A5A5" + android:pathData="M120.213,105.118L118.575,104.112L118.653,106H117.774L117.836,104.112L116.19,105.125L115.769,104.335L117.457,103.493L115.766,102.645L116.178,101.882L117.829,102.875L117.751,101H118.624L118.568,102.882L120.201,101.882L120.635,102.645L118.934,103.5L120.645,104.342L120.213,105.118Z" /> + android:fillColor="#A5A5A5" + android:pathData="M128.911,105.118L127.273,104.112L127.352,106H126.472L126.535,104.112L124.888,105.125L124.467,104.335L126.155,103.493L124.464,102.645L124.876,101.882L126.527,102.875L126.449,101H127.322L127.266,102.882L128.899,101.882L129.333,102.645L127.632,103.5L129.343,104.342L128.911,105.118Z" /> + android:fillColor="#A5A5A5" + android:pathData="M142.493,105.118L140.855,104.112L140.933,106H140.054L140.116,104.112L138.47,105.125L138.049,104.335L139.737,103.493L138.046,102.645L138.458,101.882L140.109,102.875L140.031,101H140.904L140.848,102.882L142.481,101.882L142.915,102.645L141.214,103.5L142.925,104.342L142.493,105.118Z" /> + android:fillColor="#A5A5A5" + android:pathData="M151.191,105.118L149.553,104.112L149.632,106H148.752L148.815,104.112L147.169,105.125L146.748,104.335L148.435,103.493L146.744,102.645L147.156,101.882L148.807,102.875L148.729,101H149.602L149.546,102.882L151.179,101.882L151.613,102.645L149.912,103.5L151.623,104.342L151.191,105.118Z" /> + android:fillColor="#A5A5A5" + android:pathData="M159.889,105.118L158.252,104.112L158.33,106H157.45L157.513,104.112L155.867,105.125L155.446,104.335L157.133,103.493L155.443,102.645L155.854,101.882L157.506,102.875L157.427,101H158.3L158.244,102.882L159.877,101.882L160.311,102.645L158.611,103.5L160.321,104.342L159.889,105.118Z" /> + android:fillColor="#A5A5A5" + android:pathData="M168.588,105.118L166.95,104.112L167.028,106H166.148L166.211,104.112L164.565,105.125L164.144,104.335L165.831,103.493L164.141,102.645L164.553,101.882L166.204,102.875L166.126,101H166.999L166.943,102.882L168.575,101.882L169.01,102.645L167.309,103.5L169.02,104.342L168.588,105.118Z" /> diff --git a/app/src/main/res/drawable/ic_arrow_angle_down.xml b/app/src/main/res/drawable/ic_arrow_angle_down.xml new file mode 100644 index 0000000000..e773e9862f --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_angle_down.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_angle_right.xml b/app/src/main/res/drawable/ic_arrow_angle_right.xml new file mode 100644 index 0000000000..c009fd11a9 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_angle_right.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_down_short_button.xml b/app/src/main/res/drawable/ic_arrow_down_short_button.xml new file mode 100644 index 0000000000..18b58e3f6c --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down_short_button.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/layout/dialog_onboarding_address_info.xml b/app/src/main/res/layout/dialog_onboarding_address_info.xml index a3c34a8f06..55617e452f 100644 --- a/app/src/main/res/layout/dialog_onboarding_address_info.xml +++ b/app/src/main/res/layout/dialog_onboarding_address_info.xml @@ -24,24 +24,26 @@ app:layout_constraintTop_toBottomOf="@+id/pseudo_toolbar" /> + app:layout_constraintBottom_toTopOf="@+id/guideline3" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/imv_qr_code" + tools:text="@string/address_qr_code_message_token_format" /> + app:layout_constraintGuide_percent="0.8" /> @@ -83,6 +85,7 @@ @@ -90,7 +93,7 @@ + android:src="@drawable/ic_share_chip" + android:tint="@color/accent" /> diff --git a/app/src/main/res/layout/fragment_add_tokens.xml b/app/src/main/res/layout/fragment_add_tokens.xml index 3ed5cbd260..792f48fc05 100644 --- a/app/src/main/res/layout/fragment_add_tokens.xml +++ b/app/src/main/res/layout/fragment_add_tokens.xml @@ -22,7 +22,7 @@ android:layout_height="?attr/actionBarSize" app:menu="@menu/popular_tokens" app:navigationIcon="@drawable/ic_baseline_arrow_back_24" - app:title="Add Currencies" /> + app:title="Add tokens" /> diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index 70c51c9eb3..90df722e29 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -43,27 +43,46 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="33dp"> + + + + + app:layout_constraintTop_toBottomOf="@id/tv_card_title" /> + + + + + - - + app:layout_constraintTop_toBottomOf="@id/tv_security_title" /> + app:layout_constraintTop_toBottomOf="@id/tv_erase_wallet" /> + @@ -187,6 +222,37 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_settings_title" /> + + + + + app:layout_constraintTop_toBottomOf="@id/tv_card_tou" /> - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_onboarding_main.xml b/app/src/main/res/layout/fragment_onboarding_main.xml index 40df33aeb5..6d567ce91b 100644 --- a/app/src/main/res/layout/fragment_onboarding_main.xml +++ b/app/src/main/res/layout/fragment_onboarding_main.xml @@ -14,6 +14,7 @@ style="@style/Widget.MaterialComponents.Toolbar.Surface" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@android:color/transparent" android:fitsSystemWindows="false" app:liftOnScroll="true"> diff --git a/app/src/main/res/layout/fragment_wallet_details.xml b/app/src/main/res/layout/fragment_wallet_details.xml index 7b61f56d8d..27da2ac50f 100644 --- a/app/src/main/res/layout/fragment_wallet_details.xml +++ b/app/src/main/res/layout/fragment_wallet_details.xml @@ -23,6 +23,7 @@ android:layout_height="?attr/actionBarSize" app:menu="@menu/wallet_details" app:navigationIcon="@drawable/ic_baseline_arrow_back_24" + android:theme="@style/ThemeOverlay.MyTheme.Toolbar" app:title=" " /> @@ -131,7 +132,7 @@ app:constraint_referenced_ids="l_wallet_details,card_pending_transaction_warning" /> + app:constraint_referenced_ids="btn_trade, btn_sell" /> diff --git a/app/src/main/res/layout/item_currency_subtitle.xml b/app/src/main/res/layout/item_currency_subtitle.xml index c4ae27800d..677c928c32 100644 --- a/app/src/main/res/layout/item_currency_subtitle.xml +++ b/app/src/main/res/layout/item_currency_subtitle.xml @@ -2,6 +2,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_currency_wallet.xml b/app/src/main/res/layout/item_currency_wallet.xml index 3b74abdffd..05af6fcb26 100644 --- a/app/src/main/res/layout/item_currency_wallet.xml +++ b/app/src/main/res/layout/item_currency_wallet.xml @@ -52,33 +52,56 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - app:layout_constraintGuide_percent="0.5"/> + app:layout_constraintGuide_percent="0.5" /> + app:layout_constraintEnd_toStartOf="@+id/tv_amount" + app:layout_constraintStart_toEndOf="@id/iv_currency" + app:layout_constraintTop_toTopOf="parent" + tools:text="Binance Smart Chain Optimal" /> + app:layout_constraintGuide_percent="0.45" + app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintStart_toEndOf="@id/iv_currency" + app:layout_constraintTop_toTopOf="@id/guideline" /> - - @@ -168,8 +171,8 @@ android:textColor="@color/darkGray2" android:textSize="14sp" app:layout_constraintBottom_toBottomOf="@id/tv_exchange_rate" - app:layout_constraintTop_toTopOf="@id/tv_exchange_rate" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/tv_exchange_rate" tools:text="0.43 USD" /> diff --git a/app/src/main/res/layout/item_popular_token.xml b/app/src/main/res/layout/item_popular_token.xml index a052651d61..976047b14b 100644 --- a/app/src/main/res/layout/item_popular_token.xml +++ b/app/src/main/res/layout/item_popular_token.xml @@ -4,9 +4,9 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="52dp" android:layout_marginBottom="1dp" - android:background="@android:color/white"> + android:background="@android:color/white" + android:minHeight="60dp"> + app:layout_constraintGuide_percent="0.5" /> + tools:text="Binance Smart Chain Optimal" /> + android:gravity="end" + android:textColor="@color/darkGray1" + tools:text="123.29837729 ADA 39487593.109342039402938049 will be sent" /> diff --git a/app/src/main/res/layout/layout_send_address_payid.xml b/app/src/main/res/layout/layout_send_address_payid.xml index 2b0f046335..232b778f22 100644 --- a/app/src/main/res/layout/layout_send_address_payid.xml +++ b/app/src/main/res/layout/layout_send_address_payid.xml @@ -140,7 +140,7 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_wallet_short_buttons.xml b/app/src/main/res/layout/layout_wallet_short_buttons.xml index be494ee95b..61217164ee 100644 --- a/app/src/main/res/layout/layout_wallet_short_buttons.xml +++ b/app/src/main/res/layout/layout_wallet_short_buttons.xml @@ -9,6 +9,7 @@ android:id="@+id/btn_scan_short" style="@style/TapBlackButton" android:layout_width="0dp" + android:layout_height="52dp" android:layout_marginStart="16dp" android:layout_marginTop="30dp" android:layout_marginBottom="33dp" @@ -17,14 +18,15 @@ android:paddingTop="7dp" android:text="@string/wallet_button_scan" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@id/btn_top_up" + app:layout_constraintEnd_toStartOf="@id/btn_trade" app:layout_constraintStart_toStartOf="parent" app:layout_constraintVertical_bias="1" /> \ No newline at end of file diff --git a/app/src/main/res/layout/lp_onboarding_done.xml b/app/src/main/res/layout/lp_onboarding_done.xml index af43383cc7..e6f1cb76fa 100644 --- a/app/src/main/res/layout/lp_onboarding_done.xml +++ b/app/src/main/res/layout/lp_onboarding_done.xml @@ -25,7 +25,7 @@ @@ -18,14 +19,16 @@ android:layout_height="650dp" android:src="@drawable/shape_circle" android:tint="@color/backgroundWhite" + android:transitionName="bg_circle_medium" android:translationX="170dp" android:translationY="-165dp" /> diff --git a/app/src/main/res/layout/view_bg_twins_welcome.xml b/app/src/main/res/layout/view_bg_twins_welcome.xml index 5202b1851b..acfee09420 100644 --- a/app/src/main/res/layout/view_bg_twins_welcome.xml +++ b/app/src/main/res/layout/view_bg_twins_welcome.xml @@ -12,6 +12,7 @@ android:scaleType="fitXY" android:src="@drawable/shape_circle" android:tint="#DEDEE0" + android:transitionName="bg_circle_large" android:translationX="-272dp" android:translationY="-200dp" app:layout_constraintStart_toStartOf="parent" @@ -25,6 +26,7 @@ android:scaleType="fitXY" android:src="@drawable/shape_circle" android:tint="#DCDCDC" + android:transitionName="bg_circle_medium" android:translationX="-254dp" android:translationY="-269dp" app:layout_constraintStart_toStartOf="parent" @@ -38,6 +40,7 @@ android:scaleType="fitXY" android:src="@drawable/shape_circle" android:tint="#D9D9D9" + android:transitionName="bg_circle_min" android:translationX="-394dp" android:translationY="-411dp" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/view_onboarding_tv_balance.xml b/app/src/main/res/layout/view_onboarding_tv_balance.xml index 2825b6d930..84aceb28fa 100644 --- a/app/src/main/res/layout/view_onboarding_tv_balance.xml +++ b/app/src/main/res/layout/view_onboarding_tv_balance.xml @@ -1,12 +1,9 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content"> - - + android:textSize="14sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/tv_balance_value" + app:layout_constraintTop_toBottomOf="@id/tv_balance" + tools:text="BTC" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/menu/wallet_details.xml b/app/src/main/res/menu/wallet_details.xml index 9622311e82..08ba8df389 100644 --- a/app/src/main/res/menu/wallet_details.xml +++ b/app/src/main/res/menu/wallet_details.xml @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index d34ea02c07..c28a51e88b 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -29,6 +29,7 @@ The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. Tap the card #%s You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet. + You\'ve tapped the same card. To create twin wallet you need to tap a second twin card. The twin address was successfully created @@ -57,6 +58,7 @@ Reason: %s. Do you want to send feedback? Are you having difficulty scanning your card? Please try to tap the card exactly as shown in the animation or request support. + I\'m okay Try again Request support Send feedback @@ -88,6 +90,8 @@ ex. USD Coin ex. USDC Added + Remove token + Token icon //Details Blockchain @@ -174,7 +178,9 @@ Get a new card Activating card - What does it mean? + How it works? + Receive crypto + Create a wallet Let’s generate all the keys on your card and create a secure wallet Create wallet @@ -190,11 +196,16 @@ Your crypto card is activated and ready to be used Continue Balance + Send only %s (%s) to this address. Sending any other currency will result in its irreversible loss. + Send only %s (%s) from %s network to this address. Sending any other currency will result in its irreversible loss. If the process of re-creating the wallet gets interrupted in any way, you’ll have to start over. + The twinning process is partly complete. You can’t exit it now. Internal error: can\'t create wallet manager + Blockchain is unreachable. Try later + Ok, Got it! diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 43173d1bad..286cddae59 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -20,6 +20,14 @@ @android:color/white + + +