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..8494a61786 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,16 @@ 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 blockchainList: List? by lazy { blockchains?.map { Blockchain.fromId(it.toUpperCase()) } @@ -43,6 +46,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..8bde4ae283 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 @@ -3,6 +3,8 @@ package com.tangem.tap.domain.configurable.warningMessage 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 +100,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( "", "", 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..18ffaa31b1 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 @@ -111,6 +111,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 0dabc601c8..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 @@ -115,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/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index 79ab8e3feb..46a7693492 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 @@ -87,9 +87,18 @@ class WarningsMiddleware { ): 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 || card.isMultiwalletAllowed) { + return if (validator == null) { if (card.hasSignedHashes()) { WarningMessagesManager.alreadySignedHashesWarning() } else { 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/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt index a15a4e3e9a..80b739b21c 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,7 +54,8 @@ 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) = + if (resId == null) default else view.getString(resId) view.tv_title.text = getString(warning.titleResId, warning.title) view.tv_message.text = getString(warning.messageResId, warning.message) @@ -67,63 +70,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 6e69a80b7d..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.* @@ -94,24 +95,24 @@ 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: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence, ) = with(fragment) { l_card_balance.show() l_balance.hide() @@ -137,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 97f2618542..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.* @@ -238,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/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 238bc7a845..762001572e 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -103,5 +103,12 @@ 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