diff --git a/.gitignore b/.gitignore index 0fde302b00..13b2127afa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ # Local configuration file (sdk path, etc) local.properties +# Google services + + # Gradle generated files .gradle diff --git a/app/build.gradle b/app/build.gradle index 76faa09bc5..7248d6b0bc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -75,10 +75,9 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - - implementation 'com.tangem:blockchain:1.166.0' - implementation 'com.tangem:core:1.109.0' - implementation 'com.tangem:sdk:1.109.0' + implementation 'com.tangem:blockchain:develop-8' + implementation 'com.tangem:core:develop-15' + implementation 'com.tangem:sdk:develop-15' // WebView implementation "androidx.browser:browser:1.3.0" diff --git a/app/src/main/assets/erc20_tokens.json b/app/src/main/assets/erc20_tokens.json index 9b29c198bb..5f2bfeb629 100644 --- a/app/src/main/assets/erc20_tokens.json +++ b/app/src/main/assets/erc20_tokens.json @@ -629,6 +629,13 @@ "name" : "QASH", "contractAddress" : "0x618e75ac90b12c6049ba3b27f5d5f8651b0037f6" }, + { + "decimalCount" : 8, + "symbol" : "QCX", + "name" : "QuickX Protocol", + "contractAddress" : "0xf9e5af7b42d31d51677c75bbbd37c1986ec79aee", + "customIcon": "qcx" + }, { "decimalCount" : 8, "symbol" : "QRL", 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 new file mode 100644 index 0000000000..97a952841e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt @@ -0,0 +1,86 @@ +package com.tangem.tap.common.extensions + +import android.widget.ImageView +import android.widget.TextView +import com.squareup.picasso.Callback +import com.squareup.picasso.Picasso +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.IconsUtil +import com.tangem.blockchain.common.Token +import com.tangem.wallet.R + +fun Picasso.loadCurrenciesIcon( + imageView: ImageView, + textView: TextView, + token: Token? = null, + blockchain: Blockchain?, +) { + val blockchain = blockchain ?: Blockchain.Ethereum + + val url = if (token != null) { + IconsUtil.getTokenIconUri(blockchain, token) + } else { + IconsUtil.getBlockchainIconUri(blockchain) + } + + imageView.setImageDrawable(null) + imageView.colorFilter = null + textView.text = null + + when { + url != null -> { + this.load(url) + .placeholder(R.drawable.shape_circle) + ?.into(imageView, + object : Callback { + override fun onError(e: Exception?) { + setOfflineCurrencyImage(imageView, textView, token, blockchain) + } + + override fun onSuccess() { + } + }) + } + token?.symbol == QCX -> { + this.load(R.drawable.ic_qcx)?.into(imageView) + } + else -> { + setOfflineCurrencyImage(imageView, textView, token, blockchain) + } + } +} + +private const val QCX = "QCX" + +private fun setOfflineCurrencyImage( + imageView: ImageView, + textView: TextView, + token: Token?, + blockchain: Blockchain, +) { + if (token != null) { + setTokenImage(imageView, textView, token) + } else { + setBlockchainImage(imageView, textView, blockchain) + } +} + +private fun setBlockchainImage( + imageView: ImageView, + textView: TextView, + blockchain: Blockchain +) { + imageView.setImageResource(blockchain.getIconRes()) + imageView.colorFilter = null + textView.text = null +} + +private fun setTokenImage( + imageView: ImageView, + textView: TextView, + token: Token +) { + imageView.setImageResource(R.drawable.shape_circle) + imageView.setColorFilter(token.getColor()) + textView.text = token.symbol.take(1) +} 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 2a2a612757..38f45e4ca7 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 @@ -24,7 +24,12 @@ sealed class GlobalAction : Action { data class Success(val appCurrency: FiatCurrencyName) : GlobalAction() } - data class UpdateWalletSignedHashes(val walletSignedHashes: Int?) : GlobalAction() + data class UpdateWalletSignedHashes( + val walletSignedHashes: Int?, + val remainingSignatures: Int?, + val walletPublicKey: ByteArray, + ) : GlobalAction() + data class HideWarningMessage(val warning: WarningMessage) : GlobalAction() data class UpdateSecurityOptions(val securityOption: SecurityOption) : GlobalAction() 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 dce356f1be..b06dc1b5df 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 @@ -53,6 +53,9 @@ val globalMiddleware: Middleware = { dispatch, appState -> is GlobalAction.SendFeedback -> { store.state.globalState.feedbackManager?.send(action.emailData) } + is GlobalAction.UpdateWalletSignedHashes -> { + store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures)) + } } nextDispatch(action) } 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 bfc866a06c..c7ed09486a 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 @@ -1,5 +1,6 @@ package com.tangem.tap.common.redux.global +import com.tangem.commands.wallet.WalletIndex import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.SecurityOption import org.rekotlin.Action @@ -25,16 +26,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.RestoreAppCurrency.Success -> { globalState.copy(appCurrency = action.appCurrency) } - is GlobalAction.UpdateWalletSignedHashes -> { - val card = globalState.scanNoteResponse?.card?.copy( - walletSignedHashes = action.walletSignedHashes - ) - if (card != null) { - globalState.copy(scanNoteResponse = globalState.scanNoteResponse.copy(card = card)) - } else { - globalState - } - } is GlobalAction.SetConfigManager -> { globalState.copy(configManager = action.configManager) } @@ -42,13 +33,13 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.UpdateSecurityOptions -> { val card = when (action.securityOption) { SecurityOption.LongTap -> globalState.scanNoteResponse?.card?.copy( - isPin1Default = true, isPin2Default = true + isPin1Default = true, isPin2Default = true ) SecurityOption.PassCode -> globalState.scanNoteResponse?.card?.copy( - isPin1Default = true, isPin2Default = false + isPin1Default = true, isPin2Default = false ) SecurityOption.AccessCode -> globalState.scanNoteResponse?.card?.copy( - isPin1Default = false, isPin2Default = true + isPin1Default = false, isPin2Default = true ) } if (card != null) { @@ -57,6 +48,22 @@ fun globalReducer(action: Action, state: AppState): GlobalState { globalState } } + is GlobalAction.UpdateWalletSignedHashes -> { + val wallet = globalState.scanNoteResponse?.card + ?.wallet(WalletIndex.PublicKey(action.walletPublicKey)) + ?.copy( + signedHashes = action.walletSignedHashes, + remainingSignatures = action.remainingSignatures + ) + val card = globalState.scanNoteResponse?.card + wallet?.let { globalState.scanNoteResponse.card.updateWallet(wallet) } + + if (card != null) { + globalState.copy(scanNoteResponse = globalState.scanNoteResponse.copy(card = card)) + } else { + globalState + } + } is GlobalAction.SetFeedbackManager -> { globalState.copy(feedbackManager = action.feedbackManager) } diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt new file mode 100644 index 0000000000..c6cb5f22bd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt @@ -0,0 +1,48 @@ +package com.tangem.tap.common.toggleWidget + +import android.graphics.drawable.Drawable +import android.view.View +import com.google.android.material.button.MaterialButton +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show + +/** +[REDACTED_AUTHOR] + */ +open class IndeterminateProgressButtonWidget( + private val button: MaterialButton, + private val progress: View, + initialState: ProgressState = ProgressState.None +) : ViewStateWidget { + + private var initialButtonText: CharSequence = button.text + private var icon: Drawable? = null + + init { + icon = button.icon + changeState(initialState) + } + + override fun changeState(state: WidgetState) { + val progressState = state as? ProgressState ?: return + + when (progressState) { + is ProgressState.None -> switchToNone() + is ProgressState.Progress -> switchToProgress() + } + } + + protected open fun switchToNone() { + button.isClickable = true + button.icon = icon + button.text = initialButtonText + progress.hide() + } + + protected open fun switchToProgress() { + button.isClickable = false + button.icon = null + button.text = "" + progress.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/ToggleWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/ToggleWidget.kt deleted file mode 100644 index 153a5eddc8..0000000000 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/ToggleWidget.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.tangem.merchant.common.toggleWidget - -import android.view.View -import android.view.ViewGroup - - -/** -[REDACTED_AUTHOR] - */ -interface ToggleState - -interface StateModifier { - fun stateChanged(container: ViewGroup, view: View, state: ToggleState) -} - -interface ToggleView { - val mainViewModifiers: MutableList - val toggleViewModifiers: MutableList - - fun setState(state: ToggleState, andApply: Boolean = true) - fun applyState() - fun getView(): View - fun getMainView(): View - fun getToggleView(): View -} - -class ToggleWidget : ToggleView { - private val container: ViewGroup - private val mainView: View - private val toggleView: View - - private var state: ToggleState - - constructor( - container: ViewGroup, - mainView: View, - toggleView: View, - initialState: ToggleState, - mainViewModifier: List = mutableListOf(), - loadingViewModifier: List = mutableListOf() - ) { - this.container = container - this.mainView = mainView - this.toggleView = toggleView - this.state = initialState - this.mainViewModifiers.addAll(mainViewModifier) - this.toggleViewModifiers.addAll(loadingViewModifier) - } - - constructor( - container: ViewGroup, - mainViewId: Int, - toggleViewId: Int, - initialState: ToggleState, - mainViewModifier: List = mutableListOf(), - loadingViewModifier: List = mutableListOf() - ) { - this.container = container - this.mainView = container.findViewById(mainViewId) - this.toggleView = container.findViewById(toggleViewId) - this.state = initialState - this.mainViewModifiers.addAll(mainViewModifier) - this.toggleViewModifiers.addAll(loadingViewModifier) - } - - override val mainViewModifiers: MutableList = mutableListOf() - - override val toggleViewModifiers: MutableList = mutableListOf() - - override fun setState(state: ToggleState, andApply: Boolean) { - this.state = state - if (andApply) applyState() - } - - override fun applyState() { - mainViewModifiers.forEach { it.stateChanged(container, mainView, state) } - toggleViewModifiers.forEach { it.stateChanged(container, toggleView, state) } - } - - override fun getView(): View = container - - override fun getMainView(): View = mainView - - override fun getToggleView(): View = toggleView -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt new file mode 100644 index 0000000000..246268de7a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.common.toggleWidget + + +/** +[REDACTED_AUTHOR] + */ +interface WidgetState + +interface ViewStateWidget { + fun changeState(state: WidgetState) +} + +sealed class ProgressState : WidgetState { + object None : ProgressState() + data class Progress(val progress: Int = 0) : ProgressState() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/progressModifiers.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/progressModifiers.kt deleted file mode 100644 index bc66fa830b..0000000000 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/progressModifiers.kt +++ /dev/null @@ -1,145 +0,0 @@ -package com.tangem.tap.common.toggleWidget - -import android.graphics.drawable.Drawable -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.TextView -import androidx.core.widget.TextViewCompat -import com.google.android.material.button.MaterialButton -import com.tangem.merchant.common.toggleWidget.StateModifier -import com.tangem.merchant.common.toggleWidget.ToggleState -import com.tangem.tap.common.extensions.beginDelayedTransition - -/** -[REDACTED_AUTHOR] - */ - -sealed class ProgressState : ToggleState { - class Progress : ProgressState() - class None : ProgressState() -} - -class ReplaceTextStateModifier( - private val initialText: String, - private val replaceText: String = "" -) : StateModifier { - - override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { - val tv = view as? TextView ?: return - - when (state) { - is ProgressState.Progress -> { - tv.text = replaceText - } - is ProgressState.None -> { - tv.text = initialText - } - } - } -} - -class TextViewDrawableStateModifier( - private val initialDrawable: Drawable?, - private val replaceDrawable: Drawable?, - private val position: Int -) : StateModifier { - companion object { - val LEFT = 1 - val RIGHT = 2 - } - - override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { - val drawable = when (state) { - is ProgressState.Progress -> replaceDrawable - is ProgressState.None -> initialDrawable - else -> null - } - val drawableChanger = getChanger(view) - drawableChanger?.change(drawable, position) - } - - private fun getChanger(view: View): DrawableChanger? = when (view) { - is MaterialButton -> MaterialButtonChanger(view) - is Button -> TextViewChanger(view) - is TextView -> TextViewChanger(view) - else -> null - } - - internal interface DrawableChanger { - fun change(drawable: Drawable?, position: Int) - } - - internal class TextViewChanger(private val view: TextView) : DrawableChanger { - override fun change(drawable: Drawable?, position: Int) { - when (position) { - LEFT -> setLeft(drawable) - RIGHT -> setRight(drawable) - } - } - - private fun setLeft(drawable: Drawable?) { - if (drawable == null) { - TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, 0, 0, 0, 0) - } else { - TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, drawable, null, null, null) - } - } - - private fun setRight(drawable: Drawable?) { - if (drawable == null) { - TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, 0, 0, 0, 0) - } else { - TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, null, null, drawable, null) - } - } - } - - internal class MaterialButtonChanger(private val view: MaterialButton) : DrawableChanger { - override fun change(drawable: Drawable?, position: Int) { - when (position) { - LEFT -> setLeft(drawable) - RIGHT -> setRight(drawable) - } - } - - private fun setLeft(drawable: Drawable?) { - view.iconGravity = MaterialButton.ICON_GRAVITY_START - view.icon = drawable - } - - private fun setRight(drawable: Drawable?) { - view.iconGravity = MaterialButton.ICON_GRAVITY_END - view.icon = drawable - } - } -} - -class ShowHideStateModifier( - private val isShowOnLoading: Boolean = true, - private val typeOfHiding: Int = View.INVISIBLE -) : StateModifier { - - override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { - container.beginDelayedTransition() - view.visibility = when (state) { - is ProgressState.Progress -> if (isShowOnLoading) View.VISIBLE else typeOfHiding - is ProgressState.None -> if (isShowOnLoading) typeOfHiding else View.VISIBLE - else -> return - } - } -} - -class ClickableStateModifier( - private val isClickableOnLoading: Boolean = false -) : StateModifier { - - override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { - view.isClickable = when (state) { - is ProgressState.Progress -> isClickableOnLoading - is ProgressState.None -> !isClickableOnLoading - else -> return - - } - } -} \ No newline at end of file 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 77d26702ab..d4ce5e4b9f 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -2,14 +2,21 @@ package com.tangem.tap.domain import androidx.activity.ComponentActivity import com.tangem.* -import com.tangem.commands.* +import com.tangem.commands.CommandResponse +import com.tangem.commands.PinType +import com.tangem.commands.SetPinCommand +import com.tangem.commands.SetPinResponse import com.tangem.commands.common.card.Card import com.tangem.commands.common.card.CardType +import com.tangem.commands.wallet.PurgeWalletCommand +import com.tangem.commands.wallet.PurgeWalletResponse import com.tangem.common.CompletionResult +import com.tangem.common.TangemSdkConstants import com.tangem.common.extensions.calculateSha256 import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsHandler +import com.tangem.tap.domain.extensions.getDefaultWalletIndex import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.tasks.ScanNoteTask @@ -32,13 +39,15 @@ class TangemSdkManager(val activity: ComponentActivity) { initialMessage = Message(activity.getString(R.string.initial_message_scan_header))) } - suspend fun createWallet(cardId: String?): CompletionResult { + suspend fun createWallet(cardId: String?): CompletionResult { return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask(), cardId, initialMessage = Message(activity.getString(R.string.initial_message_create_wallet_body))) } suspend fun eraseWallet(cardId: String?): CompletionResult { - return runTaskAsyncReturnOnMain(PurgeWalletCommand(), cardId, + return runTaskAsyncReturnOnMain(PurgeWalletCommand( + TangemSdkConstants.getDefaultWalletIndex()), + cardId, initialMessage = Message(activity.getString(R.string.initial_message_purge_wallet_body))) } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt new file mode 100644 index 0000000000..7a3556b33a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt @@ -0,0 +1,65 @@ +package com.tangem.tap.domain + +import com.tangem.Message +import com.tangem.TangemSdk +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.commands.SignCommand +import com.tangem.commands.SignResponse +import com.tangem.commands.wallet.WalletIndex +import com.tangem.common.CompletionResult +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +class TangemSigner( + private val tangemSdk: TangemSdk, + private val initialMessage: Message, + private val signerCallback: (SignResponse) -> Unit +) : TransactionSigner { + + override suspend fun sign( + hash: ByteArray, + cardId: String, + walletPublicKey: ByteArray + ): CompletionResult = + suspendCoroutine { continuation -> + val command = SignCommand(arrayOf(hash), WalletIndex.PublicKey(walletPublicKey)) + tangemSdk.startSessionWithRunnable( + runnable = command, + cardId = cardId, + initialMessage = initialMessage, + ) { result -> + when (result) { + is CompletionResult.Success -> { + signerCallback(result.data) + continuation.resume(CompletionResult.Success(result.data.signatures.first())) + } + is CompletionResult.Failure -> + continuation.resume(CompletionResult.Failure(result.error)) + } + } + } + + + override suspend fun sign( + hashes: List, + cardId: String, + walletPublicKey: ByteArray + ): CompletionResult> = + suspendCoroutine { continuation -> + val command = SignCommand(hashes.toTypedArray(), WalletIndex.PublicKey(walletPublicKey)) + tangemSdk.startSessionWithRunnable( + runnable = command, + cardId = cardId, + initialMessage = initialMessage, + ) { result -> + when (result) { + is CompletionResult.Success -> { + signerCallback(result.data) + continuation.resume(CompletionResult.Success(result.data.signatures)) + } + is CompletionResult.Failure -> + continuation.resume(CompletionResult.Failure(result.error)) + } + } + } +} \ 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 497c7dde68..c8995c0f97 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -11,8 +11,7 @@ import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.configurable.config.ConfigManager -import com.tangem.tap.domain.extensions.amountToCreateAccount -import com.tangem.tap.domain.extensions.isNoAccountError +import com.tangem.tap.domain.extensions.* import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.tokens.CardCurrencies import com.tangem.tap.domain.twins.TwinsHelper @@ -102,8 +101,10 @@ class TapWalletManager { )) } } - if (data.walletManager?.wallet?.blockchain == Blockchain.Ethereum || - data.walletManager?.wallet?.blockchain == Blockchain.EthereumTestnet) { + + val blockchain = data.card.getBlockchain() + if (blockchain == Blockchain.Ethereum || + blockchain == Blockchain.EthereumTestnet) { store.dispatch(TokensAction.LoadCardTokens) } loadData(data) @@ -112,10 +113,11 @@ class TapWalletManager { private fun updateConfigManager(data: ScanNoteResponse) { val configManager = store.state.globalState.configManager + val blockchain = data.card.getBlockchain() if (TapWorkarounds.isStart2Coin) { configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled) configManager?.turnOff(ConfigManager.isTopUpEnabled) - } else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin + } else if (blockchain == Blockchain.Bitcoin || data.card.cardData?.blockchainName == Blockchain.Bitcoin.id) { configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled) configManager?.resetToDefault(ConfigManager.isTopUpEnabled) @@ -128,82 +130,104 @@ class TapWalletManager { suspend fun loadData(data: ScanNoteResponse) { withContext(Dispatchers.Main) { val artworkId = data.verifyResponse?.artworkInfo?.id - if (data.walletManager != null) { - val config = store.state.globalState.configManager?.config ?: return@withContext - - val primaryWalletManager = data.walletManager - val primaryBlockchain = data.walletManager.wallet.blockchain - val primaryTokenSymbol = data.card.cardData?.tokenSymbol - val primaryToken = primaryWalletManager.presetTokens.toList() - .firstOrNull { it.symbol == primaryTokenSymbol } - - store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(primaryBlockchain)) - if (primaryToken != null) { - store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken)) + when { + data.card.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { + store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) + store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) } - if (data.card.isMultiwalletAllowed) { - loadMultiWalletData(data.card, primaryBlockchain, primaryWalletManager) - } else { - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) - store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain))) + data.card.getStatus() == CardStatus.Empty || + (data.card.isTwinCard() && data.secondTwinPublicKey == null) -> { + store.dispatch(WalletAction.EmptyWallet) + store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) + } + else -> { + val config = store.state.globalState.configManager?.config ?: return@withContext + + val blockchain = data.card.getBlockchain() + val primaryWalletManager = if (blockchain != null) { + walletManagerFactory.makeWalletManagerForApp(data.card, blockchain) + } else { + null + } + + if (blockchain != null && primaryWalletManager != null) { + val primaryToken = data.card.getToken() + + store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain)) + if (primaryToken != null) { + primaryWalletManager.addToken(primaryToken) + store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken)) + } + if (data.card.isMultiwalletAllowed) { + loadMultiWalletData(data.card, blockchain, primaryWalletManager) + } else { + store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) + store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain))) + } + + } else { + if (data.card.isMultiwalletAllowed) { + loadMultiWalletData(data.card, blockchain, null) + } + } + store.dispatch(WalletAction.SetArtworkId(data.verifyResponse?.artworkInfo?.id)) + store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled)) + store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) + store.dispatch(WalletAction.LoadFiatRate()) } - store.dispatch(WalletAction.SetArtworkId(data.verifyResponse?.artworkInfo?.id)) - store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled)) - store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) - store.dispatch(WalletAction.LoadFiatRate()) - } else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) { - store.dispatch(WalletAction.EmptyWallet) - store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) - } else { - store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) - store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) } store.dispatch(WalletAction.Warnings.CheckIfNeeded) } } private fun loadMultiWalletData( - card: Card, primaryBlockchain: Blockchain, primaryWalletManager: WalletManager + card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager? ) { - val presetTokens = primaryWalletManager.presetTokens.toList() + val presetTokens = primaryWalletManager?.presetTokens?.toList() ?: emptyList() val savedCurrencies = currenciesRepository.loadCardCurrencies(card.cardId) if (savedCurrencies == null) { - store.dispatch(WalletAction.MultiWallet.SaveCurrencies( - CardCurrencies( - blockchains = listOf(primaryBlockchain), tokens = presetTokens - ))) - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) - store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain))) - store.dispatch(WalletAction.MultiWallet.AddTokens(presetTokens)) + if (primaryBlockchain != null && primaryWalletManager != null) { + store.dispatch(WalletAction.MultiWallet.SaveCurrencies( + CardCurrencies( + blockchains = listOf(primaryBlockchain), tokens = presetTokens + ))) + store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) + store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain))) + store.dispatch(WalletAction.MultiWallet.AddTokens(presetTokens)) + } store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse(card, walletManagerFactory)) store.dispatch(WalletAction.MultiWallet.FindTokensInUse) } else { - val blockchains = listOf(primaryBlockchain) + savedCurrencies.blockchains - val walletManagers = walletManagerFactory.makeWalletManagers(card, blockchains) - val tokens = presetTokens + savedCurrencies.tokens + val blockchains = savedCurrencies.blockchains + val walletManagers = walletManagerFactory.makeWalletManagersForApp(card, blockchains) store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers)) store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains)) - store.dispatch(WalletAction.MultiWallet.AddTokens(tokens)) + store.dispatch(WalletAction.MultiWallet.AddTokens(savedCurrencies.tokens)) store.dispatch(WalletAction.MultiWallet.FindTokensInUse) } } suspend fun reloadData(data: ScanNoteResponse) { withContext(Dispatchers.Main) { - if (data.walletManager != null) { - if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection)) - return@withContext + when { + data.card.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> { + store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) + } + data.card.getStatus() == CardStatus.Empty || + (data.card.isTwinCard() && data.secondTwinPublicKey == null) -> { + store.dispatch(WalletAction.EmptyWallet) + } + else -> { + if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection)) + return@withContext + } + val config = store.state.globalState.configManager?.config ?: return@withContext + store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled)) + store.dispatch(WalletAction.LoadFiatRate()) } - val config = store.state.globalState.configManager?.config ?: return@withContext - store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled)) - store.dispatch(WalletAction.LoadFiatRate()) - } else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) { - store.dispatch(WalletAction.EmptyWallet) - } else { - store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) } } } 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 eb1e716da7..afcac855d5 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt @@ -32,7 +32,7 @@ object TapWorkarounds { "0030", "0031", "0035" - ) // Tangem tags + ) private val excludedIssuers = listOf( "TTM BANK" @@ -43,5 +43,6 @@ val Card.isMultiwalletAllowed: Boolean get() { return cardData?.productMask?.contains(Product.TwinCard) != true && !TapWorkarounds.isStart2Coin - && this.curve == EllipticCurve.Secp256k1 + && (this.firmwareVersion.major >= 4 || + this.getWallets().getOrNull(0)?.curve == EllipticCurve.Secp256k1) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt index ab94c0d563..3c3ad5f5ce 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt @@ -1,5 +1,7 @@ package com.tangem.tap.domain.configurable.warningMessage +import android.view.View +import androidx.annotation.StringRes import com.squareup.moshi.Json import com.tangem.blockchain.common.Blockchain @@ -7,15 +9,17 @@ import com.tangem.blockchain.common.Blockchain [REDACTED_AUTHOR] */ data class WarningMessage( - val title: String, - val message: String, - val type: Type, - val priority: Priority, - val location: List, - private val blockchains: List?, - val titleResId: Int? = null, - val messageResId: Int? = null, - val origin: Origin = Origin.Remote, + val title: String, + val message: String, + val type: Type, + val priority: Priority, + val location: List, + private val blockchains: List?, + @StringRes val titleResId: Int? = null, + @StringRes val messageResId: Int? = null, + val origin: Origin = Origin.Remote, + @StringRes val buttonTextId: Int? = null, + val messageFormatArg: String? = null ) { val blockchainList: List? by lazy { blockchains?.map { Blockchain.fromId(it.toUpperCase()) } @@ -43,6 +47,7 @@ data class WarningMessage( Temporary, // можно скрыть (кнопка ОК) AppRating + } enum class Location { diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt index 81739ee7b5..59e91a7f6a 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt @@ -1,8 +1,11 @@ package com.tangem.tap.domain.configurable.warningMessage +import com.tangem.TangemSdkError import com.tangem.blockchain.common.Blockchain import com.tangem.tangem_sdk_new.ui.animation.VoidCallback import com.tangem.tap.common.extensions.containsAny +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store import com.tangem.wallet.R /** @@ -98,6 +101,19 @@ class WarningMessagesManager( WarningMessage.Origin.Local ) + fun signedHashesMultiWalletWarning(): WarningMessage = WarningMessage( + title = "", + message = "", + type = WarningMessage.Type.Temporary, + priority = WarningMessage.Priority.Info, + location = listOf(WarningMessage.Location.MainScreen), + blockchains = null, + titleResId = R.string.warning_important_security_info, + messageResId = R.string.warning_signed_tx_previously, + origin = WarningMessage.Origin.Local, + buttonTextId = R.string.warning_button_learn_more, + ) + fun appRatingWarning(): WarningMessage = WarningMessage( "", "", @@ -125,5 +141,20 @@ class WarningMessagesManager( R.string.warning_failed_to_verify_card_message, WarningMessage.Origin.Local ) + + fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage = WarningMessage( + title = "", + message = "", + type = WarningMessage.Type.Permanent, + priority = WarningMessage.Priority.Critical, + listOf(WarningMessage.Location.MainScreen), + blockchains = null, + titleResId = R.string.alert_title, + messageResId = R.string.warning_low_signatures_format, + origin = WarningMessage.Origin.Local, + messageFormatArg = remainingSignatures.toString() + ) + + const val REMAINING_SIGNATURES_WARNING = 10 } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/BlockchainExtensions.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt similarity index 60% rename from app/src/main/java/com/tangem/tap/domain/extensions/BlockchainExtensions.kt rename to app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt index 00ad0c17ea..4f55b5b86e 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/BlockchainExtensions.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt @@ -2,6 +2,7 @@ package com.tangem.tap.domain.extensions import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.commands.common.card.EllipticCurve import org.stellar.sdk.requests.ErrorResponse import java.math.BigDecimal @@ -28,4 +29,15 @@ fun Blockchain.minimalAmount(): BigDecimal { return 1.toBigDecimal().movePointLeft(decimals()) } +fun Blockchain.getCurve(): EllipticCurve? { + return when (this) { + Blockchain.Unknown -> null + Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.BitcoinCash, Blockchain.Litecoin, + Blockchain.Ducatus, Blockchain.Ethereum, Blockchain.EthereumTestnet, Blockchain.RSK, + Blockchain.Tezos, Blockchain.XRP, Blockchain.Binance, Blockchain.BinanceTestnet -> + EllipticCurve.Secp256k1 + Blockchain.Cardano, Blockchain.CardanoShelley, Blockchain.Stellar -> EllipticCurve.Ed25519 + } +} + private const val NODL = "NODL" \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt new file mode 100644 index 0000000000..eb60189b4f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -0,0 +1,47 @@ +package com.tangem.tap.domain.extensions + +import com.tangem.FirmwareConstraints +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token +import com.tangem.commands.common.card.Card +import com.tangem.commands.common.card.CardStatus +import com.tangem.commands.wallet.CardWallet +import com.tangem.commands.wallet.WalletStatus +import com.tangem.common.TangemSdkConstants + +fun Card.getToken(): Token? { + val symbol = cardData?.tokenSymbol ?: return null + val contractAddress = cardData?.tokenContractAddress ?: return null + val decimals = cardData?.tokenDecimal ?: return null + return Token(symbol, contractAddress, decimals) +} + +fun Card.getBlockchain(): Blockchain? { + val blockchainName: String = cardData?.blockchainName ?: return null + return Blockchain.fromId(blockchainName) +} + +fun Card.getSingleWallet(): CardWallet? { + return wallet(TangemSdkConstants.getDefaultWalletIndex()) +} + +fun Card.getStatus(): CardStatus { + if (firmwareVersion < FirmwareConstraints.AvailabilityVersions.walletData) return status!! + + return if (getWallets().any { it.status == WalletStatus.Loaded }) { + CardStatus.Loaded + } else { + CardStatus.Empty + } +} + +fun Card.hasSignedHashes(): Boolean { + return getWallets().any { it.status == WalletStatus.Loaded && it.signedHashes ?: 0 > 0 } +} + +fun Card.signedHashesCount(): Int { + return getWallets().map { it.signedHashes ?: 0 }.sum() +} + +val Card.remainingSignatures: Int? + get() = this.getSingleWallet()?.remainingSignatures \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/TangemSdkConstants.kt b/app/src/main/java/com/tangem/tap/domain/extensions/TangemSdkConstants.kt new file mode 100644 index 0000000000..c8710e3aeb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/extensions/TangemSdkConstants.kt @@ -0,0 +1,8 @@ +package com.tangem.tap.domain.extensions + +import com.tangem.commands.wallet.WalletIndex +import com.tangem.common.TangemSdkConstants + +fun TangemSdkConstants.Companion.getDefaultWalletIndex(): WalletIndex { + return WalletIndex.Index(oldCardDefaultWalletIndex) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt new file mode 100644 index 0000000000..fc847ae987 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.domain.extensions + +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.commands.common.card.Card +import com.tangem.commands.common.card.EllipticCurve + +fun WalletManagerFactory.makeWalletManagerForApp(card: Card, blockchain: Blockchain): WalletManager? { + val curve = blockchain.getCurve() ?: return null + val publicKey = card.getWallets().firstOrNull { it.curve == curve }?.publicKey ?: return null + return makeWalletManager(card.cardId, publicKey, blockchain, curve) +} + +fun WalletManagerFactory.makeWalletManagersForApp( + card: Card, blockchains: List +): List { + return makeWalletManagersForCurve(card, blockchains, EllipticCurve.Secp256k1) + + makeWalletManagersForCurve(card, blockchains, EllipticCurve.Ed25519) +} + +fun WalletManagerFactory.makeWalletManagersForCurve( + card: Card, blockchains: List, curve: EllipticCurve +): List { + val blockchainsForCurve = blockchains.filter { it.getCurve() == curve } + val walletPublicKey = card.getWallets().firstOrNull { it.curve == curve }?.publicKey + + return if (walletPublicKey != null) { + makeWalletManagers(card.cardId, walletPublicKey, blockchainsForCurve, curve) + } else { + emptyList() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt index 0af8e1f29b..735cc1de64 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt @@ -2,26 +2,39 @@ package com.tangem.tap.domain.tasks import com.tangem.CardSession import com.tangem.CardSessionRunnable -import com.tangem.commands.ReadCommand +import com.tangem.FirmwareConstraints +import com.tangem.TangemSdkError +import com.tangem.commands.common.card.Card import com.tangem.common.CompletionResult import com.tangem.tasks.CreateWalletTask +import com.tangem.tasks.PreflightReadCapable +import com.tangem.tasks.PreflightReadSettings +import com.tangem.tasks.PreflightReadTask -class CreateWalletAndRescanTask : CardSessionRunnable { +class CreateWalletAndRescanTask : CardSessionRunnable, PreflightReadCapable { override val requiresPin2 = false + override fun preflightReadSettings() = PreflightReadSettings.FullCardRead - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - CreateWalletTask().run(session) { result -> + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val firmwareVerion = session.environment.card?.firmwareVersion + if (firmwareVerion == null) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + val task = if (firmwareVerion < FirmwareConstraints.AvailabilityVersions.walletData) { + CreateWalletTask() + } else { + CreateWalletsTask() + } + + task.run(session) { result -> when (result) { - is CompletionResult.Success -> { - ReadCommand().run(session) { readResult -> - when (readResult) { - is CompletionResult.Success -> ScanNoteTask(readResult.data).run(session, callback) - is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) - } - } - } + is CompletionResult.Success -> + PreflightReadTask(PreflightReadSettings.FullCardRead).run(session, callback) is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } + + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt new file mode 100644 index 0000000000..b72cf26ae3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt @@ -0,0 +1,64 @@ +package com.tangem.tap.domain.tasks + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.commands.common.card.Card +import com.tangem.commands.common.card.EllipticCurve +import com.tangem.commands.wallet.CreateWalletCommand +import com.tangem.commands.wallet.WalletConfig +import com.tangem.common.CompletionResult +import com.tangem.tasks.PreflightReadSettings +import com.tangem.tasks.PreflightReadTask + +class CreateWalletsTask(wallets: List = emptyList()) : CardSessionRunnable { + override val requiresPin2 = false + + val wallets = if (wallets.isEmpty()) { + listOf( + WalletConfig(null, null, EllipticCurve.Secp256k1, null), + WalletConfig(null, null, EllipticCurve.Ed25519, null), + WalletConfig(null, null, EllipticCurve.Secp256r1, null), + ) + } else { + wallets + } + + var index = 0 + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val walletConfig = wallets[index] + createWallet(walletConfig, session, callback) + } + + private fun createWallet( + walletConfig: WalletConfig, session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + + CreateWalletCommand( + walletConfig, walletIndexValue = walletConfig.curveId!!.toWalletIndex() + ).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + if (index == wallets.lastIndex) { + PreflightReadTask(PreflightReadSettings.FullCardRead).run(session, callback) + return@run + } + index += 1 + createWallet(wallets[index], session, callback) + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + } + } + } +} + +private fun EllipticCurve.toWalletIndex(): Int { + return when (this) { + EllipticCurve.Secp256k1 -> 0 + EllipticCurve.Ed25519 -> 1 + EllipticCurve.Secp256r1 -> 2 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt index 0c6e990dc3..f252714774 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt @@ -1,36 +1,40 @@ package com.tangem.tap.domain.tasks import com.tangem.* -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkConfig -import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.commands.CommandResponse import com.tangem.commands.ReadIssuerDataCommand import com.tangem.commands.common.card.Card import com.tangem.commands.common.card.CardStatus -import com.tangem.commands.verifycard.VerifyCardCommand -import com.tangem.commands.verifycard.VerifyCardResponse +import com.tangem.commands.common.card.EllipticCurve +import com.tangem.commands.verification.VerifyCardCommand +import com.tangem.commands.verification.VerifyCardResponse +import com.tangem.commands.wallet.WalletConfig import com.tangem.common.CompletionResult import com.tangem.common.extensions.toHexString import com.tangem.tap.domain.TapSdkError import com.tangem.tap.domain.TapWorkarounds.isExcluded -import com.tangem.tap.domain.isMultiwalletAllowed +import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.tap.domain.extensions.getStatus import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.domain.twins.isTwinCard import com.tangem.tap.store +import com.tangem.tasks.PreflightReadCapable +import com.tangem.tasks.PreflightReadSettings import com.tangem.tasks.ScanTask data class ScanNoteResponse( - val walletManager: WalletManager?, val card: Card, val verifyResponse: VerifyCardResponse? = null, val secondTwinPublicKey: String? = null, ) : CommandResponse -class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { +class ScanNoteTask(val card: Card? = null) : CardSessionRunnable, PreflightReadCapable { override val requiresPin2 = false + override fun preflightReadSettings() = PreflightReadSettings.FullCardRead + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { ScanTask().run(session) { result -> when (result) { @@ -48,39 +52,25 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable) -> Unit + card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit ) { - VerifyCardCommand(true).run(session) { verifyResult -> when (verifyResult) { is CompletionResult.Success -> { - callback(CompletionResult.Success(ScanNoteResponse( - walletManager, card, verifyResult.data, publicKey))) + if (card.isTwinCard()) { + dealWithTwinCard(card, session, verifyResult.data, callback) + } else if (card.firmwareVersion.major >= 4) { + createMissingWalletsIfNeeded(card, session, verifyResult.data, callback) + } else { + callback(CompletionResult.Success(ScanNoteResponse(card, verifyResult.data))) + } } is CompletionResult.Failure -> { callback(CompletionResult.Failure(TangemSdkError.CardVerificationFailed())) @@ -89,39 +79,73 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable) -> Unit + ) { + if (card.getStatus() == CardStatus.Empty) { + callback(CompletionResult.Success(ScanNoteResponse(card, verifyResponse))) + return + } + + val curvesPresent = card.getWallets().map { it.curve } + val curvesToCreate = EllipticCurve.values().subtract(curvesPresent) + + if (curvesToCreate.isEmpty()) { + callback(CompletionResult.Success(ScanNoteResponse(card, verifyResponse))) + return + } + + val configs = curvesToCreate.map { curve -> + WalletConfig( + isReusable = null, prohibitPurgeWallet = null, curveId = curve, + signingMethods = null + ) + } + CreateWalletsTask(configs).run(session) { result -> + when (result) { + is CompletionResult.Success -> + callback(CompletionResult.Success(ScanNoteResponse(result.data, verifyResponse))) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + + } + private fun dealWithTwinCard( - card: Card, session: CardSession, + card: Card, session: CardSession, verifyResponse: VerifyCardResponse, callback: (result: CompletionResult) -> Unit ) { ReadIssuerDataCommand().run(session) { readDataResult -> when (readDataResult) { is CompletionResult.Success -> { + val publicKey = card.getSingleWallet()?.publicKey + if (publicKey == null) { + callback(CompletionResult.Success(ScanNoteResponse(card, null))) + return@run + } val verified = TwinCardsManager.verifyTwinPublicKey( - readDataResult.data.issuerData, card.walletPublicKey + readDataResult.data.issuerData, publicKey ) if (verified) { val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) - val walletManager = try { - getWalletManagerFactory().makeMultisigWalletManager(card, twinPublicKey) - } catch (exception: Exception) { - callback(CompletionResult.Success(ScanNoteResponse(null, card))) - return@run - } - verifyCard(walletManager, card, twinPublicKey.toHexString(), session, callback) + callback(CompletionResult.Success( + ScanNoteResponse(card, verifyResponse, twinPublicKey.toHexString()) + )) return@run } else { - callback(CompletionResult.Success(ScanNoteResponse(null, card))) + callback(CompletionResult.Success(ScanNoteResponse(card, null))) } } is CompletionResult.Failure -> - callback(CompletionResult.Success(ScanNoteResponse(null, card))) + callback(CompletionResult.Success(ScanNoteResponse(card, null))) } } } private fun getErrorIfExcludedCard(card: Card): TangemError? { if (card.isExcluded()) return TapSdkError.CardForDifferentApp - if (card.status == CardStatus.Purged) return TangemSdkError.CardIsPurged() + if (card.status == CardStatus.Purged) return TangemSdkError.WalletIsPurged() if (card.status == CardStatus.NotPersonalized) return TangemSdkError.NotPersonalized() return null } 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 42c7aeefe4..78afe93215 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 @@ -7,6 +7,7 @@ import com.squareup.moshi.JsonClass import com.squareup.moshi.Types import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.commands.common.card.FirmwareVersion import com.tangem.tap.common.extensions.readJsonFileToString import com.tangem.tap.network.createMoshi @@ -93,19 +94,27 @@ class CurrenciesRepository(val context: Application) { return tokensAdapter.fromJson(json)!!.map { it.toToken() } } - fun getBlockchains(): List { - return listOf( - Blockchain.Bitcoin, Blockchain.BitcoinCash, Blockchain.Binance, Blockchain.Litecoin, - Blockchain.XRP, Blockchain.Tezos, - Blockchain.Ethereum, Blockchain.RSK) + fun getBlockchains(cardFirmware: FirmwareVersion?): List { + return if (cardFirmware == null || cardFirmware.major < 4) { + secp256k1Blochcains + } else { + secp256k1Blochcains + ed25519Blockchains + } } companion object { private const val POPULAR_TOKENS_FILE_NAME = "erc20_tokens" private const val FILE_NAME_PREFIX_TOKENS = "tokens" private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains" + fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId" fun getFileNameForBlockchains(cardId: String): String = "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId" + + private val secp256k1Blochcains = listOf( + Blockchain.Bitcoin, Blockchain.BitcoinCash, Blockchain.Binance, Blockchain.Litecoin, + Blockchain.XRP, Blockchain.Tezos, + Blockchain.Ethereum, Blockchain.RSK) + private val ed25519Blockchains = listOf(Blockchain.CardanoShelley, Blockchain.Stellar) } } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index 7296c72a11..e9b5dd4435 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -2,18 +2,21 @@ package com.tangem.tap.domain.twins import com.tangem.CardSession import com.tangem.CardSessionRunnable -import com.tangem.commands.CreateWalletResponse -import com.tangem.commands.PurgeWalletCommand import com.tangem.commands.common.card.CardStatus +import com.tangem.commands.wallet.CreateWalletResponse +import com.tangem.commands.wallet.PurgeWalletCommand import com.tangem.common.CompletionResult +import com.tangem.common.TangemSdkConstants +import com.tangem.tap.domain.extensions.getDefaultWalletIndex +import com.tangem.tap.domain.extensions.getSingleWallet import com.tangem.tasks.CreateWalletTask class CreateFirstTwinWalletTask : CardSessionRunnable { override val requiresPin2 = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - if (session.environment.card?.walletPublicKey != null) { - PurgeWalletCommand().run(session) { response -> + if (session.environment.card?.getSingleWallet()?.publicKey != null) { + PurgeWalletCommand(TangemSdkConstants.getDefaultWalletIndex()).run(session) { response -> when (response) { is CompletionResult.Success -> { session.environment.card = session.environment.card?.copy(status = CardStatus.Empty) 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 5587faa1ab..9946e37d07 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 @@ -3,11 +3,14 @@ package com.tangem.tap.domain.twins import com.tangem.CardSession import com.tangem.CardSessionRunnable import com.tangem.Message -import com.tangem.commands.CreateWalletResponse -import com.tangem.commands.PurgeWalletCommand import com.tangem.commands.common.card.CardStatus +import com.tangem.commands.wallet.CreateWalletResponse +import com.tangem.commands.wallet.PurgeWalletCommand import com.tangem.common.CompletionResult +import com.tangem.common.TangemSdkConstants import com.tangem.common.extensions.hexToBytes +import com.tangem.tap.domain.extensions.getDefaultWalletIndex +import com.tangem.tap.domain.extensions.getSingleWallet import com.tangem.tasks.CreateWalletTask class CreateSecondTwinWalletTask( @@ -18,9 +21,9 @@ class CreateSecondTwinWalletTask( override val requiresPin2 = true override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - if (session.environment.card?.walletPublicKey != null) { + if (session.environment.card?.getSingleWallet()?.publicKey != null) { session.setInitialMessage(preparingMessage) - PurgeWalletCommand().run(session) { response -> + PurgeWalletCommand(TangemSdkConstants.getDefaultWalletIndex()).run(session) { response -> when (response) { is CompletionResult.Success -> { session.environment.card = diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt index 073c47b344..e8454b6756 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.twins import com.tangem.CardSession import com.tangem.CardSessionRunnable import com.tangem.KeyPair -import com.tangem.commands.ReadCommand +import com.tangem.commands.read.ReadCommand import com.tangem.common.CompletionResult import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.tasks.ScanNoteTask diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt index 30d3b688dd..bf6c735be5 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt @@ -6,9 +6,11 @@ import com.tangem.KeyPair import com.tangem.TangemSdkError import com.tangem.commands.* import com.tangem.common.CompletionResult +import com.tangem.common.TangemSdkConstants import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.guard import com.tangem.common.files.FileHashHelper +import com.tangem.tap.domain.extensions.getDefaultWalletIndex class WriteProtectedIssuerDataTask( private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair @@ -17,14 +19,14 @@ class WriteProtectedIssuerDataTask( override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val hashes = arrayOf(twinPublicKey.calculateSha256()) - SignCommand(hashes).run(session) { signResult -> + SignCommand(hashes, TangemSdkConstants.getDefaultWalletIndex()).run(session) { signResult -> when (signResult) { is CompletionResult.Success -> { ReadIssuerDataCommand().run(session) { readResult -> when (readResult) { is CompletionResult.Success -> { writeIssuerData( - twinPublicKey, issuerKeys, signResult.data.signature, + twinPublicKey, issuerKeys, signResult.data.signatures[0], readResult.data, session, callback ) } 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 ee44e36c9d..4156e2449f 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 @@ -5,6 +5,7 @@ import com.tangem.commands.common.card.Card import com.tangem.commands.common.card.masks.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapWorkarounds +import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.domain.extensions.toSendableAmounts import com.tangem.tap.domain.twins.TwinsHelper import com.tangem.tap.domain.twins.isTwinCard @@ -83,6 +84,7 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS return when (action) { DetailsAction.EraseWallet.Check -> { val notAllowedByCard = state.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true + || state.card?.settingsMask?.contains(Settings.IsReusable) == false val notEmpty = state.wallets.any { !it.recentTransactions.isNullOrEmpty() || it.amounts.toSendableAmounts().isNotEmpty() } @@ -203,6 +205,5 @@ private fun prepareAllowedSecurityOptions(card: Card): EnumSet { private fun Card.toCardInfo(): CardInfo? { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.cardData?.issuerName ?: return null - val signedHashes = this.walletSignedHashes ?: return null - return CardInfo(cardId, issuer, signedHashes) -} \ No newline at end of file + val signedHashes = this.signedHashesCount() + return CardInfo(cardId, issuer, signedHashes)} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt index 669ab9be91..e99f7c7ad6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/twins/CreateTwinWalletMiddleware.kt @@ -18,7 +18,7 @@ class CreateTwinWalletMiddleware { fun handle(action: DetailsAction.CreateTwinWalletAction) { when (action) { is DetailsAction.CreateTwinWalletAction.ShowWarning -> { - val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet + val wallet = store.state.detailsState.wallets.firstOrNull() if (wallet == null) { store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning)) return diff --git a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt index c340a59194..cd68250600 100644 --- a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt @@ -18,6 +18,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet import com.tangem.commands.common.card.Card import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.store import timber.log.Timber import java.io.File @@ -161,7 +162,7 @@ class AdditionalEmailInfo { fun setCardInfo(card: Card) { cardId = card.cardId cardFirmwareVersion = card.firmwareVersion.version - signedHashesCount = card.walletSignedHashes?.toString() ?: "0" + signedHashesCount = card.signedHashesCount().toString() } fun setWalletsInfo(wallets: List) { @@ -274,7 +275,6 @@ class FeedbackEmail : EmailData { val builder = StringBuilder() builder.appendKeyValue("Card ID", infoHolder.cardId) builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) - builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount) infoHolder.walletsInfo.forEach { builder.appendKeyValue("Blockchain", it.blockchain.fullName) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt index a713ed3163..3da9747dd3 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt @@ -135,9 +135,11 @@ internal class AddressPayIdMiddleware { if (failReason == null) { noSchemeAddress.getQueryParameter("amount")?.toBigDecimalOrNull()?.let { dispatch(AmountAction.SetAmount(it, false)) + dispatch(AmountActionUi.CheckAmountToSend) } dispatch(SetWalletAddress(supposedAddress, isUserInput)) dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, null)) + dispatch(FeeAction.RequestFee) } else { dispatch(SetAddressError(failReason)) dispatch(TransactionExtrasAction.Release) 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 08dc775fba..19bb270b16 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 @@ -6,6 +6,7 @@ import com.tangem.common.extensions.isZero import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.states.MainCurrencyType +import com.tangem.tap.features.send.redux.states.SendState import org.rekotlin.Action import java.math.BigDecimal @@ -43,6 +44,7 @@ class AmountMiddleware { } dispatch(AmountAction.SetAmount(inputValueCrypto, true)) + dispatch(AmountActionUi.CheckAmountToSend) dispatch(FeeAction.RequestFee) } @@ -78,15 +80,18 @@ class AmountMiddleware { } private fun setMaxAmount(appState: AppState?, dispatch: (Action) -> Unit) { - val amountState = appState?.sendState?.amountState ?: return + val sendState = appState?.sendState ?: return - dispatch(AmountAction.SetAmount(amountState.balanceCrypto, false)) - dispatch(FeeAction.RequestFee) - if (!amountState.isCoinAmount()) return - - dispatch(FeeAction.ChangeLayoutVisibility(main = true, controls = true, chipGroup = true)) - dispatch(FeeActionUi.ChangeIncludeFee(true)) + dispatch(AmountAction.SetAmount(sendState.amountState.balanceCrypto, false)) dispatch(AmountActionUi.CheckAmountToSend) + + if (SendState.isReadyToRequestFee()) { + dispatch(FeeAction.RequestFee) + if (!sendState.amountState.isCoinAmount()) return + + dispatch(FeeAction.ChangeLayoutVisibility(main = true, controls = true, chipGroup = true)) + dispatch(FeeActionUi.ChangeIncludeFee(true)) + } } private fun toggleMainCurrency(appState: AppState?, dispatch: (Action) -> Unit) { 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 49b02cfea8..b2523e9e7a 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 @@ -10,6 +10,7 @@ import com.tangem.tap.features.send.redux.AmountActionUi import com.tangem.tap.features.send.redux.FeeAction import com.tangem.tap.features.send.redux.ReceiptAction import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.features.send.redux.states.SendState import com.tangem.tap.scope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -26,7 +27,7 @@ class RequestFeeMiddleware { val sendState = appState?.sendState ?: return val walletManager = sendState.walletManager ?: return - if (!sendState.addressPayIdIsReady()) { + if (!SendState.isReadyToRequestFee()) { dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.ADDRESS_OR_AMOUNT_IS_EMPTY)) dispatch(FeeAction.ChangeLayoutVisibility(main = false, chipGroup = true)) dispatch(ReceiptAction.RefreshReceipt) 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 5a5f927b30..495209fb5b 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 @@ -4,8 +4,9 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder import com.tangem.blockchain.common.* -import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.Signer +import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.commands.SignResponse import com.tangem.commands.common.card.Card import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler @@ -13,6 +14,7 @@ import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.TangemSigner import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapWorkarounds import com.tangem.tap.domain.configurable.warningMessage.WarningMessage @@ -111,15 +113,24 @@ private fun sendTransaction( if (TapWorkarounds.isStart2Coin) { tangemSdk.config.linkedTerminal = false } - val signer = Signer(tangemSdk, action.messageForSigner) + val signer = TangemSigner( + tangemSdk = tangemSdk, initialMessage = action.messageForSigner + ) { signResponse -> + store.dispatch( + GlobalAction.UpdateWalletSignedHashes( + walletSignedHashes = signResponse.walletSignedHashes, + remainingSignatures = signResponse.walletRemainingSignatures, + walletPublicKey = walletManager.wallet.publicKey + ) + ) + } val result = (walletManager as TransactionSender).send(txData, signer) withContext(Dispatchers.Main) { when (result) { - is Result.Success -> { + is SimpleResult.Success -> { tangemSdk.config.linkedTerminal = isLinkedTerminal FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.TRANSACTION_IS_SENT, card) dispatch(SendAction.SendSuccess) - dispatch(GlobalAction.UpdateWalletSignedHashes(result.data.walletSignedHashes)) dispatch(NavigationAction.PopBackTo()) scope.launch(Dispatchers.IO) { withContext(Dispatchers.Main) { @@ -131,7 +142,7 @@ private fun sendTransaction( } } } - is Result.Failure -> { + is SimpleResult.Failure -> { when (result.error) { is CreateAccountUnderfunded -> { val error = result.error as CreateAccountUnderfunded 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 1ecf9ea6bb..0a4a4df672 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 @@ -41,7 +41,7 @@ class ReceiptReducer : SendInternalReducer { val layoutType = determineLayoutType(amountState.mainCurrency.type, amountState.typeOfAmount) val symbols = determineSymbols(wallet) - val showBlank = !sendState.isReadyToSend() + val showBlank = !SendState.isReadyToSend() val result = state.copy( visibleTypeOfReceipt = layoutType, mainCurrency = amountState.mainCurrency, diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index 9db3d6e7ca..8cad24d39c 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -5,7 +5,6 @@ import com.tangem.tap.common.CurrencyConverter import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.states.IdStateHolder import com.tangem.tap.features.send.redux.states.SendState -import com.tangem.tap.store import org.rekotlin.Action import java.math.BigDecimal @@ -60,8 +59,7 @@ private class EmptyReducer : SendInternalReducer { private class PrepareSendScreenStatesReducer : SendInternalReducer { override fun handle(action: SendScreenAction, sendState: SendState): SendState { val prepareAction = action as PrepareSendScreen - val walletManager = action.walletManager - ?: store.state.globalState.scanNoteResponse!!.walletManager!! + val walletManager = action.walletManager!! val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!! val decimals = amountToExtract.decimals diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index 1e4e8d5072..41d2fd79c5 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -45,13 +45,6 @@ data class SendState( override val stateId: StateId = StateId.SEND_SCREEN - fun isReadyToSend(): Boolean { - val sendState = store.state.sendState - return addressPayIdIsReady() && sendState.amountState.isReady() && sendState.feeState.isReady() - } - - fun addressPayIdIsReady(): Boolean = store.state.sendState.addressPayIdState.isReady() - fun getDecimals(type: MainCurrencyType): Int = when (type) { MainCurrencyType.FIAT -> 2 MainCurrencyType.CRYPTO -> amountState.amountToExtract?.decimals ?: 0 @@ -110,6 +103,17 @@ data class SendState( AmountType.Reserve -> false } } + + companion object { + fun addressPayIdIsReady(): Boolean = store.state.sendState.addressPayIdState.isReady() + + fun amountIsReady(): Boolean = store.state.sendState.amountState.isReady() + + fun isReadyToRequestFee(): Boolean = addressPayIdIsReady() && amountIsReady() + + fun isReadyToSend(): Boolean = addressPayIdIsReady() && amountIsReady() && + store.state.sendState.feeState.isReady() + } } enum class SendButtonState { 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 080114705d..5be3d75998 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 @@ -13,12 +13,10 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.textfield.TextInputEditText import com.tangem.Message -import com.tangem.merchant.common.toggleWidget.ToggleWidget import com.tangem.tangem_sdk_new.extensions.dpToPx import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard import com.tangem.tap.common.KeyboardObserver import com.tangem.tap.common.entities.TapCurrency -import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.tap.common.extensions.setOnImeActionListener import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity @@ -54,17 +52,11 @@ import java.text.DecimalFormatSymbols */ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { - lateinit var sendBtn: ToggleWidget + lateinit var sendBtn: ViewStateWidget private lateinit var etAmountToSend: TextInputEditText private lateinit var warningsAdapter: WarningMessagesAdapter - private fun initSendButtonStates() { - sendBtn = ToggleWidget(flSendButtonContainer, btnSend, progress, ProgressState.None()) - sendBtn.setupSendButtonStateModifiers(requireContext()) - sendBtn.setState(ProgressState.None()) - } - private val sendSubscriber = SendStateSubscriber(this) private lateinit var keyboardObserver: KeyboardObserver @@ -79,12 +71,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { setupAmountLayout() setupFeeLayout() setupWarningMessages() + } + private fun initSendButtonStates() { btnSend.setOnClickListener { store.dispatch(SendActionUi.SendAmountToRecipient( Message(getString(R.string.initial_message_sign_header)) )) } + sendBtn = IndeterminateProgressButtonWidget(btnSend, progress) } private fun setupAddressOrPayIdLayout() { @@ -99,14 +94,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it } .onEach { store.dispatch(AddressPayIdActionUi.HandleUserInput(it)) - store.dispatch(FeeAction.RequestFee) } .launchIn(mainScope) imvPaste.setOnClickListener { store.dispatch(PasteAddressPayId(requireContext().getFromClipboard()?.toString() ?: "")) store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused)) - store.dispatch(FeeAction.RequestFee) } imvQrCode.setOnClickListener { startActivityForResult( @@ -126,12 +119,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { .onEach { store.dispatch(TransactionExtrasAction.XlmMemo.HandleUserInput(it)) } .launchIn(mainScope) -// groupMemo.setOnCheckedChangeListener { group, checkedId -> -// if (checkedId == -1) return@setOnCheckedChangeListener -// -// store.dispatch(TransactionExtrasAction.XlmMemo.ChangeSelectedMemo(MemoUiHelper.toType(checkedId))) -// } - etDestinationTag.inputtedTextAsFlow() .debounce(400) .filter { @@ -154,7 +141,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { imvQrCode.postDelayed({ store.dispatch(PasteAddressPayId(scannedCode)) store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused)) - store.dispatch(FeeAction.RequestFee) }, 200) } @@ -176,7 +162,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { etAmountToSend.clearFocus() etAmountToSend.postDelayed(200) { etAmountToSend.hideSoftKeyboard() } store.dispatch(SetMaxAmount) - store.dispatch(CheckAmountToSend) } var snackbarControlledByChangingFocus = false keyboardObserver = KeyboardObserver(requireActivity()) @@ -229,6 +214,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { flExpandCollapse.setOnClickListener { store.dispatch(ToggleControlsVisibility) } + chipGroup.check(FeeUiHelper.toId(FeeType.NORMAL)) chipGroup.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener @@ -307,37 +293,4 @@ class FeeUiHelper { } } } -} - - -//class MemoUiHelper { -// companion object { -// fun toId(memo: MemoType): Int { -// return when (memo) { -// MemoType.TEXT -> R.id.chipMemoText -// MemoType.ID -> R.id.chipMemoId -// } -// } -// -// fun toType(id: Int): MemoType { -// return when (id) { -// R.id.chipMemoText -> MemoType.TEXT -// R.id.chipMemoId -> MemoType.ID -// else -> MemoType.TEXT -// } -// } -// } -//} - -private fun ToggleWidget.setupSendButtonStateModifiers(context: Context) { - mainViewModifiers.clear() - mainViewModifiers.add(ReplaceTextStateModifier(context.getString(R.string.send_title), "")) - mainViewModifiers.add( - TextViewDrawableStateModifier( - context.getDrawableCompat(R.drawable.ic_arrow_right), null, TextViewDrawableStateModifier.RIGHT - )) - mainViewModifiers.add(ClickableStateModifier()) - toggleViewModifiers.clear() - toggleViewModifiers.add(ShowHideStateModifier()) -} - +} \ No newline at end of file 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 973ed08f8b..933b1580d7 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 @@ -119,15 +119,15 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber when (state.sendButtonState) { SendButtonState.ENABLED -> { fg.btnSend.isEnabled = true - sendFragment.sendBtn.setState(ProgressState.None(), true) + sendFragment.sendBtn.changeState(ProgressState.None) } SendButtonState.DISABLED -> { fg.btnSend.isEnabled = false - sendFragment.sendBtn.setState(ProgressState.None(), true) + sendFragment.sendBtn.changeState(ProgressState.None) } SendButtonState.PROGRESS -> { fg.btnSend.isEnabled = true - sendFragment.sendBtn.setState(ProgressState.Progress(), true) + sendFragment.sendBtn.changeState(ProgressState.Progress()) } } 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 7923d17bd4..cb13d64dd0 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 @@ -13,8 +13,9 @@ class TokensMiddleware { { action -> when (action) { is TokensAction.LoadCurrencies -> { + val cardFirmware = state()?.globalState?.scanNoteResponse?.card?.firmwareVersion val tokens = currenciesRepository.getPopularTokens() - val blockchains = currenciesRepository.getBlockchains() + val blockchains = currenciesRepository.getBlockchains(cardFirmware) val currencies = CurrencyListItem.createListOfCurrencies( blockchains, tokens ) 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 9fe1ba05bd..0047481627 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 @@ -7,7 +7,10 @@ import androidx.annotation.StringRes import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import com.squareup.picasso.Callback +import com.squareup.picasso.Picasso import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.IconsUtil import com.tangem.blockchain.common.Token import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.global.CryptoCurrencyName @@ -16,6 +19,7 @@ import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.item_currency_subtitle.view.* import kotlinx.android.synthetic.main.item_popular_token.view.* +import java.lang.Exception import java.util.* class CurrenciesAdapter : ListAdapter(DiffUtilCallback) { @@ -104,9 +108,11 @@ class CurrenciesAdapter : ListAdapter view.btn_add_token.show(!isAdded) view.btn_token_added.show(isAdded) - view.iv_currency.setImageResource(currency.blockchain.getIconRes()) - view.iv_currency.colorFilter = null - view.tv_token_letter.text = null + Picasso.get().loadCurrenciesIcon( + imageView = view.iv_currency, + textView = view.tv_token_letter, + blockchain = blockchain, token = null + ) view.btn_add_token.setOnClickListener { store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain)) @@ -123,10 +129,11 @@ class CurrenciesAdapter : ListAdapter view.btn_add_token.show(!isAdded) view.btn_token_added.show(isAdded) - view.iv_currency.setImageResource(R.drawable.shape_circle) - view.iv_currency.setColorFilter(token.getColor()) - view.tv_token_letter.text = token.symbol.take(1) - + Picasso.get().loadCurrenciesIcon( + imageView = view.iv_currency, + textView = view.tv_token_letter, + token = token, blockchain = Blockchain.Ethereum + ) view.btn_add_token.setOnClickListener { store.dispatch(WalletAction.MultiWallet.AddToken(token)) view.btn_add_token.hide() @@ -152,7 +159,10 @@ sealed class CurrencyListItem { data class TitleListItem(@StringRes val titleResId: Int) : CurrencyListItem() companion object { - fun createListOfCurrencies(blockchains: List, tokens: List): List { + fun createListOfCurrencies( + blockchains: List, + tokens: List + ): List { val blockchainsTitle = R.string.add_tokens_subtitle_blockchains val tokensTitle = R.string.add_tokens_subtitle_tokens return listOf(TitleListItem(blockchainsTitle)) + 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 5b3877338d..405e96be66 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 @@ -68,6 +68,7 @@ sealed class WalletAction : Action { object SetNeverToShow : Warnings() object RemindLater : Warnings() } + class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings() } data class UpdateWallet(val currency: CryptoCurrencyName? = null) : WalletAction() { @@ -111,6 +112,7 @@ sealed class WalletAction : Action { object ShowDialog : WalletAction() { object QrCode : WalletAction() object ScanFails : WalletAction() + object SignedHashesMultiWalletDialog : WalletAction() } object HideDialog : 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 244f7f026d..62de9a0a68 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 @@ -81,6 +81,9 @@ data class WalletState( && (walletData.token == null || walletData.token != store.state.walletState.primaryToken)) { val walletManager = getWalletManager(walletData.currencyData.currencySymbol) ?: return true + + if (walletData.token == null && walletManager.presetTokens.isNotEmpty()) return false + val wallet = walletManager.wallet if (walletData.blockchain != null) { return wallet.recentTransactions.toPendingTransactions(wallet.address).isEmpty() && @@ -112,7 +115,8 @@ sealed class WalletDialog: StateDialog { ) : WalletDialog() data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog() - object ScanFailsDialog: WalletDialog() + object ScanFailsDialog : WalletDialog() + object SignedHashesMultiWalletDialog : WalletDialog() } enum class ProgressState { Loading, Done, Error } 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 6daa182e35..da28809df6 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 @@ -10,6 +10,8 @@ import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.currenciesRepository +import com.tangem.tap.domain.extensions.makeWalletManagerForApp +import com.tangem.tap.domain.extensions.makeWalletManagersForApp import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.scope @@ -37,16 +39,16 @@ class MultiWalletMiddleware { globalState?.scanNoteResponse?.card?.cardId?.let { currenciesRepository.saveAddedToken(it, action.token) } - addToken(action.token, walletState) + addToken(action.token, walletState, globalState) } is WalletAction.MultiWallet.AddTokens -> { - action.tokens.map { addToken(it, walletState) } + action.tokens.map { addToken(it, walletState, globalState) } } is WalletAction.MultiWallet.AddBlockchain -> { globalState?.scanNoteResponse?.card?.let { card -> currenciesRepository.saveAddedBlockchain(card.cardId, action.blockchain) globalState.tapWalletManager.walletManagerFactory - .makeWalletManager(card, action.blockchain)?.let { + .makeWalletManagerForApp(card, action.blockchain)?.let { store.dispatch(WalletAction.MultiWallet.AddWalletManagers(it)) } } @@ -60,15 +62,18 @@ class MultiWalletMiddleware { is WalletAction.MultiWallet.RemoveWallet -> { val cardId = globalState?.scanNoteResponse?.card?.cardId if (action.walletData.token != null) { + walletState?.getWalletManagerForToken(action.walletData.token.symbol) + ?.removeToken(action.walletData.token) cardId?.let { currenciesRepository.removeToken(it, action.walletData.token) } } else if (action.walletData.blockchain != null) { cardId?.let { currenciesRepository.removeBlockchain(it, action.walletData.blockchain) } } } is WalletAction.MultiWallet.FindBlockchainsInUse -> { - val blockchains = currenciesRepository.getBlockchains() + val cardFirmware = globalState?.scanNoteResponse?.card?.firmwareVersion + val blockchains = currenciesRepository.getBlockchains(cardFirmware) .filterNot { walletState?.blockchains?.contains(it) == true } - val walletManagers = action.factory.makeWalletManagers(action.card, blockchains) + val walletManagers = action.factory.makeWalletManagersForApp(action.card, blockchains) scope.launch { walletManagers.map { walletManager -> @@ -117,8 +122,15 @@ class MultiWalletMiddleware { } } - private fun addToken(token: Token, walletState: WalletState?) { - val walletManager = walletState?.getWalletManager(token.symbol) + private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) { + val card = globalState?.scanNoteResponse?.card ?: return + val walletManager = walletState?.getWalletManager(token.symbol) ?: + globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp( + card = card, + blockchain = Blockchain.Ethereum + )?.also { walletManager -> + store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) + } scope.launch { val result = walletManager?.addToken(token) withContext(Dispatchers.Main) { 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 05993a08ef..e11a577a51 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 @@ -108,7 +108,11 @@ class WalletMiddleware { ) when (result) { is CompletionResult.Success -> { - globalState?.tapWalletManager?.onCardScanned(result.data) + val scanNoteResponse = + globalState?.scanNoteResponse?.copy(card = result.data) + scanNoteResponse?.let { + globalState.tapWalletManager.onCardScanned(scanNoteResponse) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index d158d3d323..211e5ef2b1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -6,7 +6,7 @@ import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.extensions.SimpleResult import com.tangem.commands.common.card.Card import com.tangem.commands.common.card.CardType -import com.tangem.commands.verifycard.VerifyCardState +import com.tangem.commands.verification.VerifyCardState import com.tangem.common.extensions.getType import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler @@ -14,6 +14,9 @@ import com.tangem.tap.common.extensions.isGreaterThan import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager +import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.tap.domain.extensions.hasSignedHashes +import com.tangem.tap.domain.extensions.remainingSignatures import com.tangem.tap.domain.isMultiwalletAllowed import com.tangem.tap.domain.twins.isTwinCard import com.tangem.tap.features.wallet.redux.WalletAction @@ -46,6 +49,17 @@ class WarningsMiddleware { is WalletAction.Warnings.AppRating.SetNeverToShow -> { preferencesStorage.appRatingLaunchObserver.setNeverToShow() } + is WalletAction.Warnings.CheckRemainingSignatures -> { + if (action.remainingSignatures != null && + action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING + ) { + addWarningMessage( + warning = + WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures), + autoUpdate = true + ) + } + } } } @@ -63,17 +77,18 @@ class WarningsMiddleware { } private fun showCardWarningsIfNeeded(globalState: GlobalState?) { - val validator = globalState?.scanNoteResponse?.walletManager as? SignatureCountValidator globalState?.scanNoteResponse?.card?.let { card -> globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local) + showWarningLowRemainingSignaturesIfNeeded(card) if (card.getType() != CardType.Release) { addWarningMessage(WarningMessagesManager.devCardWarning()) } else if (!preferencesStorage.wasCardScannedBefore(card.cardId)) { - checkIfWarningNeeded(card, validator)?.let { addWarningMessage(it) } + checkIfWarningNeeded(card)?.let { warning -> addWarningMessage(warning) } } if (card.getType() == CardType.Release) { if (globalState.scanNoteResponse.verifyResponse?.verificationState == - VerifyCardState.VerifiedOffline) { + VerifyCardState.VerifiedOffline + ) { addWarningMessage(WarningMessagesManager.onlineVerificationFailed()) } } @@ -81,13 +96,37 @@ class WarningsMiddleware { } } - private fun checkIfWarningNeeded( - card: Card, signatureCountValidator: SignatureCountValidator? = null, - ): WarningMessage? { - if (card.isTwinCard() || card.isMultiwalletAllowed) return null + private fun showWarningLowRemainingSignaturesIfNeeded(card: Card) { + val remainingSignatures = card.remainingSignatures + if (remainingSignatures != null && + remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING + ) { + addWarningMessage( + WarningMessagesManager.remainingSignaturesNotEnough( + remainingSignatures + ) + ) + } + } - return if (signatureCountValidator == null) { - if (card.walletSignedHashes ?: 0 > 0) { + private fun checkIfWarningNeeded( + card: Card + ): WarningMessage? { + if (card.isTwinCard()) return null + + if (card.isMultiwalletAllowed) { + return if (card.hasSignedHashes()) { + WarningMessagesManager.signedHashesMultiWalletWarning() + } else { + store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) + null + } + } + + val validator = store.state.walletState.walletManagers.firstOrNull() + as? SignatureCountValidator + return if (validator == null) { + if (card.hasSignedHashes()) { WarningMessagesManager.alreadySignedHashesWarning() } else { store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) @@ -108,10 +147,11 @@ class WarningsMiddleware { if (card.isTwinCard() || card.isMultiwalletAllowed) return - val validator = store.state.globalState.scanNoteResponse?.walletManager + val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator scope.launch { - val result = validator?.validateSignatureCount(card.walletSignedHashes ?: 0) + val signedHashes = card.getSingleWallet()?.signedHashes ?: 0 + val result = validator?.validateSignatureCount(signedHashes) withContext(Dispatchers.Main) { when (result) { SimpleResult.Success -> { @@ -120,9 +160,15 @@ class WarningsMiddleware { } is SimpleResult.Failure -> if (result.error is BlockchainSdkError.SignatureCountNotMatched) { - addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true) - } else if (card.walletSignedHashes ?: 0 > 0) { - addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true) + addWarningMessage( + WarningMessagesManager.alreadySignedHashesWarning(), + true + ) + } else if (signedHashes > 0) { + addWarningMessage( + WarningMessagesManager.alreadySignedHashesWarning(), + true + ) } } } @@ -140,6 +186,9 @@ class WarningsMiddleware { private fun getWarnings(): List { val warningManager = store.state.globalState.warningManager ?: return emptyList() - return warningManager.getWarnings(WarningMessage.Location.MainScreen, store.state.walletState.blockchains) + return warningManager.getWarnings( + WarningMessage.Location.MainScreen, + store.state.walletState.blockchains + ) } } \ No newline at end of file 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 957e9a32d3..06f40d4d68 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 @@ -225,6 +225,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState { is WalletAction.ShowDialog.ScanFails -> { newState = newState.copy(walletDialog = WalletDialog.ScanFailsDialog) } + is WalletAction.ShowDialog.SignedHashesMultiWalletDialog -> { + newState = newState.copy(walletDialog = WalletDialog.SignedHashesMultiWalletDialog) + } is WalletAction.HideDialog -> { newState = newState.copy(walletDialog = null) } 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 c14d364abf..c82c47f25f 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 @@ -6,10 +6,14 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import com.squareup.picasso.Callback +import com.squareup.picasso.Picasso import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.IconsUtil import com.tangem.blockchain.common.Token import com.tangem.tap.common.extensions.getColor import com.tangem.tap.common.extensions.getIconRes +import com.tangem.tap.common.extensions.loadCurrenciesIcon import com.tangem.tap.common.extensions.show import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletData @@ -17,6 +21,11 @@ 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 kotlinx.android.synthetic.main.item_currency_wallet.view.iv_currency +import kotlinx.android.synthetic.main.item_currency_wallet.view.tv_currency_symbol +import kotlinx.android.synthetic.main.item_currency_wallet.view.tv_token_letter +import kotlinx.android.synthetic.main.item_popular_token.view.* +import java.lang.Exception import java.math.BigDecimal class WalletAdapter @@ -89,16 +98,15 @@ class WalletAdapter view.card_wallet.setOnClickListener { store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) } - val blockchain = wallet.currencyData.currencySymbol?.let { Blockchain.fromCurrency(it) } - if (blockchain != null && blockchain != Blockchain.Unknown) { - view.tv_token_letter.text = null - view.iv_currency.colorFilter = null - view.iv_currency.setImageResource(blockchain.getIconRes()) - } else { - view.tv_token_letter.text = wallet.currencyData.currencySymbol?.take(1) - wallet.token?.getColor()?.let { view.iv_currency.setColorFilter(it) } - view.iv_currency.setImageResource(R.drawable.shape_circle) - } + val blockchain = wallet.blockchain + val token = wallet.token + + Picasso.get().loadCurrenciesIcon( + imageView = view.iv_currency, + textView = view.tv_token_letter, + token = token, blockchain = blockchain + ) + when (wallet.currencyData.status) { BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress -> hideWarning() BalanceStatus.Loading -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt index a15a4e3e9a..c262bbb35c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt @@ -37,9 +37,11 @@ class WarningMessagesAdapter : ListAdapter(Dif } object DiffUtilCallback : DiffUtil.ItemCallback() { - override fun areContentsTheSame(oldItem: WarningMessage, newItem: WarningMessage) = oldItem == newItem + override fun areContentsTheSame(oldItem: WarningMessage, newItem: WarningMessage) = + oldItem == newItem - override fun areItemsTheSame(oldItem: WarningMessage, newItem: WarningMessage) = oldItem == newItem + override fun areItemsTheSame(oldItem: WarningMessage, newItem: WarningMessage) = + oldItem == newItem } } @@ -52,10 +54,13 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) { } private fun setText(warning: WarningMessage) { - fun getString(resId: Int?, default: String) = if (resId == null) default else view.getString(resId) + fun getString(resId: Int?, default: String, formatArgs: String? = null) = + if (resId == null) default else view.context.getString(resId, formatArgs) view.tv_title.text = getString(warning.titleResId, warning.title) - view.tv_message.text = getString(warning.messageResId, warning.message) + view.tv_message.text = getString( + resId = warning.messageResId, default = warning.message, formatArgs = warning.messageFormatArg + ) } private fun setBgColor(priority: WarningMessage.Priority) { @@ -67,63 +72,83 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) { view.card_view.setCardBackgroundColor(view.context.resources.getColor(color)) } - private fun setupControlButtons(warning: WarningMessage) { - when (warning.type) { - WarningMessage.Type.Permanent -> { - view.group_controls_temporary.hide() - view.group_controls_rating.hide() - } - WarningMessage.Type.Temporary -> { - view.group_controls_rating.hide() - view.group_controls_temporary.show() - view.btn_got_it.setOnClickListener { store.dispatch(GlobalAction.HideWarningMessage(warning)) } - } - WarningMessage.Type.AppRating -> { - view.group_controls_temporary.hide() - view.group_controls_rating.show() - view.btn_close.setOnClickListener { - FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS) - store.dispatch(GlobalAction.HideWarningMessage(warning)) - store.dispatch(WalletAction.Warnings.AppRating.RemindLater) - } - view.btn_can_be_better.setOnClickListener { - FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE) - store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) - store.dispatch(GlobalAction.HideWarningMessage(warning)) - store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail())) - } - view.btn_really_cool.setOnClickListener { - val activity = view.context.getActivity() ?: return@setOnClickListener - - FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE) - store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) - val reviewManager = ReviewManagerFactory.create(activity) - val task = reviewManager.requestReviewFlow() - task.addOnCompleteListener { - if (it.isSuccessful) { - val reviewFlow = reviewManager.launchReviewFlow(activity, it.result) - reviewFlow.addOnCompleteListener { - if (it.isSuccessful) { - // send review was succeed - } else { - // send fails - } - } - } else { - Timber.e(task.exception) + private fun setupControlButtons(warning: WarningMessage) = when (warning.type) { + WarningMessage.Type.Permanent -> { + view.group_controls_temporary.hide() + view.group_controls_rating.hide() + } + WarningMessage.Type.Temporary -> { + view.group_controls_rating.hide() + view.group_controls_temporary.show() + val buttonAction = + when (warning.titleResId) { + R.string.warning_important_security_info -> { + View.OnClickListener { + store.dispatch(WalletAction.ShowDialog.SignedHashesMultiWalletDialog) + } + } + else -> { + View.OnClickListener { + store.dispatch(GlobalAction.HideWarningMessage(warning)) } - }.addOnFailureListener { - Timber.e(it) } - store.dispatch(GlobalAction.HideWarningMessage(warning)) } + val buttonTitle = view.getString( + warning.buttonTextId ?: R.string.how_to_got_it_button + ) + view.btn_got_it.setOnClickListener (buttonAction) + view.btn_got_it.text = buttonTitle + } + WarningMessage.Type.AppRating -> { + view.group_controls_temporary.hide() + view.group_controls_rating.show() + view.btn_close.setOnClickListener { + FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS) + store.dispatch(GlobalAction.HideWarningMessage(warning)) + store.dispatch(WalletAction.Warnings.AppRating.RemindLater) + } + view.btn_can_be_better.setOnClickListener { + FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE) + store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) + store.dispatch(GlobalAction.HideWarningMessage(warning)) + store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail())) + } + view.btn_really_cool.setOnClickListener { + val activity = view.context.getActivity() ?: return@setOnClickListener + + FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE) + store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) + val reviewManager = ReviewManagerFactory.create(activity) + val task = reviewManager.requestReviewFlow() + task.addOnCompleteListener { + if (it.isSuccessful) { + val reviewFlow = reviewManager.launchReviewFlow(activity, it.result) + reviewFlow.addOnCompleteListener { + if (it.isSuccessful) { + // send review was succeed + } else { + // send fails + } + } + } else { + Timber.e(task.exception) + } + }.addOnFailureListener { + Timber.e(it) + } + store.dispatch(GlobalAction.HideWarningMessage(warning)) } } } } class SpacesItemDecoration(private val spacePx: Int) : ItemDecoration() { - override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { outRect.left = spacePx outRect.right = spacePx diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt new file mode 100644 index 0000000000..9aaf4b88d6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.features.wallet.ui.dialogs + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import com.tangem.wallet.R + +class SignedHashesWarningDialog { + companion object { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.warning_important_security_info)) + setMessage(R.string.alert_signed_hashes_message) + setPositiveButton(R.string.alert_button_i_understand) { _, _ -> + store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) + store.dispatch( + GlobalAction.HideWarningMessage( + WarningMessagesManager.signedHashesMultiWalletWarning() + ) + ) + } + setNegativeButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { + store.dispatch(WalletAction.HideDialog) + } + }.create() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 9e2320d30c..5deff50687 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -15,6 +15,7 @@ import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.tap.features.wallet.ui.adapters.WalletAdapter import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog +import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.card_balance.* @@ -34,28 +35,26 @@ class MultiWalletView : WalletView { override fun changeWalletView(fragment: WalletFragment) { setFragment(fragment) onViewCreated() - showMultiWalletView() - setupButtons() + showMultiWalletView(fragment) + setupButtons(fragment) } - private fun showMultiWalletView() { - val fragment = fragment ?: return - fragment.tv_twin_card_number.hide() - fragment.iv_twin_card.hide() - fragment.rv_pending_transaction.hide() - fragment.l_card_balance.hide() - fragment.l_address.hide() - fragment.l_buttons_short.hide() - fragment.l_buttons_long.hide() - fragment.btn_scan_multiwallet?.show() - fragment.rv_multiwallet.show() - fragment.btn_add_token.show() + private fun showMultiWalletView(fragment: WalletFragment) = with(fragment) { + tv_twin_card_number.hide() + iv_twin_card.hide() + rv_pending_transaction.hide() + l_card_balance.hide() + l_address.hide() + l_buttons_short.hide() + l_buttons_long.hide() + btn_scan_multiwallet?.show() + rv_multiwallet.show() + btn_add_token.show() } - private fun setupButtons() { - val fragment = fragment ?: return - fragment.btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) } + private fun setupButtons(fragment: WalletFragment) = with(fragment) { + btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) } } override fun setFragment(fragment: WalletFragment) { @@ -96,40 +95,40 @@ class MultiWalletView : WalletView { when (state.primaryWallet?.currencyData?.status) { BalanceStatus.EmptyCard -> { showErrorState( - fragment, - fragment.getText(R.string.wallet_error_empty_card), - fragment.getString(R.string.wallet_error_empty_card_subtitle) + fragment, + fragment.getText(R.string.wallet_error_empty_card), + fragment.getString(R.string.wallet_error_empty_card_subtitle) ) configureButtonsForEmptyWalletState(fragment) } BalanceStatus.UnknownBlockchain -> { showErrorState( - fragment, - fragment.getText(R.string.wallet_error_unsupported_blockchain), - fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle) + fragment, + fragment.getText(R.string.wallet_error_unsupported_blockchain), + fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle) ) } } } private fun showErrorState( - fragment: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence, - ) { - fragment.l_card_balance.show() - fragment.l_balance.hide() - fragment.l_balance_error.show() - fragment.rv_multiwallet.hide() - fragment.btn_add_token.hide() - fragment.tv_error_title.text = errorTitle - fragment.tv_error_descriptions.text = errorDescription + fragment: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence, + ) = with(fragment) { + l_card_balance.show() + l_balance.hide() + l_balance_error.show() + rv_multiwallet.hide() + btn_add_token.hide() + tv_error_title.text = errorTitle + tv_error_descriptions.text = errorDescription } - private fun configureButtonsForEmptyWalletState(fragment: WalletFragment) { - fragment.btn_scan_multiwallet.hide() - fragment.l_buttons_long.show() - fragment.btn_scan_long.setOnClickListener { store.dispatch(WalletAction.Scan) } - fragment.btn_confirm_long.setOnClickListener { store.dispatch(WalletAction.CreateWallet) } - fragment.btn_confirm_long.text = fragment.getText(R.string.wallet_button_create_wallet) + private fun configureButtonsForEmptyWalletState(fragment: WalletFragment) = with(fragment) { + btn_scan_multiwallet.hide() + l_buttons_long.show() + btn_scan_long.setOnClickListener { store.dispatch(WalletAction.Scan) } + btn_confirm_long.setOnClickListener { store.dispatch(WalletAction.CreateWallet) } + btn_confirm_long.text = fragment.getText(R.string.wallet_button_create_wallet) } private fun handleDialogs(walletDialog: StateDialog?) { @@ -139,6 +138,11 @@ class MultiWalletView : WalletView { is WalletDialog.ScanFailsDialog -> { if (dialog == null) dialog = ScanFailsDialog.create(context).apply { show() } } + is WalletDialog.SignedHashesMultiWalletDialog -> { + if (dialog == null) { + dialog = SignedHashesWarningDialog.create(context).apply { show() } + } + } else -> { dialog?.dismiss() dialog = null 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 d6762f815b..56eba3612c 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 @@ -19,6 +19,7 @@ import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog import com.tangem.tap.features.wallet.ui.dialogs.QrDialog import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog +import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.card_balance.* @@ -44,17 +45,16 @@ class SingleWalletView : WalletView { override fun changeWalletView(fragment: WalletFragment) { setFragment(fragment) onViewCreated() - showSingleWalletView() + showSingleWalletView(fragment) } - private fun showSingleWalletView() { - val fragment = fragment ?: return - fragment.rv_multiwallet.hide() - fragment.btn_add_token.hide() - fragment.btn_scan_multiwallet?.hide() - fragment.rv_pending_transaction.hide() - fragment.l_card_balance.show() - fragment.l_address.show() + private fun showSingleWalletView(fragment: WalletFragment) = with(fragment) { + rv_multiwallet.hide() + btn_add_token.hide() + btn_scan_multiwallet?.hide() + rv_pending_transaction.hide() + l_card_balance.show() + l_address.show() } override fun onViewCreated() { @@ -70,13 +70,15 @@ class SingleWalletView : WalletView { } override fun onNewState(state: WalletState) { - setupTwinCards(state.twinCardsState) + val fragment = fragment ?: return state.primaryWallet ?: return - setupButtons(state.primaryWallet, state.twinCardsState != null) - setupAddressCard(state.primaryWallet) + + setupTwinCards(state.twinCardsState, fragment) + setupButtons(state.primaryWallet, state.twinCardsState != null, fragment) + setupAddressCard(state.primaryWallet, fragment) showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions) setupBalance(state, state.primaryWallet) - handleDialogs(state.walletDialog) + handleDialogs(state.walletDialog, fragment) } private fun showPendingTransactionsIfPresent(pendingTransactions: List) { @@ -92,43 +94,45 @@ class SingleWalletView : WalletView { } } - private fun setupTwinCards(twinCardsState: TwinCardsState?) { - fragment?.apply { - twinCardsState?.cardNumber?.let { cardNumber -> - this.tv_twin_card_number.show() - this.iv_twin_card.show() - val number = when (cardNumber) { - TwinCardNumber.First -> "1" - TwinCardNumber.Second -> "2" - } - this.tv_twin_card_number.text = - this.getString(R.string.wallet_twins_chip_format, number) - } - if (twinCardsState?.cardNumber == null) { - this.tv_twin_card_number.hide() - this.iv_twin_card.hide() - } - if (twinCardsState?.showTwinOnboarding == true) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding)) + private fun setupTwinCards( + twinCardsState: TwinCardsState?, fragment: WalletFragment + ) = with(fragment) { + twinCardsState?.cardNumber?.let { cardNumber -> + tv_twin_card_number.show() + iv_twin_card.show() + val number = when (cardNumber) { + TwinCardNumber.First -> "1" + TwinCardNumber.Second -> "2" } + tv_twin_card_number.text = + this.getString(R.string.wallet_twins_chip_format, number) } + if (twinCardsState?.cardNumber == null) { + tv_twin_card_number.hide() + iv_twin_card.hide() + } + if (twinCardsState?.showTwinOnboarding == true) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding)) + } + } - private fun setupButtons(state: WalletData, isTwinsWallet: Boolean) { - val fragment = fragment ?: return + private fun setupButtons( + state: WalletData, isTwinsWallet: Boolean, fragment: WalletFragment + ) = with(fragment){ setupButtonsType(state, fragment) val btnConfirm = if (state.topUpState.allowed) { - fragment.btn_confirm_short + btn_confirm_short } else { - fragment.btn_confirm_long + btn_confirm_long } val btnScan = if (state.topUpState.allowed) { - fragment.btn_scan_short + btn_scan_short } else { - fragment.btn_scan_long + btn_scan_long } setupConfirmButton(state, btnConfirm, fragment, isTwinsWallet) @@ -137,27 +141,27 @@ class SingleWalletView : WalletView { store.dispatch(WalletAction.Scan) } - fragment.btn_copy.setOnClickListener { + btn_copy.setOnClickListener { state.walletAddresses?.selectedAddress?.address?.let { addressString -> store.dispatch(WalletAction.CopyAddress(addressString, fragment.requireContext())) } } - fragment.btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) } + btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) } - fragment.btn_top_up.setOnClickListener { + btn_top_up.setOnClickListener { store.dispatch( WalletAction.TopUpAction.TopUp(fragment.requireContext(), R.color.backgroundLightGray) ) } } - private fun setupButtonsType(state: WalletData, fragment: WalletFragment) { + private fun setupButtonsType(state: WalletData, fragment: WalletFragment) = with(fragment) { if (state.topUpState.allowed) { - fragment.l_buttons_long.hide() - fragment.l_buttons_short.show() + l_buttons_long.hide() + l_buttons_short.show() } else { - fragment.l_buttons_long.show() - fragment.l_buttons_short.hide() + l_buttons_long.show() + l_buttons_short.hide() } } @@ -185,39 +189,37 @@ class SingleWalletView : WalletView { } - private fun setupAddressCard(state: WalletData) { - val fragment = fragment ?: return + private fun setupAddressCard(state: WalletData, fragment: WalletFragment) = with(fragment) { if (state.walletAddresses != null && state.blockchain != null) { - fragment.l_address?.show() + l_address?.show() if (state.shouldShowMultipleAddress()) { - (fragment.l_address as? ViewGroup)?.beginDelayedTransition() - fragment.chip_group_address_type.show() - fragment.chip_group_address_type.fitChipsByGroupWidth() + (l_address as? ViewGroup)?.beginDelayedTransition() + chip_group_address_type.show() + chip_group_address_type.fitChipsByGroupWidth() val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) - if (checkedId != View.NO_ID) fragment.chip_group_address_type.check(checkedId) + if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId) - fragment.chip_group_address_type.setOnCheckedChangeListener { group, checkedId -> + chip_group_address_type.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener val type = MultipleAddressUiHelper.idToType(checkedId, state.blockchain) type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } } else { - fragment.chip_group_address_type.hide() + chip_group_address_type.hide() } - fragment.tv_address.text = state.walletAddresses.selectedAddress.address - fragment.tv_explore?.setOnClickListener { + tv_address.text = state.walletAddresses.selectedAddress.address + tv_explore?.setOnClickListener { store.dispatch(WalletAction.ExploreAddress( state.walletAddresses.selectedAddress.exploreUrl, fragment.requireContext())) } } else { - fragment.l_address?.hide() + l_address?.hide() } } - private fun handleDialogs(walletDialog: StateDialog?) { - val fragment = fragment ?: return + private fun handleDialogs(walletDialog: StateDialog?, fragment: WalletFragment) { val context = fragment.context ?: return when (walletDialog) { is WalletDialog.QrDialog -> { @@ -237,6 +239,11 @@ class SingleWalletView : WalletView { is WalletDialog.ScanFailsDialog -> { if (dialog == null) dialog = ScanFailsDialog.create(context).apply { show() } } + is WalletDialog.SignedHashesMultiWalletDialog -> { + if (dialog == null) { + dialog = SignedHashesWarningDialog.create(context).apply { show() } + } + } null -> { dialog?.dismiss() dialog = null diff --git a/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt b/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt index 05c2b6a6bd..00c2f87e19 100644 --- a/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt +++ b/app/src/main/java/com/tangem/tap/network/NetworkConnectivity.kt @@ -44,6 +44,7 @@ class NetworkConnectivity( (capabilities != null) && (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) } else { diff --git a/app/src/main/res/drawable-ldpi/ic_qcx.png b/app/src/main/res/drawable-ldpi/ic_qcx.png new file mode 100644 index 0000000000..68f2ded287 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_qcx.png differ diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index 00c0626cba..fc33c31d7d 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -108,6 +108,8 @@ android:layout_height="wrap_content" android:textColor="@color/darkGray1" android:textSize="16sp" + android:paddingTop="7dp" + android:paddingBottom="7dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_issuer" tools:text="48 hashes" /> diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 238bc7a845..339ccae0b2 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -103,5 +103,14 @@ this wallet. This card might be a production sample or counterfeit + Important security information \u26A0 + This card has signed transactions in the past + Learn more + This card is not a bearer note. We can’t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern. + \n\nDo not accept this card as physical payment from someone you don’t trust. + \n\nIt’s perfectly safe in all other respects. + \n\nTangem is the only hardware wallet to offer signature count protection. + I understand + There are only %s signatures available on this card. You must withdraw all of your funds. diff --git a/dependencies.gradle b/dependencies.gradle index f74991aa26..22dc84c906 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ - kotlin : '1.4.31', - build_gradle: '4.1.3', + kotlin : '1.5.0', + build_gradle: '4.2.0', ] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 385ee4fcc0..c6dab66ff6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip