Updated on 2026-08-14
This commit is contained in:
commit
31bae3fd2c
11 changed files with 178 additions and 70 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
"",
|
||||
"",
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ sealed class WalletAction : Action {
|
|||
object ShowDialog : WalletAction() {
|
||||
object QrCode : WalletAction()
|
||||
object ScanFails : WalletAction()
|
||||
object SignedHashesMultiWalletDialog : WalletAction()
|
||||
}
|
||||
|
||||
object HideDialog : WalletAction()
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 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.</string>
|
||||
<string name="alert_button_i_understand" translatable="false">I understand</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue