diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 77b306e09d..6497a63edc 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -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.FeaturesLocalLoader -import com.tangem.tap.domain.config.FeaturesRemoteLoader +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 @@ -54,5 +56,7 @@ class TapApplication : Application() { 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)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index fc4f5bba9e..f47245676d 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index d7ec41960f..a0534fe8af 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -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 = { 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) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 7ab9334181..afbb6d5b7e 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -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( diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index f9e05cdeac..2ee123a725 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 36dd3c238c..8ffe1480c6 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt b/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt new file mode 100644 index 0000000000..e0d0a6a52c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.domain.configurable + +import com.tangem.wallet.BuildConfig + +/** +[REDACTED_AUTHOR] + */ +interface Loader { + 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}" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt similarity index 69% rename from app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt rename to app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt index e292e5d2c6..a47e8fec1c 100644 --- a/app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt @@ -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 FeaturesLocalLoader( private val context: Context, - private val moshi: Moshi -) : ConfigLoader { + private val moshi: Moshi, +) : Loader { - override fun loadConfig(onComplete: (ConfigModel) -> Unit) { + override fun load(onComplete: (ConfigModel) -> Unit) { val config = try { val featureAdapter: JsonAdapter = moshi.adapter(FeatureModel::class.java) val valuesAdapter: JsonAdapter = 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) { @@ -49,15 +39,15 @@ class FeaturesLocalLoader( } class FeaturesRemoteLoader( - private val moshi: Moshi -) : ConfigLoader { + private val moshi: Moshi, +) : Loader { - 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 FeaturesRemoteLoader( onComplete(emptyConfig) } }.addOnFailureListener { - FirebaseAnalyticsHandler.logException("remote_config_error", it) + FirebaseAnalyticsHandler.logException("remote_config_error.features", it) onComplete(emptyConfig) } } diff --git a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt similarity index 92% rename from app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt rename to app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index c7acd653c7..67025b175d 100644 --- a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -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, + private val remoteLoader: Loader ) { 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() } diff --git a/app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt similarity index 91% rename from app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt rename to app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index 4724f8b2c3..32ce46da8b 100644 --- a/app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.domain.config +package com.tangem.tap.domain.configurable.config /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningLoader.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningLoader.kt new file mode 100644 index 0000000000..c95fc39a24 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningLoader.kt @@ -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> { + + override fun load(onComplete: (List) -> Unit) { + val emptyConfig = listOf() + 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> = 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt new file mode 100644 index 0000000000..0a8f66677e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt @@ -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, + private val blockchains: List?, + val titleResId: Int? = null, + val messageResId: Int? = null, + val origin: Origin = Origin.Remote, +) { + val blockchainList: List? 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 + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..4855b20499 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt @@ -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 = 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 { + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/remoteWarning/RemoteWarning.kt b/app/src/main/java/com/tangem/tap/domain/remoteWarning/RemoteWarning.kt deleted file mode 100644 index 58b0e6b2f3..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/remoteWarning/RemoteWarning.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.tangem.tap.domain.remoteWarning - -import com.tangem.blockchain.common.Blockchain - -/** -[REDACTED_AUTHOR] - */ -data class RemoteWarning( - val title: String, - val message: String, - val type: Warning.Type, - val priority: Warning.Priority, - val location: List, - val blockchains: List, -) { -} - - - -sealed class Warning { - enum class Priority(val priority: String) { - Info("info"), - Warning("warning"), - Critical("critical") - } - enum class Type(val type: String) { - Permanent("permanent"), // нельзя скрыть - Temporary("temporary") // можно скрыть (кнопка ОК) - } - - enum class Location(val location: String) { - MainScreen("main"), - SendScreen("send") - } -} - diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index bcb227de3b..165c811904 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -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) : SendAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index 16da618931..0d816d6d62 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -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 } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index b85eb86bdc..c4e57d708a 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -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 = listOf(), val sendButtonState: SendButtonState = SendButtonState.DISABLED, val dialog: SendAction.Dialog? = null ) : SendScreenState { diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 874e744207..ca47b5613e 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -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 -> diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 8c0c8fdf23..20d3988515 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -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) { 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 0656a9583f..5f11e932bf 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 @@ -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) : WalletAction() object SaveCardId : WalletAction() object UpdateWallet : WalletAction() { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt index 2f2c041924..a06b52a6b1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt index 533121bbe8..275ad57895 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt @@ -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 -> 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 ec4c134bab..1b541930c7 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 @@ -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 = 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?) : 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 } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 5098d05d83..749486e3fe 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -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 { - if (dialog == null) dialog = WarningDialog(requireContext()).apply { - this.show(walletDialog.type) - } - } null -> { dialog?.dismiss() dialog = null diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WarningMessagesAdapter.kt new file mode 100644 index 0000000000..e7a2a559e3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WarningMessagesAdapter.kt @@ -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(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() { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt deleted file mode 100644 index 6f70a4f60d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml index 86ec08548b..78131a2e15 100644 --- a/app/src/main/res/layout/fragment_send.xml +++ b/app/src/main/res/layout/fragment_send.xml @@ -74,6 +74,13 @@ android:layout_marginBottom="24dp" tools:visibility="gone" /> + + + android:clipToPadding="false" + android:paddingBottom="33dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/guideline" + app:layout_constraintTop_toTopOf="parent"> + app:layout_constraintTop_toBottomOf="@id/iv_card" /> + 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" /> + app:barrierDirection="bottom" + app:constraint_referenced_ids="iv_card,iv_twin_card" /> + + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" /> + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" /> + app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/app/src/main/res/layout/layout_warning.xml b/app/src/main/res/layout/layout_warning.xml new file mode 100644 index 0000000000..9c53f7fed4 --- /dev/null +++ b/app/src/main/res/layout/layout_warning.xml @@ -0,0 +1,62 @@ + + + + + + + + + +