From 434a8ac74f6d71d299275fec6feae67194d1e471 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Nov 2020 15:06:14 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle | 3 + app/src/main/java/com/tangem/tap/TapConfig.kt | 2 + .../tap/common/extensions/Navigation.kt | 2 + .../com/tangem/tap/common/redux/AppState.kt | 6 +- .../redux/navigation/NavigationState.kt | 2 +- .../com/tangem/tap/domain/TapWalletManager.kt | 5 +- .../com/tangem/tap/domain/TapWorkarounds.kt | 2 +- .../java/com/tangem/tap/domain/TopUpHelper.kt | 44 +++ .../tap/features/wallet/redux/WalletAction.kt | 8 +- .../features/wallet/redux/WalletMiddleware.kt | 369 ++++++++++-------- .../features/wallet/redux/WalletReducer.kt | 21 +- .../tap/features/wallet/redux/WalletState.kt | 11 +- .../tap/features/wallet/ui/WalletFragment.kt | 6 + .../features/wallet/ui/topup/TopUpFragment.kt | 63 +++ .../wallet/ui/topup/TopUpWebViewClient.kt | 31 ++ app/src/main/res/drawable/ic_arrow_up.xml | 17 + .../res/drawable/ic_arrow_up_short_btn.xml | 9 + app/src/main/res/drawable/ic_close.xml | 10 + app/src/main/res/drawable/ic_scan.xml | 15 +- .../main/res/drawable/ic_send_short_btn.xml | 9 + app/src/main/res/layout/fragment_home.xml | 2 - app/src/main/res/layout/fragment_send.xml | 1 - app/src/main/res/layout/fragment_topup.xml | 31 ++ app/src/main/res/layout/fragment_wallet.xml | 41 +- .../main/res/values/strings_untranslated.xml | 1 + app/src/main/res/values/styles.xml | 2 +- 26 files changed, 506 insertions(+), 207 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/topup/TopUpFragment.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/topup/TopUpWebViewClient.kt create mode 100644 app/src/main/res/drawable/ic_arrow_up.xml create mode 100644 app/src/main/res/drawable/ic_arrow_up_short_btn.xml create mode 100644 app/src/main/res/drawable/ic_close.xml create mode 100644 app/src/main/res/drawable/ic_send_short_btn.xml create mode 100644 app/src/main/res/layout/fragment_topup.xml diff --git a/app/build.gradle b/app/build.gradle index c80ef90109..9803ecb85c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -82,6 +82,9 @@ dependencies { } } + // WebView + implementation "androidx.browser:browser:1.2.0" + //lifecycle implementation "androidx.lifecycle:lifecycle-runtime:2.2.0" implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0" diff --git a/app/src/main/java/com/tangem/tap/TapConfig.kt b/app/src/main/java/com/tangem/tap/TapConfig.kt index 483f9e8306..a90d1d36df 100644 --- a/app/src/main/java/com/tangem/tap/TapConfig.kt +++ b/app/src/main/java/com/tangem/tap/TapConfig.kt @@ -3,4 +3,6 @@ package com.tangem.tap object TapConfig { const val usePayId: Boolean = true const val coinMarketCapKey = "f6622117-c043-47a0-8975-9d673ce484de" + const val moonPayApiKey = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE" + const val moonPayApiSecretKey = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C" } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index bb69887342..588342e8f7 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -11,6 +11,7 @@ import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment import com.tangem.tap.features.home.HomeFragment import com.tangem.tap.features.send.ui.SendFragment import com.tangem.tap.features.wallet.ui.WalletFragment +import com.tangem.tap.features.wallet.ui.topup.TopUpFragment import com.tangem.wallet.R fun FragmentActivity.openFragment(screen: AppScreen, addToBackStack: Boolean = true) { @@ -39,6 +40,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment { return when (screen) { AppScreen.Home -> HomeFragment() AppScreen.Wallet -> WalletFragment() + AppScreen.TopUp -> TopUpFragment() AppScreen.Send -> SendFragment() AppScreen.Details -> DetailsFragment() AppScreen.DetailsConfirm -> DetailsConfirmFragment() diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index 5f23825839..e60ae97fa7 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -12,8 +12,8 @@ import com.tangem.tap.features.home.redux.HomeMiddleware import com.tangem.tap.features.home.redux.HomeState import com.tangem.tap.features.send.redux.middlewares.sendMiddleware import com.tangem.tap.features.send.redux.states.SendState +import com.tangem.tap.features.wallet.redux.WalletMiddleware import com.tangem.tap.features.wallet.redux.WalletState -import com.tangem.tap.features.wallet.redux.walletMiddleware import org.rekotlin.Middleware import org.rekotlin.StateType @@ -31,7 +31,9 @@ data class AppState( fun getMiddleware(): List> { return listOf( logMiddleware, navigationMiddleware, notificationsMiddleware, globalMiddleware, - HomeMiddleware().homeMiddleware, walletMiddleware, sendMiddleware, + HomeMiddleware().homeMiddleware, + WalletMiddleware().walletMiddleware, + sendMiddleware, DetailsMiddleware().detailsMiddleware, DisclaimerMiddleware().disclaimerMiddleware ) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index 5edfad540b..33d2b18a16 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -9,4 +9,4 @@ data class NavigationState( val activity: WeakReference? = null ) : StateType -enum class AppScreen { Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer } \ No newline at end of file +enum class AppScreen { Home, Wallet, TopUp, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer } \ No newline at end of file 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 76d43787eb..c553e71540 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -56,7 +56,10 @@ class TapWalletManager { } withContext(Dispatchers.Main) { when (result) { - is Result.Success -> store.dispatch(WalletAction.UpdateWallet.Success(result.data)) + is Result.Success -> + store.dispatch(WalletAction.UpdateWallet.Success( + result.data, tapWorkarounds?.isStart2Coin != true + )) is Result.Failure -> store.dispatch(WalletAction.UpdateWallet.Failure(result.error?.localizedMessage)) } diff --git a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt index 7764650537..9dc11ee062 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt @@ -4,7 +4,7 @@ import com.tangem.commands.Card class TapWorkarounds(val card: Card) { - private val isStart2Coin: Boolean = card.cardData?.issuerName == START_2_COIN_ISSUER + val isStart2Coin: Boolean = card.cardData?.issuerName == START_2_COIN_ISSUER fun isPayIdCreationEnabled(): Boolean { if (isStart2Coin) return false diff --git a/app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt b/app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt new file mode 100644 index 0000000000..6f715de06b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt @@ -0,0 +1,44 @@ +package com.tangem.tap.domain + +import android.net.Uri +import com.tangem.tap.TapConfig +import com.tangem.tap.common.redux.global.CryptoCurrencyName +import org.spongycastle.util.encoders.Base64 +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +class TopUpHelper { + + companion object { + const val REDIRECT_URL = "https://success.tangem.com" + + private const val BASE_URL = "https://buy-staging.moonpay.io" + private const val API_KEY_PATH = "?apiKey=" + private const val CURRENCY_PATH = "¤cyCode=" + private const val WALLET_ADDRESS_PATH = "&walletAddress=" + private const val REDIRECT_URL_PATH = "&redirectUrl=" + private const val SIGNATURE_PATH = "&signature=" + + fun getUrl(cryptoCurrencyName: CryptoCurrencyName, walletAddress: String): String { + val originalQuery = API_KEY_PATH + TapConfig.moonPayApiKey.urlEncode() + + CURRENCY_PATH + cryptoCurrencyName.urlEncode() + + WALLET_ADDRESS_PATH + walletAddress.urlEncode() + + REDIRECT_URL_PATH + REDIRECT_URL.urlEncode() + val signature = createSignature(originalQuery, TapConfig.moonPayApiSecretKey) + + return BASE_URL + originalQuery + SIGNATURE_PATH + signature.urlEncode() + } + + private fun String.urlEncode(): String { + return Uri.encode(this) + } + + + private fun createSignature(data: String, key: String): String { + val sha256Hmac = Mac.getInstance("HmacSHA256") + val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256") + sha256Hmac.init(secretKey) + return Base64.toBase64String(sha256Hmac.doFinal(data.toByteArray())) + } + } +} \ No newline at end of file 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 1a573776e6..97e1752ed7 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 @@ -35,7 +35,7 @@ sealed class WalletAction : Action { object UpdateWallet : WalletAction() { object ScheduleUpdatingWallet : WalletAction() - data class Success(val wallet: Wallet) : WalletAction() + data class Success(val wallet: Wallet, val topUpAllowed: Boolean) : WalletAction() data class Failure(val errorMessage: String? = null) : WalletAction() } @@ -85,4 +85,10 @@ sealed class WalletAction : Action { data class ExploreAddress(val context: Context) : WalletAction() object CreateWallet : WalletAction() object EmptyWallet : WalletAction() + + sealed class TopUpAction : WalletAction() { + data class TopUp(val context: Context, val toolbarColor: Int?) : TopUpAction() + data class Start(val url: String, val redirectUrl: String) : TopUpAction() + data class Finish(val topUpCompleted: Boolean) : TopUpAction() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt index 5b03acb6da..48ae369426 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt @@ -18,6 +18,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.TopUpHelper import com.tangem.tap.domain.extensions.toSendableAmounts import com.tangem.tap.features.send.redux.PrepareSendScreen import com.tangem.tap.features.wallet.models.toPendingTransactions @@ -29,211 +30,239 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.rekotlin.Action import org.rekotlin.Middleware +import timber.log.Timber +class WalletMiddleware { + private val topUpMiddleware = TopUpMiddleware() -val walletMiddleware: Middleware = { dispatch, state -> - { next -> - { action -> - when (action) { - is WalletAction.LoadWallet -> { - scope.launch { - store.state.globalState.tapWalletManager.loadWalletData() - } - } - is WalletAction.LoadPayId -> { - scope.launch { - store.state.globalState.tapWalletManager.loadPayId() - } - } - is WalletAction.LoadFiatRate -> { - scope.launch { - store.state.globalState.tapWalletManager.loadFiatRate(store.state.globalState.appCurrency) - } - } - is WalletAction.CreateWallet -> { - scope.launch { - val result = tangemSdkManager.createWallet( - store.state.globalState.scanNoteResponse?.card?.cardId - ) - when (result) { - is CompletionResult.Success -> { - store.state.globalState.tapWalletManager.onCardScanned(result.data) - } - + val walletMiddleware: Middleware = { dispatch, state -> + { next -> + { action -> + when (action) { + is WalletAction.TopUpAction -> topUpMiddleware.handle(action) + is WalletAction.LoadWallet -> { + scope.launch { + store.state.globalState.tapWalletManager.loadWalletData() } } - } - is WalletAction.UpdateWallet -> { - scope.launch { store.state.globalState.tapWalletManager.updateWallet() } - } - is WalletAction.UpdateWallet.Success -> setupWalletUpdate(action.wallet) - is WalletAction.LoadWallet.Success -> { - store.dispatch(WalletAction.CheckHashesCountOnline) - if (!store.state.walletState.updatingWallet) setupWalletUpdate(action.wallet) - } - is WalletAction.CreatePayId.CompleteCreatingPayId -> { - scope.launch { - val cardId = store.state.globalState.scanNoteResponse?.card?.cardId - val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet - val publicKey = store.state.globalState.scanNoteResponse?.card?.cardPublicKey - if (cardId != null && wallet != null && publicKey != null) { - val result = PayIdManager().setPayId( - cardId, publicKey.toHexString(), - action.payId, wallet.address, wallet.blockchain + is WalletAction.LoadPayId -> { + scope.launch { + store.state.globalState.tapWalletManager.loadPayId() + } + } + is WalletAction.LoadFiatRate -> { + scope.launch { + store.state.globalState.tapWalletManager.loadFiatRate(store.state.globalState.appCurrency) + } + } + is WalletAction.CreateWallet -> { + scope.launch { + val result = tangemSdkManager.createWallet( + store.state.globalState.scanNoteResponse?.card?.cardId ) - withContext(Dispatchers.Main) { - when (result) { - is Result.Success -> - store.dispatch(WalletAction.CreatePayId.Success(action.payId)) - is Result.Failure -> { - val error = result.error as? TapError - ?: TapError.PayIdCreatingError - store.dispatch(WalletAction.CreatePayId.Failure(error)) + when (result) { + is CompletionResult.Success -> { + store.state.globalState.tapWalletManager.onCardScanned(result.data) + } + + } + } + } + is WalletAction.UpdateWallet -> { + scope.launch { store.state.globalState.tapWalletManager.updateWallet() } + } + is WalletAction.UpdateWallet.Success -> setupWalletUpdate(action.wallet) + is WalletAction.LoadWallet.Success -> { + store.dispatch(WalletAction.CheckHashesCountOnline) + if (!store.state.walletState.updatingWallet) setupWalletUpdate(action.wallet) + } + is WalletAction.CreatePayId.CompleteCreatingPayId -> { + scope.launch { + val cardId = store.state.globalState.scanNoteResponse?.card?.cardId + val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet + val publicKey = store.state.globalState.scanNoteResponse?.card?.cardPublicKey + if (cardId != null && wallet != null && publicKey != null) { + val result = PayIdManager().setPayId( + cardId, publicKey.toHexString(), + action.payId, wallet.address, wallet.blockchain + ) + withContext(Dispatchers.Main) { + when (result) { + is Result.Success -> + store.dispatch(WalletAction.CreatePayId.Success(action.payId)) + is Result.Failure -> { + val error = result.error as? TapError + ?: TapError.PayIdCreatingError + store.dispatch(WalletAction.CreatePayId.Failure(error)) + } } } } } } - } - is WalletAction.Scan -> { - scope.launch { - val result = tangemSdkManager.scanNote(analytics) - when (result) { - is CompletionResult.Success -> { - store.state.globalState.tapWalletManager - .onCardScanned(result.data, true) + is WalletAction.Scan -> { + scope.launch { + val result = tangemSdkManager.scanNote(analytics) + when (result) { + is CompletionResult.Success -> { + store.state.globalState.tapWalletManager + .onCardScanned(result.data, true) + } } } } - } - is WalletAction.LoadData -> { - scope.launch { - store.state.globalState.scanNoteResponse?.let { - store.state.globalState.tapWalletManager.loadData(it) - } - } - } - is NetworkStateChanged -> { - store.state.globalState.scanNoteResponse?.let { scanNoteResponse -> - store.dispatch(WalletAction.CheckHashesCountOnline) + is WalletAction.LoadData -> { scope.launch { - store.state.globalState.tapWalletManager.onCardScanned(scanNoteResponse) + store.state.globalState.scanNoteResponse?.let { + store.state.globalState.tapWalletManager.loadData(it) + } } } - } - is WalletAction.CopyAddress -> { - store.state.walletState.addressData?.address?.let { - action.context.copyToClipboard(it) - store.dispatch(WalletAction.CopyAddress.Success) + is NetworkStateChanged -> { + store.state.globalState.scanNoteResponse?.let { scanNoteResponse -> + store.dispatch(WalletAction.CheckHashesCountOnline) + scope.launch { + store.state.globalState.tapWalletManager.onCardScanned(scanNoteResponse) + } + } + } + is WalletAction.CopyAddress -> { + store.state.walletState.addressData?.address?.let { + action.context.copyToClipboard(it) + store.dispatch(WalletAction.CopyAddress.Success) + } + } + is WalletAction.ExploreAddress -> { + val uri = Uri.parse(store.state.walletState.addressData?.exploreUrl) + val intent = Intent(Intent.ACTION_VIEW, uri) + ContextCompat.startActivity(action.context, intent, null) + } + is WalletAction.Send -> { + val newAction = prepareSendAction(action.amount) + store.dispatch(newAction) + if (newAction is PrepareSendScreen) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) + } + } + is WalletAction.CheckIfWarningNeeded -> { + val card = store.state.globalState.scanNoteResponse?.card + val validator = store.state.globalState.scanNoteResponse?.walletManager + as? SignatureCountValidator + if (card != null && !preferencesStorage.wasCardScannedBefore(card.cardId)) { + val result = checkIfWarningNeeded(card, validator) + if (result != null) store.dispatch(WalletAction.ShowWarning(result)) + } + } + is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline() + is WalletAction.SaveCardId -> { + val cardId = store.state.globalState.scanNoteResponse?.card?.cardId + cardId?.let { preferencesStorage.saveScannedCardId(it) } } } - is WalletAction.ExploreAddress -> { - val uri = Uri.parse(store.state.walletState.addressData?.exploreUrl) - val intent = Intent(Intent.ACTION_VIEW, uri) - ContextCompat.startActivity(action.context, intent, null) - } - is WalletAction.Send -> { - val newAction = prepareSendAction(action.amount) - store.dispatch(newAction) - if (newAction is PrepareSendScreen) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) - } - } - is WalletAction.CheckIfWarningNeeded -> { - val card = store.state.globalState.scanNoteResponse?.card - val validator = store.state.globalState.scanNoteResponse?.walletManager - as? SignatureCountValidator - if (card != null && !preferencesStorage.wasCardScannedBefore(card.cardId)) { - val result = checkIfWarningNeeded(card, validator) - if (result != null) store.dispatch(WalletAction.ShowWarning(result)) - } - } - is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline() - is WalletAction.SaveCardId -> { - val cardId = store.state.globalState.scanNoteResponse?.card?.cardId - cardId?.let { preferencesStorage.saveScannedCardId(it) } - } - } - next(action) - } - } -} - -private fun setupWalletUpdate(wallet: Wallet) { - if (!wallet.recentTransactions.toPendingTransactions(wallet.address).isNullOrEmpty()) { - store.dispatch(WalletAction.UpdateWallet.ScheduleUpdatingWallet) - scope.launch(Dispatchers.IO) { - delay(10000) - withContext(Dispatchers.Main) { - store.dispatch(WalletAction.UpdateWallet) + next(action) } } } -} - -private fun prepareSendAction(amount: Amount?): Action { - return if (amount != null) { - if (amount.type == AmountType.Token) { - PrepareSendScreen(store.state.walletState.wallet?.amounts?.get(AmountType.Coin), amount) - } else { - PrepareSendScreen(amount) - } - } else { - val amounts = store.state.walletState.wallet?.amounts?.toSendableAmounts() - if (amounts?.size ?: 0 > 1) { - WalletAction.Send.ChooseCurrency(amounts) - } else { - val amountToSend = amounts?.first() - PrepareSendScreen(amountToSend) + private fun setupWalletUpdate(wallet: Wallet) { + if (!wallet.recentTransactions.toPendingTransactions(wallet.address).isNullOrEmpty()) { + store.dispatch(WalletAction.UpdateWallet.ScheduleUpdatingWallet) + scope.launch(Dispatchers.IO) { + delay(10000) + withContext(Dispatchers.Main) { + store.dispatch(WalletAction.UpdateWallet) + } + } } } -} -private fun checkIfWarningNeeded( - card: Card, signatureCountValidator: SignatureCountValidator? = null -): WarningType? { - if (card.getType() != CardType.Release) { - return WarningType.DevCard + private fun prepareSendAction(amount: Amount?): Action { + return if (amount != null) { + if (amount.type == AmountType.Token) { + PrepareSendScreen(store.state.walletState.wallet?.amounts?.get(AmountType.Coin), amount) + } else { + PrepareSendScreen(amount) + } + } else { + val amounts = store.state.walletState.wallet?.amounts?.toSendableAmounts() + if (amounts?.size ?: 0 > 1) { + WalletAction.Send.ChooseCurrency(amounts) + } else { + val amountToSend = amounts?.first() + PrepareSendScreen(amountToSend) + } + } } - return if (signatureCountValidator == null) { - if (card.walletSignedHashes ?: 0 > 0) { - WarningType.CardSignedHashesBefore + private fun checkIfWarningNeeded( + card: Card, signatureCountValidator: SignatureCountValidator? = null + ): WarningType? { + + if (card.getType() != CardType.Release) { + return WarningType.DevCard + } + + return if (signatureCountValidator == null) { + if (card.walletSignedHashes ?: 0 > 0) { + WarningType.CardSignedHashesBefore + } else { + store.dispatch(WalletAction.SaveCardId) + null + } } else { - store.dispatch(WalletAction.SaveCardId) + store.dispatch(WalletAction.NeedToCheckHashesCountOnline) null } - } else { - store.dispatch(WalletAction.NeedToCheckHashesCountOnline) - null + } + + private fun checkHashesCountOnline() { + if (store.state.walletState.hashesCountVerified != false) return + if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return + + val card = store.state.globalState.scanNoteResponse?.card + if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return + + val validator = store.state.globalState.scanNoteResponse?.walletManager + as? SignatureCountValidator + scope.launch { + val result = validator?.validateSignatureCount(card.walletSignedHashes ?: 0) + withContext(Dispatchers.Main) { + when (result) { + SimpleResult.Success -> { + store.dispatch(WalletAction.ConfirmHashesCount) + store.dispatch(WalletAction.SaveCardId) + } + is SimpleResult.Failure -> + if (result.error is BlockchainSdkError.SignatureCountNotMatched) { + store.dispatch(WalletAction.ShowWarning(WarningType.CardSignedHashesBefore)) + } else if (card.walletSignedHashes ?: 0 > 0) { + store.dispatch(WalletAction.ShowWarning(WarningType.CardSignedHashesBefore)) + } + } + } + } } } -private fun checkHashesCountOnline() { - if (store.state.walletState.hashesCountVerified != false) return - if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return +private class TopUpMiddleware { + fun handle(action: WalletAction.TopUpAction) { + when (action) { + is WalletAction.TopUpAction.TopUp -> { + val url = TopUpHelper.getUrl( + store.state.walletState.currencyData.currencySymbol!!, + store.state.walletState.addressData!!.address + ) + Timber.d(url) + store.dispatch(WalletAction.TopUpAction.Start(url, TopUpHelper.REDIRECT_URL)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.TopUp)) + } + is WalletAction.TopUpAction.Start -> { - val card = store.state.globalState.scanNoteResponse?.card - if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return - - val validator = store.state.globalState.scanNoteResponse?.walletManager - as? SignatureCountValidator - scope.launch { - val result = validator?.validateSignatureCount(card.walletSignedHashes ?: 0) - withContext(Dispatchers.Main) { - when (result) { - SimpleResult.Success -> { - store.dispatch(WalletAction.ConfirmHashesCount) - store.dispatch(WalletAction.SaveCardId) - } - is SimpleResult.Failure -> - if (result.error is BlockchainSdkError.SignatureCountNotMatched) { - store.dispatch(WalletAction.ShowWarning(WarningType.CardSignedHashesBefore)) - } else if (card.walletSignedHashes ?: 0 > 0) { - store.dispatch(WalletAction.ShowWarning(WarningType.CardSignedHashesBefore)) - } + } + is WalletAction.TopUpAction.Finish -> { + if (action.topUpCompleted) store.dispatch(WalletAction.UpdateWallet) + store.dispatch(NavigationAction.PopBackTo()) } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt index ffd86c6469..5c495ef9b1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt @@ -88,6 +88,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { currencyData = BalanceWidgetData( BalanceStatus.Loading, wallet?.blockchain?.fullName, + currencySymbol = wallet?.blockchain?.currency, token = wallet?.token?.symbol?.let { TokenData("", tokenSymbol = it) } @@ -210,11 +211,25 @@ private fun internalReduce(action: Action, state: AppState): WalletState { newState = newState.copy(hashesCountVerified = false) is WalletAction.ConfirmHashesCount -> newState = newState.copy(hashesCountVerified = true) + is WalletAction.TopUpAction -> { + newState = newState.copy(topUpState = handleTopUpActions(action, newState.topUpState)) + } } return newState } -private fun onWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { +private fun handleTopUpActions(action: WalletAction.TopUpAction, state: TopUpState): TopUpState { + return when (action) { + is WalletAction.TopUpAction.TopUp -> state + is WalletAction.TopUpAction.Start -> + state.copy(url = action.url, redirectUrl = action.redirectUrl) + is WalletAction.TopUpAction.Finish -> state.copy(url = null) + } +} + +private fun onWalletLoaded( + wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null +): WalletState { val fiatCurrencySymbol = store.state.globalState.appCurrency val token = wallet.amounts[AmountType.Token] val tokenData = if (token != null) { @@ -242,6 +257,7 @@ private fun onWalletLoaded(wallet: Wallet, walletState: WalletState): WalletStat } else { BalanceStatus.VerifiedOnline } + val topUpState = topUpAllowed?.let { TopUpState(topUpAllowed) } ?: walletState.topUpState return walletState.copy( state = ProgressState.Done, wallet = wallet, currencyData = BalanceWidgetData( @@ -252,6 +268,7 @@ private fun onWalletLoaded(wallet: Wallet, walletState: WalletState): WalletStat fiatAmount = fiatAmount ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled) + mainButton = WalletMainButton.SendButton(sendButtonEnabled), + topUpState = topUpState ) } \ No newline at end of file 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 be5c2fad71..d6c916d79e 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 @@ -21,7 +21,8 @@ data class WalletState( val payIdData: PayIdData = PayIdData(), val walletDialog: WalletDialog? = null, val updatingWallet: Boolean = false, - val mainButton: WalletMainButton = WalletMainButton.SendButton(false) + val mainButton: WalletMainButton = WalletMainButton.SendButton(false), + val topUpState: TopUpState = TopUpState() ) : StateType { val showDetails: Boolean = currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard && @@ -76,4 +77,10 @@ data class Artwork( const val SERGIO_CARD_ID = "BC01" const val MARTA_CARD_ID = "BC02" } -} \ No newline at end of file +} + +data class TopUpState( + val allowed: Boolean = true, + val url: String? = null, + val redirectUrl: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index c3e7f6c8fe..a64b97e5b3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -170,6 +170,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber store.dispatch(WalletAction.CreateWallet) } } + btn_top_up.setOnClickListener { + store.dispatch( + WalletAction.TopUpAction.TopUp(requireContext(), R.color.backgroundLightGray) + ) + } } private fun setupAddressCard(state: WalletState) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/topup/TopUpFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/topup/TopUpFragment.kt new file mode 100644 index 0000000000..20a844418f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/topup/TopUpFragment.kt @@ -0,0 +1,63 @@ +package com.tangem.tap.features.wallet.ui.topup + +import android.os.Bundle +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_topup.* +import org.rekotlin.StoreSubscriber + + +class TopUpFragment : Fragment(R.layout.fragment_topup), StoreSubscriber { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + } + }) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.walletState == newState.walletState + }.select { it.walletState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + web_view.settings.javaScriptEnabled = true + + toolbar.setNavigationOnClickListener { + store.dispatch(NavigationAction.PopBackTo()) + } + + } + + override fun newState(state: WalletState) { + if (activity == null) return + + if (state.topUpState.url != null) { + web_view.webViewClient = TopUpWebViewClient(progress_bar, state.topUpState.redirectUrl) + web_view.loadUrl(state.topUpState.url) + } + } + +} diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/topup/TopUpWebViewClient.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/topup/TopUpWebViewClient.kt new file mode 100644 index 0000000000..d728b16128 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/topup/TopUpWebViewClient.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.features.wallet.ui.topup + +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.ProgressBar +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store + +class TopUpWebViewClient( + private val progressBar: ProgressBar, private val redirectUrl: String? +) : WebViewClient() { + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + if (redirectUrl != null && url.contains(redirectUrl)) { + store.dispatch(WalletAction.TopUpAction.Finish(true)) + return true + } + view.loadUrl(url) + return true + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + progressBar.hide() + } + + init { + progressBar.show() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_up.xml b/app/src/main/res/drawable/ic_arrow_up.xml new file mode 100644 index 0000000000..34f1d028fe --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_up.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_up_short_btn.xml b/app/src/main/res/drawable/ic_arrow_up_short_btn.xml new file mode 100644 index 0000000000..5ac0fb0e46 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_up_short_btn.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000000..16d6d37dd9 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml index 7fcb008d1d..476424f743 100644 --- a/app/src/main/res/drawable/ic_scan.xml +++ b/app/src/main/res/drawable/ic_scan.xml @@ -1,15 +1,16 @@ + android:width="24dp" + android:height="21dp" + android:viewportWidth="24" + android:viewportHeight="21" + > diff --git a/app/src/main/res/drawable/ic_send_short_btn.xml b/app/src/main/res/drawable/ic_send_short_btn.xml new file mode 100644 index 0000000000..273ed56f76 --- /dev/null +++ b/app/src/main/res/drawable/ic_send_short_btn.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 5bff544835..33157a5fe6 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -63,7 +63,6 @@ android:id="@+id/btn_shop" style="@style/TapBlackButton" android:layout_width="0dp" - android:layout_height="48dp" android:layout_marginStart="16dp" android:layout_marginBottom="33dp" android:text="@string/common_no" @@ -85,7 +84,6 @@ android:id="@+id/btn_yes" style="@style/TapButtonWithIcon" android:layout_width="0dp" - android:layout_height="48dp" android:layout_marginStart="8dp" android:layout_marginEnd="16dp" android:layout_marginBottom="33dp" diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml index 35b5a31030..fca84784bc 100644 --- a/app/src/main/res/layout/fragment_send.xml +++ b/app/src/main/res/layout/fragment_send.xml @@ -104,7 +104,6 @@ android:id="@+id/btnSend" style="@style/TapButtonWithIcon" android:layout_width="match_parent" - android:layout_height="48dp" android:fontFamily="@font/saira_semi_condensed_regular" android:text="@string/send_title" app:icon="@drawable/ic_arrow_right" /> diff --git a/app/src/main/res/layout/fragment_topup.xml b/app/src/main/res/layout/fragment_topup.xml new file mode 100644 index 0000000000..8069c307f0 --- /dev/null +++ b/app/src/main/res/layout/fragment_topup.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index b09f9cb769..660b309ecb 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -49,7 +49,6 @@ android:id="@+id/iv_card" android:layout_width="match_parent" android:layout_height="wrap_content" - android:maxHeight="215dp" android:layout_gravity="center" android:layout_marginStart="16dp" android:layout_marginTop="8dp" @@ -57,6 +56,7 @@ android:layout_marginBottom="12dp" android:adjustViewBounds="true" android:elevation="3dp" + android:maxHeight="215dp" android:scaleType="fitCenter" android:src="@drawable/card_placeholder" app:layout_constraintTop_toTopOf="parent" /> @@ -90,40 +90,49 @@ android:id="@+id/btn_scan" style="@style/TapBlackButton" android:layout_width="0dp" - android:layout_height="48dp" android:layout_marginStart="16dp" android:layout_marginTop="30dp" android:text="@string/wallet_button_scan" - app:icon="@drawable/ic_scan" + android:gravity="bottom|center_horizontal" + android:drawableTop="@drawable/ic_scan" + app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/guideline" - app:layout_constraintHorizontal_bias="0.5" - app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintEnd_toStartOf="@id/btn_top_up" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/l_address" app:layout_constraintVertical_bias="1" /> - + app:layout_constraintEnd_toStartOf="@+id/btn_confirm" + app:layout_constraintStart_toEndOf="@+id/btn_scan" + app:layout_constraintTop_toBottomOf="@id/l_address" + app:layout_constraintVertical_bias="1" + android:gravity="bottom|center_horizontal" + /> diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index ee920115aa..e645e2c0d3 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -3,5 +3,6 @@ To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ Reduce by %s XTZ No, send all + Top up diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 03b88ac43d..5c6ee06378 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -36,7 +36,7 @@ @dimen/btn_corner_radius @font/saira_semi_condensed_regular 93dp - 48dp + 52dp 3dp