Updated on 2026-08-14

This commit is contained in:
Tangem 2021-02-17 08:42:43 +00:00
commit 2cfb552d19
33 changed files with 571 additions and 131 deletions

View file

@ -8,9 +8,11 @@ import com.tangem.tap.common.images.PicassoHelper
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.config.ConfigManager
import com.tangem.tap.domain.config.LocalLoader
import com.tangem.tap.domain.config.RemoteLoader
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
import com.tangem.tap.domain.configurable.config.FeaturesRemoteLoader
import com.tangem.tap.domain.configurable.warningMessage.RemoteWarningLoader
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.network.createMoshi
import com.tangem.tap.persistence.PreferencesStorage
@ -50,9 +52,11 @@ class TapApplication : Application() {
private fun loadConfigs() {
val moshi = createMoshi()
val localLoader = LocalLoader(this, moshi)
val remoteLoader = RemoteLoader(moshi)
val localLoader = FeaturesLocalLoader(this, moshi)
val remoteLoader = FeaturesRemoteLoader(moshi)
val configManager = ConfigManager(localLoader, remoteLoader)
configManager.load { store.dispatch(GlobalAction.SetConfigManager(configManager)) }
val warningsManager = WarningMessagesManager(RemoteWarningLoader(moshi))
warningsManager.load { store.dispatch(GlobalAction.SetWarningManager(warningsManager)) }
}
}

View file

@ -1,6 +1,8 @@
package com.tangem.tap.common.redux.global
import com.tangem.tap.domain.config.ConfigManager
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.features.details.redux.SecurityOption
import org.rekotlin.Action
@ -18,5 +20,7 @@ sealed class GlobalAction : Action {
}
data class UpdateWalletSignedHashes(val walletSignedHashes: Int?) : GlobalAction()
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
data class HideWarningMessage(val warning: WarningMessage) : GlobalAction()
data class UpdateSecurityOptions(val securityOption: SecurityOption) : GlobalAction()
}

View file

@ -1,6 +1,10 @@
package com.tangem.tap.common.redux.global
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.preferencesStorage
import com.tangem.tap.store
import org.rekotlin.Middleware
@ -14,6 +18,19 @@ val globalMiddleware: Middleware<AppState> = { dispatch, appState ->
preferencesStorage.getAppCurrency()
))
}
is GlobalAction.HideWarningMessage -> {
store.state.globalState.warningManager?.let {
if (it.hideWarning(action.warning)) {
if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
//TODO: No appropriate warningMessage identification. Make it better later
store.dispatch(WalletAction.SaveCardId)
}
store.dispatch(WalletAction.SetWarnings(it.getWarnings(WarningMessage.Location.MainScreen)))
store.dispatch(SendAction.SetWarnings(it.getWarnings(WarningMessage.Location.SendScreen)))
}
}
}
}
nextDispatch(action)
}

View file

@ -37,6 +37,8 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.SetConfigManager -> {
globalState.copy(configManager = action.configManager)
}
is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
is GlobalAction.HideWarningMessage -> globalState
is GlobalAction.UpdateSecurityOptions -> {
val card = when (action.securityOption) {
SecurityOption.LongTap -> globalState.scanNoteResponse?.card?.copy(

View file

@ -4,7 +4,8 @@ import com.tangem.commands.common.network.TangemService
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.domain.config.ConfigManager
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
import org.rekotlin.StateType
@ -18,6 +19,7 @@ data class GlobalState(
val tangemService: TangemService = TangemService(),
val conversionRates: ConversionRates = ConversionRates(emptyMap()),
val configManager: ConfigManager? = null,
val warningManager: WarningMessagesManager? = null,
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY
) : StateType

View file

@ -12,7 +12,7 @@ import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.config.ConfigManager
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.tasks.ScanNoteResponse
@ -84,6 +84,8 @@ class TapWalletManager {
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card)
}
TapWorkarounds.updateCard(data.card)
store.state.globalState.warningManager?.setBlockchain(data.walletManager?.wallet?.blockchain)
val configManager = store.state.globalState.configManager
if (TapWorkarounds.isStart2Coin) {
configManager?.turnOff(ConfigManager.isWalletPayIdEnabled)

View file

@ -0,0 +1,16 @@
package com.tangem.tap.domain.configurable
import com.tangem.wallet.BuildConfig
/**
[REDACTED_AUTHOR]
*/
interface Loader<T> {
fun load(onComplete: (T) -> Unit)
companion object {
const val featuresName = "features_${BuildConfig.CONFIG_ENVIRONMENT}"
const val configValuesName = "config_${BuildConfig.CONFIG_ENVIRONMENT}"
const val warnings = "warnings_${BuildConfig.CONFIG_ENVIRONMENT}"
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.tap.domain.config
package com.tangem.tap.domain.configurable.config
import android.content.Context
import com.google.firebase.ktx.Firebase
@ -6,34 +6,24 @@ import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.wallet.BuildConfig
import com.tangem.tap.domain.configurable.Loader
import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
interface ConfigLoader {
fun loadConfig(onComplete: (ConfigModel) -> Unit)
companion object {
const val featuresName = "features_${BuildConfig.CONFIG_ENVIRONMENT}"
const val configValuesName = "config_${BuildConfig.CONFIG_ENVIRONMENT}"
}
}
class LocalLoader(
class FeaturesLocalLoader(
private val context: Context,
private val moshi: Moshi
) : ConfigLoader {
private val moshi: Moshi,
) : Loader<ConfigModel> {
override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
override fun load(onComplete: (ConfigModel) -> Unit) {
val config = try {
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
val valuesAdapter: JsonAdapter<ConfigValueModel> = moshi.adapter(ConfigValueModel::class.java)
val jsonFeatures = readAssetAsString(ConfigLoader.featuresName)
val jsonConfigValues = readAssetAsString(ConfigLoader.configValuesName)
val jsonFeatures = readAssetAsString(Loader.featuresName)
val jsonConfigValues = readAssetAsString(Loader.configValuesName)
ConfigModel(featureAdapter.fromJson(jsonFeatures), valuesAdapter.fromJson(jsonConfigValues))
} catch (ex: Exception) {
@ -48,16 +38,16 @@ class LocalLoader(
}
}
class RemoteLoader(
private val moshi: Moshi
) : ConfigLoader {
class FeaturesRemoteLoader(
private val moshi: Moshi,
) : Loader<ConfigModel> {
override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
override fun load(onComplete: (ConfigModel) -> Unit) {
val emptyConfig = ConfigModel.empty()
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener {
if (it.isSuccessful) {
val config = remoteConfig.getValue(ConfigLoader.featuresName)
val config = remoteConfig.getValue(Loader.featuresName)
val jsonConfig = config.asString()
if (jsonConfig.isEmpty()) {
onComplete(emptyConfig)
@ -69,7 +59,7 @@ class RemoteLoader(
onComplete(emptyConfig)
}
}.addOnFailureListener {
FirebaseAnalyticsHandler.logException("remote_config_error", it)
FirebaseAnalyticsHandler.logException("remote_config_error.features", it)
onComplete(emptyConfig)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.domain.config
package com.tangem.tap.domain.configurable.config
import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
import com.tangem.tap.domain.configurable.Loader
/**
[REDACTED_AUTHOR]
@ -16,8 +17,8 @@ data class Config(
)
class ConfigManager(
private val localLoader: ConfigLoader,
private val remoteLoader: ConfigLoader
private val localLoader: Loader<ConfigModel>,
private val remoteLoader: Loader<ConfigModel>
) {
var config: Config = Config()
@ -26,11 +27,11 @@ class ConfigManager(
private var defaultConfig = Config()
fun load(onComplete: VoidCallback? = null) {
localLoader.loadConfig { config ->
localLoader.load { config ->
setupFeature(config.features)
setupKey(config.configValues)
}
remoteLoader.loadConfig { config ->
remoteLoader.load { config ->
setupFeature(config.features)
onComplete?.invoke()
}

View file

@ -1,4 +1,4 @@
package com.tangem.tap.domain.config
package com.tangem.tap.domain.configurable.config
/**
[REDACTED_AUTHOR]

View file

@ -0,0 +1,52 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.domain.configurable.Loader
/**
[REDACTED_AUTHOR]
*/
class RemoteWarningLoader(
private val moshi: Moshi,
) : Loader<List<WarningMessage>> {
override fun load(onComplete: (List<WarningMessage>) -> Unit) {
val emptyConfig = listOf<WarningMessage>()
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener {
if (!it.isSuccessful) {
onComplete(emptyConfig)
return@addOnCompleteListener
}
val config = remoteConfig.getValue(Loader.warnings)
val jsonConfig = config.asString()
if (jsonConfig.isEmpty()) {
onComplete(emptyConfig)
return@addOnCompleteListener
}
val adapterType = Types.newParameterizedType(List::class.java, WarningMessage::class.java)
val warningsAdapter: JsonAdapter<List<WarningMessage>> = moshi.adapter(adapterType)
try {
val warnings = warningsAdapter.fromJson(jsonConfig) ?: listOf()
onComplete(warnings)
} catch (ex: Exception) {
handleError(ex)
onComplete(emptyConfig)
}
}.addOnFailureListener {
handleError(it)
onComplete(emptyConfig)
}
}
private fun handleError(ex: Exception) {
FirebaseAnalyticsHandler.logException("remote_config_error.warnings", ex)
}
}

View file

@ -0,0 +1,57 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.squareup.moshi.Json
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 blockchainList: List<Blockchain>? by lazy {
blockchains?.map { Blockchain.fromId(it.toUpperCase()) }
}
var isHidden = false
enum class Priority {
@Json(name = "critical")
Critical,
@Json(name = "warning")
Warning,
@Json(name = "info")
Info
}
enum class Type {
@Json(name = "permanent")
Permanent, // нельзя скрыть
@Json(name = "temporary")
Temporary // можно скрыть (кнопка ОК)
}
enum class Location {
@Json(name = "main")
MainScreen,
@Json(name = "send")
SendScreen
}
enum class Origin {
Local, Remote
}
}

View file

@ -0,0 +1,108 @@
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.wallet.R
/**
[REDACTED_AUTHOR]
*/
class WarningMessagesManager(
private val warningLoader: RemoteWarningLoader,
) {
private var blockchain: Blockchain? = null
private val warningsList: MutableList<WarningMessage> = mutableListOf()
fun load(onComplete: VoidCallback? = null) {
warningLoader.load { remoteList ->
warningsList.clear()
warningsList.addAll(remoteList)
sortByPriority()
onComplete?.invoke()
}
}
fun setBlockchain(blockchain: Blockchain?) {
this.blockchain = blockchain
}
fun addWarning(warning: WarningMessage) {
if (findWarning(warning) == null) {
warningsList.add(warning)
sortByPriority()
}
}
fun getWarnings(location: WarningMessage.Location): List<WarningMessage> {
return warningsList
.filter { !it.isHidden && it.location.contains(location) }
.filter {
val blockchainList = it.blockchainList
when {
blockchainList == null -> true
blockchainList.contains(blockchain) -> true
else -> false
}
}
}
fun hideWarning(warning: WarningMessage): Boolean {
val foundWarning = findWarning(warning)
return when {
foundWarning == null -> false
foundWarning.type == WarningMessage.Type.Temporary -> {
if (foundWarning.isHidden) {
false
} else {
foundWarning.isHidden = true
true
}
}
else -> false
}
}
fun removeWarnings(origin: WarningMessage.Origin) {
warningsList.removeIf { it.origin == origin }
sortByPriority()
}
private fun sortByPriority() {
warningsList.sortBy { it.priority.ordinal }
}
private fun findWarning(warning: WarningMessage): WarningMessage? {
return warningsList.firstOrNull { it == warning }
}
companion object {
fun devCardWarning(): WarningMessage = WarningMessage(
"",
"",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.alert_title,
R.string.alert_developer_card,
WarningMessage.Origin.Local
)
fun alreadySignedHashesWarning(): WarningMessage = WarningMessage(
"",
"",
type = WarningMessage.Type.Temporary,
priority = WarningMessage.Priority.Info,
listOf(WarningMessage.Location.MainScreen),
null,
R.string.alert_title,
R.string.alert_card_signed_transactions,
WarningMessage.Origin.Local
)
fun isAlreadySignedHashesWarning(warning: WarningMessage):Boolean {
return warning.messageResId == R.string.alert_card_signed_transactions
}
}
}

View file

@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.ToastNotificationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.redux.states.SendButtonState
@ -146,4 +147,5 @@ sealed class SendAction : SendScreenAction {
object Hide : Dialog()
}
data class SetWarnings(val warningList: List<WarningMessage>) : SendAction()
}

View file

@ -44,6 +44,7 @@ private class SendReducer : SendInternalReducer {
is SendAction.ChangeSendButtonState -> sendState.copy(sendButtonState = action.state)
is SendAction.Dialog.ShowTezosWarningDialog -> sendState.copy(dialog = action)
is SendAction.Dialog.Hide -> sendState.copy(dialog = null)
is SendAction.SetWarnings -> sendState.copy(sendWarningsList = action.warningList)
else -> return sendState
}

View file

@ -8,6 +8,7 @@ import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.common.entities.TapCurrency
import com.tangem.tap.common.text.DecimalDigitsInputFilter
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.store
import org.rekotlin.StateType
@ -37,6 +38,7 @@ data class SendState(
val amountState: AmountState = AmountState(),
val feeState: FeeState = FeeState(),
val receiptState: ReceiptState = ReceiptState(),
val sendWarningsList: List<WarningMessage> = listOf(),
val sendButtonState: SendButtonState = SendButtonState.DISABLED,
val dialog: SendAction.Dialog? = null
) : SendScreenState {

View file

@ -9,9 +9,12 @@ import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.core.view.postDelayed
import androidx.core.widget.addTextChangedListener
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
@ -22,6 +25,7 @@ import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity
import com.tangem.tap.common.snackBar.MaxAmountSnackbar
import com.tangem.tap.common.text.truncateMiddleWith
import com.tangem.tap.common.toggleWidget.*
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.features.send.BaseStoreFragment
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.*
@ -30,10 +34,14 @@ import com.tangem.tap.features.send.redux.FeeActionUi.*
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
import com.tangem.tap.features.wallet.ui.SpacesItemDecoration
import com.tangem.tap.features.wallet.ui.WarningMessagesAdapter
import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_send.*
import kotlinx.android.synthetic.main.fragment_send.rv_warning_messages
import kotlinx.android.synthetic.main.fragment_wallet.*
import kotlinx.android.synthetic.main.layout_send_address_payid.*
import kotlinx.android.synthetic.main.layout_send_amount.*
import kotlinx.android.synthetic.main.layout_send_fee.*
@ -50,6 +58,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
lateinit var sendBtn: ToggleWidget
private lateinit var etAmountToSend: TextInputEditText
private lateinit var warningsAdapter: WarningMessagesAdapter
private fun initSendButtonStates() {
sendBtn = ToggleWidget(flSendButtonContainer, btnSend, progress, ProgressState.None())
@ -70,6 +79,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
setupTransactionExtrasLayout()
setupAmountLayout()
setupFeeLayout()
setupWarningMessages()
btnSend.setOnClickListener {
store.dispatch(SendActionUi.SendAmountToRecipient(
@ -227,6 +237,17 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
private fun setupWarningMessages() {
warningsAdapter = WarningMessagesAdapter()
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
rv_warning_messages.layoutManager = layoutManager
rv_warning_messages.addItemDecoration(SpacesItemDecoration(rv_warning_messages.dpToPx(16f).toInt()))
rv_warning_messages.adapter = warningsAdapter
val warnings = store.state.globalState.warningManager?.getWarnings(WarningMessage.Location.SendScreen) ?: listOf()
store.dispatch(SendAction.SetWarnings(warnings))
}
override fun subscribeToStore() {
store.subscribe(sendSubscriber) { appState ->
appState.skipRepeats { oldState, newState ->

View file

@ -22,6 +22,7 @@ import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.send.ui.FeeUiHelper
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.send.ui.dialogs.TezosWarningDialog
import com.tangem.tap.features.wallet.ui.WarningMessagesAdapter
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.btn_expand_collapse.*
@ -122,6 +123,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
sendFragment.sendBtn.setState(ProgressState.Progress(), true)
}
}
val rv = fg.rv_warning_messages
val adapter = rv.adapter as? WarningMessagesAdapter ?: return
adapter.submitList(state.sendWarningsList)
rv.show(state.sendWarningsList.isNotEmpty())
}
private fun handleAddressPayIdState(fg: BaseStoreFragment, state: AddressPayIdState) {

View file

@ -9,6 +9,7 @@ import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.wallet.R
import org.rekotlin.Action
@ -34,7 +35,7 @@ sealed class WalletAction : Action {
object CheckHashesCountOnline : WalletAction()
object NeedToCheckHashesCountOnline : WalletAction()
object ConfirmHashesCount : WalletAction()
data class ShowWarning(val warningType: WarningType) : WalletAction()
data class SetWarnings(val warningList: List<WarningMessage>) : WalletAction()
object SaveCardId : WalletAction()
object UpdateWallet : WalletAction() {

View file

@ -20,6 +20,8 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TopUpHelper
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.domain.twins.TwinsHelper
import com.tangem.tap.domain.twins.isTwinCard
@ -172,13 +174,17 @@ class WalletMiddleware {
}
}
is WalletAction.CheckIfWarningNeeded -> {
val card = store.state.globalState.scanNoteResponse?.card
val validator = store.state.globalState.scanNoteResponse?.walletManager
as? SignatureCountValidator
if (card != null && !preferencesStorage.wasCardScannedBefore(card.cardId)) {
val result = checkIfWarningNeeded(card, validator)
if (result != null) store.dispatch(WalletAction.ShowWarning(result))
val globalState = store.state.globalState
val validator = globalState.scanNoteResponse?.walletManager as? SignatureCountValidator
globalState.scanNoteResponse?.card?.let { card ->
store.state.globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
if (card.getType() != CardType.Release) addWarningMessage(WarningMessagesManager.devCardWarning())
if (!preferencesStorage.wasCardScannedBefore(card.cardId)) {
checkIfWarningNeeded(card, validator)?.let { addWarningMessage(it) }
}
updateWarningMessages()
}
}
is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline()
is WalletAction.SaveCardId -> {
@ -230,17 +236,13 @@ class WalletMiddleware {
}
private fun checkIfWarningNeeded(
card: Card, signatureCountValidator: SignatureCountValidator? = null
): WarningType? {
if (card.getType() != CardType.Release) {
return WarningType.DevCard
}
card: Card, signatureCountValidator: SignatureCountValidator? = null,
): WarningMessage? {
if (card.isTwinCard()) return null
return if (signatureCountValidator == null) {
if (card.walletSignedHashes ?: 0 > 0) {
WarningType.CardSignedHashesBefore
WarningMessagesManager.alreadySignedHashesWarning()
} else {
store.dispatch(WalletAction.SaveCardId)
null
@ -272,14 +274,24 @@ class WalletMiddleware {
}
is SimpleResult.Failure ->
if (result.error is BlockchainSdkError.SignatureCountNotMatched) {
store.dispatch(WalletAction.ShowWarning(WarningType.CardSignedHashesBefore))
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
} else if (card.walletSignedHashes ?: 0 > 0) {
store.dispatch(WalletAction.ShowWarning(WarningType.CardSignedHashesBefore))
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
}
}
}
}
}
private fun addWarningMessage(warning: WarningMessage, autoUpdate: Boolean = false) {
store.state.globalState.warningManager?.addWarning(warning)
if (autoUpdate) updateWarningMessages()
}
private fun updateWarningMessages() {
val warningManager = store.state.globalState.warningManager ?: return
store.dispatch(WalletAction.SetWarnings(warningManager.getWarnings(WarningMessage.Location.MainScreen)))
}
}
private class TopUpMiddleware {

View file

@ -215,8 +215,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
}
is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null)
is WalletAction.ShowWarning ->
newState = newState.copy(walletDialog = WalletDialog.WarningDialog(action.warningType))
is WalletAction.SetWarnings -> newState = newState.copy(mainWarningsList = action.warningList)
is WalletAction.NeedToCheckHashesCountOnline ->
newState = newState.copy(hashesCountVerified = false)
is WalletAction.ConfirmHashesCount ->

View file

@ -7,6 +7,7 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.address.AddressType
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
@ -27,6 +28,7 @@ data class WalletState(
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
val topUpState: TopUpState = TopUpState(),
val twinCardsState: TwinCardsState? = null,
val mainWarningsList: List<WarningMessage> = mutableListOf()
) : StateType {
val showDetails: Boolean =
currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard &&
@ -49,13 +51,9 @@ sealed class WalletDialog {
data class CreatePayIdDialog(val creatingPayIdState: CreatingPayIdState?) : WalletDialog()
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog()
data class WarningDialog(val type: WarningType) : WalletDialog()
data class TwinsOnboardingFragment(val secondCardId: String): WalletDialog()
}
enum class WarningType { CardSignedHashesBefore, DevCard }
enum class ProgressState { Loading, Done, Error }
enum class ErrorType { NoInternetConnection }

View file

@ -8,6 +8,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionInflater
import com.google.android.material.snackbar.Snackbar
import com.squareup.picasso.Picasso
@ -15,6 +16,7 @@ import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
import com.tangem.blockchain.blockchains.cardano.CardanoAddressType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.address.AddressType
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
@ -24,7 +26,6 @@ import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
import com.tangem.tap.features.wallet.ui.dialogs.PayIdDialog
import com.tangem.tap.features.wallet.ui.dialogs.QrDialog
import com.tangem.tap.features.wallet.ui.dialogs.WarningDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.card_balance.*
@ -41,7 +42,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
private var dialog: Dialog? = null
private var snackbar: Snackbar? = null
private lateinit var viewAdapter: PendingTransactionsAdapter
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
private lateinit var warningsAdapter: WarningMessagesAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -78,14 +80,22 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
setupWarningsRecyclerView()
setupTransactionsRecyclerView()
}
private fun setupWarningsRecyclerView() {
warningsAdapter = WarningMessagesAdapter()
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
rv_warning_messages.layoutManager = layoutManager
rv_warning_messages.addItemDecoration(SpacesItemDecoration(rv_warning_messages.dpToPx(16f).toInt()))
rv_warning_messages.adapter = warningsAdapter
}
private fun setupTransactionsRecyclerView() {
viewAdapter = PendingTransactionsAdapter()
pendingTransactionAdapter = PendingTransactionsAdapter()
rv_pending_transaction.layoutManager = LinearLayoutManager(context)
rv_pending_transaction.adapter = viewAdapter
rv_pending_transaction.adapter = pendingTransactionAdapter
}
@ -122,12 +132,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
setupAddressCard(state)
setupCardImage(state.cardImage)
viewAdapter.submitList(state.pendingTransactions)
if (state.pendingTransactions.isEmpty()) {
rv_pending_transaction.hide()
} else {
rv_pending_transaction.show()
}
warningsAdapter.submitList(state.mainWarningsList)
rv_warning_messages.show(state.mainWarningsList.isNotEmpty())
pendingTransactionAdapter.submitList(state.pendingTransactions)
rv_pending_transaction.show(state.pendingTransactions.isNotEmpty())
handleDialogs(state.walletDialog)
@ -292,11 +301,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
this.show(walletDialog.amounts)
}
}
is WalletDialog.WarningDialog -> {
if (dialog == null) dialog = WarningDialog(requireContext()).apply {
this.show(walletDialog.type)
}
}
null -> {
dialog?.dismiss()
dialog = null

View file

@ -0,0 +1,78 @@
package com.tangem.tap.features.wallet.ui
import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.layout_warning.view.*
class WarningMessagesAdapter : ListAdapter<WarningMessage, WarningMessageVH>(DiffUtilCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WarningMessageVH {
val inflater = LayoutInflater.from(parent.context)
val layout = inflater.inflate(R.layout.layout_warning, parent, false)
return WarningMessageVH(layout)
}
override fun onBindViewHolder(holder: WarningMessageVH, position: Int) {
holder.bind(currentList[position])
}
object DiffUtilCallback : DiffUtil.ItemCallback<WarningMessage>() {
override fun areContentsTheSame(oldItem: WarningMessage, newItem: WarningMessage) = oldItem == newItem
override fun areItemsTheSame(oldItem: WarningMessage, newItem: WarningMessage) = oldItem == newItem
}
}
class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(warning: WarningMessage) {
setBgColor(warning.priority)
setText(warning)
setupOkButton(warning)
}
private fun setText(warning: WarningMessage) {
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)
}
private fun setBgColor(priority: WarningMessage.Priority) {
val color = when (priority) {
WarningMessage.Priority.Info -> R.color.warning_info
WarningMessage.Priority.Warning -> R.color.warning_warning
WarningMessage.Priority.Critical -> R.color.warning_critical
}
view.card_view.setCardBackgroundColor(view.context.resources.getColor(color))
}
private fun setupOkButton(warning: WarningMessage) {
view.btn_got_it.show(warning.type == WarningMessage.Type.Temporary)
view.btn_got_it.setOnClickListener {
store.dispatch(GlobalAction.HideWarningMessage(warning))
}
}
}
class SpacesItemDecoration(private val spacePx: Int) : ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
outRect.left = spacePx
outRect.right = spacePx
outRect.top = spacePx / 2
outRect.top = spacePx / 2
}
}

View file

@ -1,30 +0,0 @@
package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WarningType
import com.tangem.tap.store
import com.tangem.wallet.R
class WarningDialog(context: Context) : AlertDialog(context) {
private val dialog: AlertDialog = Builder(context)
.setTitle(context.getString(R.string.common_warning))
.setPositiveButton(context.getString(R.string.common_ok)) { _, _ ->
dismiss()
}.setOnDismissListener {
store.dispatch(WalletAction.SaveCardId)
store.dispatch(WalletAction.HideDialog)
}
.create()
fun show(warningType: WarningType) {
val messageRes = when (warningType) {
WarningType.CardSignedHashesBefore -> R.string.alert_card_signed_transactions
WarningType.DevCard -> R.string.alert_developer_card
}
dialog.setMessage(dialog.context.getString(messageRes))
dialog.show()
}
}

View file

@ -74,6 +74,13 @@
android:layout_marginBottom="24dp"
tools:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_warning_messages"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:visibility="gone" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/llBottomButtonContainer"
android:layout_width="match_parent"
@ -92,14 +99,14 @@
<FrameLayout
android:id="@+id/flSendButtonContainer"
android:layout_width="0dp"
android:paddingBottom="33dp"
android:clipToPadding="false"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="8dp">
android:clipToPadding="false"
android:paddingBottom="33dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSend"

View file

@ -65,35 +65,47 @@
android:id="@+id/iv_twin_card"
android:layout_width="100dp"
android:layout_height="30dp"
android:src="@drawable/shape_chip"
android:elevation="3dp"
android:layout_marginStart="25dp"
android:elevation="3dp"
android:src="@drawable/shape_chip"
app:layout_constraintBottom_toBottomOf="@id/iv_card"
app:layout_constraintStart_toStartOf="@id/iv_card"
app:layout_constraintTop_toBottomOf="@id/iv_card"
app:layout_constraintBottom_toBottomOf="@id/iv_card"/>
app:layout_constraintTop_toBottomOf="@id/iv_card" />
<TextView
android:id="@+id/tv_twin_card_number"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="@id/iv_twin_card"
app:layout_constraintEnd_toEndOf="@id/iv_twin_card"
app:layout_constraintTop_toTopOf="@id/iv_twin_card"
app:layout_constraintBottom_toBottomOf="@id/iv_twin_card"
tools:text="Card 2 of 2"
android:elevation="3dp"
android:gravity="center"
android:textColor="@color/buttonGray"
android:textSize="14sp"
android:textStyle="bold"
android:textSize="14sp"/>
app:layout_constraintBottom_toBottomOf="@id/iv_twin_card"
app:layout_constraintEnd_toEndOf="@id/iv_twin_card"
app:layout_constraintStart_toStartOf="@id/iv_twin_card"
app:layout_constraintTop_toTopOf="@id/iv_twin_card"
tools:text="Card 2 of 2" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
app:barrierDirection="bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="iv_card,iv_twin_card"/>
app:barrierDirection="bottom"
app:constraint_referenced_ids="iv_card,iv_twin_card" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_warning_messages"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:minHeight="0dp"
android:nestedScrollingEnabled="false"
android:overScrollMode="never"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_pending_transaction"
@ -102,7 +114,9 @@
android:layout_marginTop="12dp"
android:nestedScrollingEnabled="false"
android:overScrollMode="never"
app:layout_constraintTop_toBottomOf="@id/barrier" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" />
<include
android:id="@+id/l_card_balance"
@ -124,15 +138,15 @@
layout="@layout/layout_wallet_long_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone"/>
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" />
<include
android:id="@+id/l_buttons_short"
layout="@layout/layout_wallet_short_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"/>
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"
app:cardBackgroundColor="@color/accent"
app:cardCornerRadius="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="Test title"
android:textColor="@android:color/white"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:text="Test text messaging, or texting, is the act of composing and sending electronic messages, typically consisting of alphabetic and numeric characters, between"
android:textColor="@android:color/white"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title"
app:layout_constraintVertical_bias="0.0"
app:lineHeight="18dp" />
<Button
android:id="@+id/btn_got_it"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/how_to_got_it_button"
android:textAllCaps="false"
android:textColor="@android:color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_message" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View file

@ -95,7 +95,8 @@
<string name="details_manage_security_access_code">Zugangscode</string>
<string name="details_manage_security_access_code_description">Sie müssen den richtigen Zugangscode eingeben, bevor Sie die Karte scannen.</string>
<string name="alert_old_device_this_card">Sie können NFC-Probleme mit einigen iPhone 7/7+ während der Extraktion haben</string>
<string name="alert_card_signed_transactions">Warnung: Diese Karte wurde früher bereits aufgeladen und Transaktionen wurden damit signiert. Ziehen Sie eine sofortige Auszahlung aller Beträge in Betracht, wenn Sie diese Karte von einer nicht vertrauenswürdigen Quelle erhalten haben.</string>
<string name="alert_card_signed_transactions">Diese Karte wurde früher bereits aufgeladen und Transaktionen wurden damit signiert. Ziehen Sie eine sofortige Auszahlung aller Beträge in Betracht, wenn Sie diese Karte von einer nicht vertrauenswürdigen Quelle erhalten haben.</string>
<string name="alert_title">Warnung</string>
<string name="alert_unsupported_card">Diese Karte ist für die Zusammenarbeit mit Tangem Tap nicht geeignet</string>
<string name="alert_developer_card">Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel</string>

View file

@ -96,7 +96,8 @@
<string name="details_manage_security_access_code">Code daccès </string>
<string name="details_manage_security_access_code_description">Vous devrez entrer le mot de passe correct avant de scanner la carte </string>
<string name="alert_old_device_this_card">Vous pouvez rencontrer des problèmes NFC avec certains iPhone 7/7 + lors de lextraction </string>
<string name="alert_card_signed_transactions">Attention: cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable.</string>
<string name="alert_card_signed_transactions">Cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable.</string>
<string name="alert_title">Attention</string>
<string name="alert_unsupported_card">Cette carte n\'est pas conçue pour fonctionner avec Tangem Tap </string>
<string name="alert_developer_card">La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement </string>
<string name="alert_old_card">Les cartes Tangem émises avant septembre 2019 ne peuvent actuellement pas être extraites à l\'aide de l\'iPhone. Nous travaillons activement avec Apple pour rendre cela possible dans les futures versions d\'iOS. </string>

View file

@ -93,7 +93,8 @@
<string name="details_manage_security_access_code">Codice di accesso</string>
<string name="details_manage_security_access_code_description">Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto</string>
<string name="alert_old_device_this_card">Potresti riscontrare problemi con l\'NFC su alcuni iPhone 7/7 + durante la rimozione</string>
<string name="alert_card_signed_transactions">Attenzione: questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile.</string>
<string name="alert_card_signed_transactions">Questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile.</string>
<string name="alert_title">Attenzione</string>
<string name="alert_unsupported_card">Questa carta non è progettata per funzionare con Tangem Tap</string>
<string name="alert_developer_card">La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento</string>
<string name="alert_old_card">Le schede emesse prima di settembre 2019 non possono attualmente essere utilizzate con un iPhone. Stiamo lavorando a stretto contatto con Apple per renderle disponibili nelle future versioni di iOS. </string>

View file

@ -43,5 +43,8 @@
<color name="blue3">#CBE4FF</color>
<color name="blue_pale">#E0E6FA</color>
<color name="warning_info">#1C1C1E</color>
<color name="warning_warning">#FFB71B</color>
<color name="warning_critical">#CA0F03</color>
</resources>

View file

@ -107,7 +107,8 @@
<string name="details_manage_security_access_code_description">You will have to submit the correct access code before scanning the card</string>
<string name="alert_old_device_this_card">You may experience NFC problems with some iPhone 7/7+ during the extraction</string>
<string name="alert_card_signed_transactions">Warning: This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source.</string>
<string name="alert_card_signed_transactions">This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source.</string>
<string name="alert_title">Warning</string>
<string name="alert_unsupported_card">This card it is not designed to work with Tangem Tap</string>
<string name="alert_developer_card">The card you scanned is a development card. Dont accept it as a payment</string>
<string name="alert_old_card">Tangem cards manufactured before September 2019 cannot currently be extracted with an iPhone. Were working hard with Apple to make it possible in future versions of iOS.</string>