Updated on 2026-08-14

This commit is contained in:
Tangem 2021-05-14 11:40:51 +00:00
commit 31bae3fd2c
11 changed files with 178 additions and 70 deletions

View file

@ -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<Location>,
private val blockchains: List<String>?,
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<Location>,
private val blockchains: List<String>?,
@StringRes val titleResId: Int? = null,
@StringRes val messageResId: Int? = null,
val origin: Origin = Origin.Remote,
@StringRes val buttonTextId: Int? = null,
) {
val blockchainList: List<Blockchain>? by lazy {
blockchains?.map { Blockchain.fromId(it.toUpperCase()) }
@ -43,6 +46,7 @@ data class WarningMessage(
Temporary, // можно скрыть (кнопка ОК)
AppRating
}
enum class Location {

View file

@ -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(
"",
"",

View file

@ -111,6 +111,7 @@ sealed class WalletAction : Action {
object ShowDialog : WalletAction() {
object QrCode : WalletAction()
object ScanFails : WalletAction()
object SignedHashesMultiWalletDialog : WalletAction()
}
object HideDialog : WalletAction()

View file

@ -115,7 +115,8 @@ sealed class WalletDialog: StateDialog {
) : WalletDialog()
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog()
object ScanFailsDialog: WalletDialog()
object ScanFailsDialog : WalletDialog()
object SignedHashesMultiWalletDialog : WalletDialog()
}
enum class ProgressState { Loading, Done, Error }

View file

@ -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 {

View file

@ -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)
}

View file

@ -37,9 +37,11 @@ class WarningMessagesAdapter : ListAdapter<WarningMessage, WarningMessageVH>(Dif
}
object DiffUtilCallback : DiffUtil.ItemCallback<WarningMessage>() {
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

View file

@ -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()
}
}
}

View file

@ -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

View file

@ -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

View file

@ -103,5 +103,12 @@ this wallet.
<string name="warning_failed_to_verify_card_message" translatable="false">This card might be a production sample or counterfeit</string>
<string name="warning_important_security_info" translatable="false">Important security information \u26A0</string>
<string name="warning_signed_tx_previously" translatable="false">This card has signed transactions in the past</string>
<string name="warning_button_learn_more" translatable="false">Learn more</string>
<string name="alert_signed_hashes_message" translatable="false">This card is not a bearer note. We cant 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 dont trust.
\n\nIts perfectly safe in all other respects.
\n\nTangem is the only hardware wallet to offer signature count protection.</string>
<string name="alert_button_i_understand" translatable="false">I understand</string>
</resources>