From a345df3c8437382a864336a28e8ecd2fb8a3ba46 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Mar 2021 15:46:04 +0300 Subject: [PATCH 1/6] Updated on 2026-08-14 --- app/build.gradle | 2 + .../java/com/tangem/tap/TapApplication.kt | 17 +- .../tap/common/redux/global/GlobalAction.kt | 10 +- .../common/redux/global/GlobalMidlleware.kt | 10 +- .../tap/common/redux/global/GlobalReducer.kt | 3 + .../tap/common/redux/global/GlobalState.kt | 2 + .../com/tangem/tap/domain/TapWalletManager.kt | 52 +++-- .../warningMessage/WarningMessage.kt | 4 +- .../warningMessage/WarningMessagesManager.kt | 17 +- .../features/details/ui/DetailsFragment.kt | 5 + .../tap/features/feedback/FeedbackManager.kt | 210 ++++++++++++++++++ .../features/send/redux/SendScreenAction.kt | 4 +- .../send/redux/middlewares/SendMiddleware.kt | 36 ++- .../send/redux/reducers/SendScreenReducer.kt | 3 +- .../ui/dialogs/SendTransactionFailsDialog.kt | 30 +++ .../send/ui/dialogs/TezosWarningDialog.kt | 4 +- .../stateSubscribers/SendStateSubscriber.kt | 9 +- .../tap/features/wallet/redux/WalletAction.kt | 25 ++- .../features/wallet/redux/WalletMiddleware.kt | 28 ++- .../features/wallet/redux/WalletReducer.kt | 16 +- .../tap/features/wallet/redux/WalletState.kt | 4 +- .../tap/features/wallet/ui/WalletFragment.kt | 8 +- .../wallet/ui/WarningMessagesAdapter.kt | 51 ++++- .../wallet/ui/dialogs/ScanFailsDialog.kt | 30 +++ .../tap/persistence/PreferencesStorage.kt | 40 +++- app/src/main/res/layout/fragment_details.xml | 15 +- app/src/main/res/layout/layout_warning.xml | 52 ++++- .../main/res/values/strings_untranslated.xml | 20 +- 28 files changed, 647 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt create mode 100644 app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt diff --git a/app/build.gradle b/app/build.gradle index 549774d931..6135c65a84 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -70,6 +70,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'com.google.android.material:material:1.2.1' + implementation 'com.google.android.play:core:1.9.1' + implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' implementation 'com.tangem:blockchain:1.141.0' diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 6497a63edc..73538b649c 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -4,6 +4,7 @@ import android.app.Application import com.google.firebase.ktx.Firebase import com.google.firebase.remoteconfig.ktx.remoteConfig import com.google.firebase.remoteconfig.ktx.remoteConfigSettings +import com.tangem.Log import com.tangem.tap.common.images.PicassoHelper import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.appReducer @@ -13,6 +14,9 @@ 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.features.feedback.AdditionalEmailInfo +import com.tangem.tap.features.feedback.FeedbackManager +import com.tangem.tap.features.feedback.TangemLogCollector import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.network.createMoshi import com.tangem.tap.persistence.PreferencesStorage @@ -46,10 +50,10 @@ class TapApplication : Application() { preferencesStorage = PreferencesStorage(this) PicassoHelper.initPicassoWithCaching(this) + initFeedbackManager() loadConfigs() } - private fun loadConfigs() { val moshi = createMoshi() val localLoader = FeaturesLocalLoader(this, moshi) @@ -59,4 +63,15 @@ class TapApplication : Application() { val warningsManager = WarningMessagesManager(RemoteWarningLoader(moshi)) warningsManager.load { store.dispatch(GlobalAction.SetWarningManager(warningsManager)) } } + + private fun initFeedbackManager() { + val infoHolder = AdditionalEmailInfo() + infoHolder.updateAppVersion(this) + + val logWriter = TangemLogCollector() + Log.addLogger(logWriter) + + val feedbackManager = FeedbackManager(infoHolder, this, logWriter) + store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager)) + } } \ 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 f47245676d..2f11ea5a0c 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 @@ -5,6 +5,8 @@ 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 com.tangem.tap.features.feedback.EmailData +import com.tangem.tap.features.feedback.FeedbackManager import org.rekotlin.Action import java.math.BigDecimal @@ -19,8 +21,12 @@ sealed class GlobalAction : Action { data class Success(val appCurrency: FiatCurrencyName) : GlobalAction() } 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() + + data class SetConfigManager(val configManager: ConfigManager) : GlobalAction() + data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction() + data class SetFeedbackManager(val feedbackManager: FeedbackManager): GlobalAction() + + data class SendFeedback(val emailData: EmailData): 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 a0534fe8af..421b48f0f6 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 @@ -26,11 +26,17 @@ val globalMiddleware: Middleware = { dispatch, appState -> store.dispatch(WalletAction.SaveCardId) } - store.dispatch(WalletAction.SetWarnings(it.getWarnings(WarningMessage.Location.MainScreen))) - store.dispatch(SendAction.SetWarnings(it.getWarnings(WarningMessage.Location.SendScreen))) + store.dispatch(WalletAction.Warnings.SetWarnings( + it.getWarnings(WarningMessage.Location.MainScreen))) + store.dispatch(SendAction.SetWarnings( + it.getWarnings(WarningMessage.Location.SendScreen))) } } } + + is GlobalAction.SendFeedback -> { + store.state.globalState.feedbackManager?.send(action.emailData) + } } 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 afbb6d5b7e..1ca2642b58 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 @@ -57,6 +57,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState { globalState } } + is GlobalAction.SetFeedbackManager -> { + globalState.copy(feedbackManager = action.feedbackManager) + } else -> globalState } } \ No newline at end of file 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 2ee123a725..dc6d6e64ff 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 @@ -7,6 +7,7 @@ import com.tangem.tap.domain.TapWalletManager 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.features.feedback.FeedbackManager import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import org.rekotlin.StateType import java.math.BigDecimal @@ -20,6 +21,7 @@ data class GlobalState( val conversionRates: ConversionRates = ConversionRates(emptyMap()), val configManager: ConfigManager? = null, val warningManager: WarningMessagesManager? = null, + val feedbackManager: FeedbackManager? = 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 8ffe1480c6..3b96280599 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -84,23 +84,10 @@ 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) - configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled) - configManager?.turnOff(ConfigManager.isTopUpEnabled) - } else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin - || data.card.cardData?.blockchainName == Blockchain.Bitcoin.id){ - configManager?.turnOff(ConfigManager.isWalletPayIdEnabled) - configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled) - configManager?.resetToDefault(ConfigManager.isTopUpEnabled) - } else { - configManager?.resetToDefault(ConfigManager.isWalletPayIdEnabled) - configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled) - configManager?.resetToDefault(ConfigManager.isTopUpEnabled) - } + updateConfigManager(data) + updateFeedbackManager(data) + withContext(Dispatchers.Main) { store.dispatch(WalletAction.ResetState) store.dispatch(GlobalAction.SaveScanNoteResponse(data)) @@ -117,9 +104,40 @@ class TapWalletManager { } } + private fun updateConfigManager(data: ScanNoteResponse) { + val configManager = store.state.globalState.configManager + if (TapWorkarounds.isStart2Coin) { + configManager?.turnOff(ConfigManager.isWalletPayIdEnabled) + configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled) + configManager?.turnOff(ConfigManager.isTopUpEnabled) + } else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin + || data.card.cardData?.blockchainName == Blockchain.Bitcoin.id) { + configManager?.turnOff(ConfigManager.isWalletPayIdEnabled) + configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled) + configManager?.resetToDefault(ConfigManager.isTopUpEnabled) + } else { + configManager?.resetToDefault(ConfigManager.isWalletPayIdEnabled) + configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled) + configManager?.resetToDefault(ConfigManager.isTopUpEnabled) + } + } + + private fun updateFeedbackManager(data: ScanNoteResponse) { + val card = data.card + val wallet = data.walletManager?.wallet ?: return + val infoHolder = store.state.globalState.feedbackManager?.infoHolder ?: return + + infoHolder.cardId = card.cardId + infoHolder.cardFirmwareVersion = card.firmwareVersion.version + infoHolder.signedHashesCount = card.walletSignedHashes?.toString() ?: "0" + infoHolder.sourceAddress = wallet.address + infoHolder.explorerLink = wallet.getExploreUrl(wallet.address) + infoHolder.blockchain = wallet.blockchain + } + suspend fun loadData(data: ScanNoteResponse) { withContext(Dispatchers.Main) { - store.dispatch(WalletAction.CheckIfWarningNeeded) + store.dispatch(WalletAction.Warnings.CheckIfNeeded) val artworkId = data.verifyResponse?.artworkInfo?.id if (data.walletManager != null) { if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt index 0a8f66677e..ab94c0d563 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt @@ -40,7 +40,9 @@ data class WarningMessage( Permanent, // нельзя скрыть @Json(name = "temporary") - Temporary // можно скрыть (кнопка ОК) + Temporary, // можно скрыть (кнопка ОК) + + AppRating } enum class Location { diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt index 4855b20499..1ee151033f 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt @@ -51,7 +51,8 @@ class WarningMessagesManager( val foundWarning = findWarning(warning) return when { foundWarning == null -> false - foundWarning.type == WarningMessage.Type.Temporary -> { + foundWarning.type == WarningMessage.Type.Temporary + || foundWarning.type == WarningMessage.Type.AppRating -> { if (foundWarning.isHidden) { false } else { @@ -101,7 +102,19 @@ class WarningMessagesManager( WarningMessage.Origin.Local ) - fun isAlreadySignedHashesWarning(warning: WarningMessage):Boolean { + fun appRatingWarning(): WarningMessage = WarningMessage( + "", + "", + WarningMessage.Type.AppRating, + WarningMessage.Priority.Info, + listOf(WarningMessage.Location.MainScreen), + null, + R.string.warning_rate_app_title, + R.string.warning_rate_app_message, + WarningMessage.Origin.Local + ) + + fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean { return warning.messageResId == R.string.alert_card_signed_transactions } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt index 262482b360..8c17a4707d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.twins.getTwinCardIdForUser import com.tangem.tap.domain.twins.isTwinCard @@ -14,6 +15,7 @@ import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.details.redux.twins.CreateTwinWallet +import com.tangem.tap.features.feedback.FeedbackEmail import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_details.* @@ -107,6 +109,9 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber() + + override fun e(logTag: String, message: String) {} + override fun i(logTag: String, message: String) {} + override fun v(logTag: String, message: String) {} + + override fun write(message: LogMessage) { + logs.add(message.message) + } + + fun getLogs(): List = logs.toList() + + fun clearLogs() { + logs.clear() + } +} + +class AdditionalEmailInfo { + var cardId: String = "" + var cardFirmwareVersion: String = "" + var blockchain: Blockchain = Blockchain.Unknown + + var phoneModel: String = Build.MODEL + var osVersion: String = Build.VERSION.SDK_INT.toString() + var appVersion: String = "" + + var token: String = "" + var sourceAddress: String = "" + var destinationAddress: String = "" + var amount: String = "" + var fee: String = "" + + // var transactionHex: String = "" + var signedHashesCount: String = "" + var explorerLink: String = "" +// var outputsCount: String = "" + + fun updateAppVersion(context: Context) { + try { + val pInfo = context.packageManager.getPackageInfo(context.packageName, 0) + appVersion = pInfo.versionName + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + } + } +} + +interface EmailData { + val subject: String + val mainMessage: String + fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String + + fun joinTogether(infoHolder: AdditionalEmailInfo): String { + return "$mainMessage\n\n\n\n\n" + + "Following information is optional. You can erase it if you don’t want to share it.\n" + + createOptionalMessage(infoHolder) + } +} + +class RateCanBeBetterEmail : EmailData { + override val subject: String = "My suggestions" + override val mainMessage: String = "Tell us what functions you are missing, and we will try to help you." + + override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { + return StringBuilder().apply { + appendKeyValue("Card ID", infoHolder.cardId) + appendKeyValue("Blockchain", infoHolder.blockchain.fullName) + appendKeyValue("Phone model", infoHolder.phoneModel) + appendKeyValue("OS version", infoHolder.osVersion) + appendKeyValue("App version", infoHolder.appVersion) + }.toString() + } +} + +class ScanFailsEmail : EmailData { + override val subject: String = "Can’t scan a card" + override val mainMessage: String = "Please tell us what card do you have?" + override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { + return StringBuilder().apply { + appendKeyValue("Phone model", infoHolder.phoneModel) + appendKeyValue("OS version", infoHolder.osVersion) + appendKeyValue("App version", infoHolder.appVersion) + }.toString() + } +} + +class SendTransactionFailedEmail(private val error: String) : EmailData { + override val subject: String = "Can’t send a transaction" + override val mainMessage: String = "Please tell us more about your issue. Every small detail can help." + override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { + return StringBuilder().apply { + appendKeyValue("Error", error) + appendKeyValue("Card ID", infoHolder.cardId) + appendKeyValue("Blockchain", infoHolder.blockchain.fullName) + appendKeyValue("Token", infoHolder.token) + appendKeyValue("Source address", infoHolder.sourceAddress) + appendKeyValue("Destination address", infoHolder.destinationAddress) + appendKeyValue("Amount", infoHolder.amount) + appendKeyValue("Fee", infoHolder.fee) + appendKeyValue("Phone model", infoHolder.phoneModel) + appendKeyValue("OS version", infoHolder.osVersion) + appendKeyValue("App version", infoHolder.appVersion) + appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) +// appendKeyValue("Transaction HEX", infoHolder.transactionHex) + }.toString() + } +} + +class FeedbackEmail : EmailData { + override val subject: String = "Tangem Tap feedback" + override val mainMessage: String = "Hi Tangem," + override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { + return StringBuilder().apply { + appendKeyValue("Card ID", infoHolder.cardId) + appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) + appendKeyValue("Signed hashes", infoHolder.signedHashesCount) + appendKeyValue("Blockchain", infoHolder.blockchain.fullName) + appendKeyValue("Wallet address", infoHolder.sourceAddress) + appendKeyValue("Explorer link", infoHolder.explorerLink) +// appendKeyValue("Outputs count", infoHolder.outputsCount) + appendKeyValue("Phone model", infoHolder.phoneModel) + appendKeyValue("OS version", infoHolder.osVersion) + }.toString() + } +} + +fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder { + return this.append("$key: $value\n") +} \ No newline at end of file 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 165c811904..6380e0f13b 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 @@ -139,12 +139,12 @@ sealed class SendAction : SendScreenAction { data class SendError(override val error: TapError) : SendAction(), ErrorAction sealed class Dialog : SendAction() { - data class ShowTezosWarningDialog( + data class TezosWarningDialog( val reduceCallback: () -> Unit, val sendAllCallback: () -> Unit, val reduceAmount: BigDecimal, ) : Dialog() - + data class SendTransactionFails(val errorMessage: String): Dialog() object Hide : Dialog() } data class SetWarnings(val warningList: List) : SendAction() diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 2af762a25c..faad006fc5 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -21,6 +21,7 @@ import com.tangem.tap.features.send.redux.FeeAction.RequestFee import com.tangem.tap.features.send.redux.states.SendButtonState import com.tangem.tap.features.send.redux.states.TransactionExtrasState import com.tangem.tap.scope +import com.tangem.tap.store import com.tangem.tap.tangemSdk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -50,7 +51,7 @@ val sendMiddleware: Middleware = { dispatch, appState -> } private fun verifyAndSendTransaction( - action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit + action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit, ) { val sendState = appState?.sendState ?: return val walletManager = appState.globalState.scanNoteResponse?.walletManager ?: return @@ -66,7 +67,7 @@ private fun verifyAndSendTransaction( when { hadTezosError -> { val reduceAmount = walletManager.wallet.blockchain.minimalAmount() - dispatch(SendAction.Dialog.ShowTezosWarningDialog(reduceCallback = { + dispatch(SendAction.Dialog.TezosWarningDialog(reduceCallback = { dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false)) dispatch(AmountActionUi.CheckAmountToSend) }, sendAllCallback = { @@ -92,7 +93,7 @@ private fun sendTransaction( destinationAddress: String, transactionExtras: TransactionExtrasState, card: Card, - dispatch: (Action) -> Unit + dispatch: (Action) -> Unit, ) { dispatch(SendAction.ChangeSendButtonState(SendButtonState.PROGRESS)) var txData = walletManager.createTransaction(amountToSend, feeAmount, destinationAddress) @@ -136,19 +137,23 @@ private fun sendTransaction( when { message == null -> { dispatch(SendAction.SendError(TapError.UnknownError)) + updateFeedbackManager(walletManager, amountToSend, feeAmount, destinationAddress, card) + dispatch(SendAction.Dialog.SendTransactionFails("unknown error")) } message.contains("50002") -> { // user was cancelled the operation by closing the Sdk bottom sheet } // make it easier latter by handling an appropriate enumError or, like on iOS, // accept a string identifier of the error message - message.contains("Target account is not created. To create account send 1+ XLM.")-> { + message.contains("Target account is not created. To create account send 1+ XLM.") -> { dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated)) } else -> { Timber.e(throwable) FirebaseCrashlytics.getInstance().recordException(throwable) dispatch(SendAction.SendError(TapError.CustomError(message))) + updateFeedbackManager(walletManager, amountToSend, feeAmount, destinationAddress, card) + dispatch(SendAction.Dialog.SendTransactionFails(message)) } } } @@ -160,6 +165,29 @@ private fun sendTransaction( } } +private fun updateFeedbackManager( + walletManager: WalletManager, + amountToSend: Amount, + feeAmount: Amount, + destinationAddress: String, + card: Card, +) { + val infoHolder = store.state.globalState.feedbackManager?.infoHolder ?: return + val amountState = store.state.sendState.amountState + + infoHolder.cardId = card.cardId + infoHolder.blockchain = walletManager.wallet.blockchain + infoHolder.sourceAddress = walletManager.wallet.address + infoHolder.destinationAddress = destinationAddress + infoHolder.amount = amountToSend.value?.stripZeroPlainString() ?: "0" + infoHolder.fee = feeAmount.value?.stripZeroPlainString() ?: "0" + infoHolder.cardFirmwareVersion = card.firmwareVersion.version + if (amountState.typeOfAmount is AmountType.Token) { + infoHolder.token = amountState.amountToExtract?.currencySymbol ?: "" + } +// infoHolder.transactionHex = "" +} + fun extractErrorsForAmountField(errors: EnumSet): EnumSet { val showIntoAmountField = EnumSet.noneOf(TransactionError::class.java) errors.forEach { 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 0d816d6d62..f042f8c1ae 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 @@ -42,7 +42,8 @@ private class SendReducer : SendInternalReducer { override fun handle(action: SendScreenAction, sendState: SendState): SendState { val result = when (action) { is SendAction.ChangeSendButtonState -> sendState.copy(sendButtonState = action.state) - is SendAction.Dialog.ShowTezosWarningDialog -> sendState.copy(dialog = action) + is SendAction.Dialog.TezosWarningDialog -> sendState.copy(dialog = action) + is SendAction.Dialog.SendTransactionFails -> 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/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt new file mode 100644 index 0000000000..cb65943200 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.features.send.ui.dialogs + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.features.feedback.SendTransactionFailedEmail +import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +class SendTransactionFailsDialog { + + companion object { + fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.alert_failed_to_send_transaction_title) + setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, dialog.errorMessage)) + setNeutralButton(R.string.alert_button_send_feedback) { _, _ -> + store.dispatch(GlobalAction.SendFeedback(SendTransactionFailedEmail(dialog.errorMessage))) + } + setPositiveButton(R.string.common_no) { _, _ -> } + setOnDismissListener { store.dispatch(WalletAction.HideDialog) } + }.create() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt index 45b092ea8c..afb6276cb7 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt @@ -9,10 +9,10 @@ import com.tangem.wallet.R class TezosWarningDialog(context: Context) : AlertDialog(context) { companion object { - fun create(context: Context, showDialogData: SendAction.Dialog.ShowTezosWarningDialog): AlertDialog { + fun create(context: Context, showDialogData: SendAction.Dialog.TezosWarningDialog): AlertDialog { val reduceAmount = showDialogData.reduceAmount.toPlainString() return Builder(context).apply { - setTitle(context.getString(R.string.common_warning)) + setTitle(R.string.common_warning) setMessage(context.getString(R.string.xtz_withdrawal_message_warning, reduceAmount)) setNegativeButton(R.string.xtz_withdrawal_message_ignore) { _, _ -> showDialogData.sendAllCallback() 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 20d3988515..01eefabfd1 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 @@ -21,6 +21,7 @@ import com.tangem.tap.features.send.redux.reducers.ReceiptReducer 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.SendTransactionFailsDialog import com.tangem.tap.features.send.ui.dialogs.TezosWarningDialog import com.tangem.tap.features.wallet.ui.WarningMessagesAdapter import com.tangem.tap.store @@ -97,12 +98,18 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber val sendFragment = (fg as? SendFragment) ?: return when (state.dialog) { - is SendAction.Dialog.ShowTezosWarningDialog -> { + is SendAction.Dialog.TezosWarningDialog -> { if (dialog == null) { dialog = TezosWarningDialog.create(fg.requireContext(), state.dialog) dialog?.show() } } + is SendAction.Dialog.SendTransactionFails -> { + if (dialog == null) { + dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog) + dialog?.show() + } + } else -> { dialog?.dismiss() dialog = null 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 5f11e932bf..d7e4306e60 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 @@ -1,6 +1,7 @@ package com.tangem.tap.features.wallet.redux import android.content.Context +import com.tangem.TangemError import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.address.AddressType @@ -24,20 +25,28 @@ sealed class WalletAction : Action { } data class LoadWallet( - val wallet: Wallet, val artworkId: String?, val allowTopUp: Boolean + val wallet: Wallet, val artworkId: String?, val allowTopUp: Boolean, ) : WalletAction() { data class Success(val wallet: Wallet) : WalletAction() data class NoAccount(val amountToCreateAccount: String) : WalletAction() data class Failure(val errorMessage: String? = null) : WalletAction() } - object CheckIfWarningNeeded : WalletAction() object CheckHashesCountOnline : WalletAction() object NeedToCheckHashesCountOnline : WalletAction() object ConfirmHashesCount : WalletAction() - data class SetWarnings(val warningList: List) : WalletAction() object SaveCardId : WalletAction() + object Warnings : WalletAction() { + object CheckIfNeeded : WalletAction() + data class SetWarnings(val warningList: List) : WalletAction() + + object AppRating : WalletAction() { + object SetNeverToShow : WalletAction() + object RemindLater : WalletAction() + } + } + object UpdateWallet : WalletAction() { object ScheduleUpdatingWallet : WalletAction() data class Success(val wallet: Wallet) : WalletAction() @@ -63,6 +72,8 @@ sealed class WalletAction : Action { } object Scan : WalletAction() + class ScanCardFinished(val scanError: TangemError? = null) : WalletAction() + data class Send(val amount: Amount? = null) : WalletAction() { data class ChooseCurrency(val amounts: List?) : WalletAction() object Cancel : WalletAction() @@ -85,8 +96,12 @@ sealed class WalletAction : Action { } } - object ShowQrCode : WalletAction() + object ShowDialog : WalletAction() { + object QrCode : WalletAction() + object ScanFails : WalletAction() + } object HideDialog : WalletAction() + data class ExploreAddress(val context: Context) : WalletAction() object CreateWallet : WalletAction() object EmptyWallet : WalletAction() @@ -102,7 +117,7 @@ sealed class WalletAction : Action { object SetOnboardingShown : TwinsAction() data class SetTwinCard( val secondCardId: String, val number: TwinCardNumber, - val isCreatingTwinCardsAllowed: Boolean + val isCreatingTwinCardsAllowed: Boolean, ) : TwinsAction() } } \ No newline at end of file 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 a06b52a6b1..7c8c4d1438 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 @@ -4,6 +4,7 @@ import android.content.Intent import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent import androidx.core.content.ContextCompat +import com.tangem.TangemSdkError import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.SimpleResult import com.tangem.commands.common.card.Card @@ -136,6 +137,19 @@ class WalletMiddleware { store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding)) } } + store.dispatch(WalletAction.ScanCardFinished()) + } + is CompletionResult.Failure -> { + if (result.error !is TangemSdkError.UserCancelled) { + // Weird things... If you run the code below without coroutines, + // then rescanning will be impossible + scope.launch(Dispatchers.Main) { + store.dispatch(WalletAction.ScanCardFinished(result.error)) + if (store.state.walletState.scanCardFailsCounter >= 2) { + store.dispatch(WalletAction.ShowDialog.ScanFails) + } + } + } } } } @@ -173,7 +187,7 @@ class WalletMiddleware { store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) } } - is WalletAction.CheckIfWarningNeeded -> { + is WalletAction.Warnings.CheckIfNeeded -> { val globalState = store.state.globalState val validator = globalState.scanNoteResponse?.walletManager as? SignatureCountValidator globalState.scanNoteResponse?.card?.let { card -> @@ -184,7 +198,8 @@ class WalletMiddleware { } updateWarningMessages() } - + val readyToShow = preferencesStorage.appRatingLaunchObserver.isReadyToShow() + if (readyToShow) addWarningMessage(WarningMessagesManager.appRatingWarning(), true) } is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline() is WalletAction.SaveCardId -> { @@ -198,6 +213,12 @@ class WalletMiddleware { is WalletAction.TwinsAction.SetOnboardingShown -> { preferencesStorage.saveTwinsOnboardingShown() } + is WalletAction.Warnings.AppRating.RemindLater -> { + preferencesStorage.appRatingLaunchObserver.applyDelayedShowing() + } + is WalletAction.Warnings.AppRating.SetNeverToShow -> { + preferencesStorage.appRatingLaunchObserver.setNeverToShow() + } } next(action) } @@ -290,7 +311,8 @@ class WalletMiddleware { private fun updateWarningMessages() { val warningManager = store.state.globalState.warningManager ?: return - store.dispatch(WalletAction.SetWarnings(warningManager.getWarnings(WarningMessage.Location.MainScreen))) + store.dispatch(WalletAction.Warnings.SetWarnings( + warningManager.getWarnings(WarningMessage.Location.MainScreen))) } } 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 275ad57895..f458fa9433 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 @@ -176,7 +176,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl)) } - is WalletAction.ShowQrCode -> { + is WalletAction.ShowDialog.QrCode -> { newState = newState.copy( walletDialog = WalletDialog.QrDialog( newState.walletAddresses?.selectedAddress?.shareUrl?.toQrCode(), @@ -185,6 +185,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) ) } + is WalletAction.ShowDialog.ScanFails -> { + newState = newState.copy(walletDialog = WalletDialog.ScanFailsDialog) + } is WalletAction.HideDialog -> { newState = newState.copy(walletDialog = null) } @@ -215,7 +218,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) } is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null) - is WalletAction.SetWarnings -> newState = newState.copy(mainWarningsList = action.warningList) + is WalletAction.Warnings.SetWarnings -> newState = newState.copy(mainWarningsList = action.warningList) is WalletAction.NeedToCheckHashesCountOnline -> newState = newState.copy(hashesCountVerified = false) is WalletAction.ConfirmHashesCount -> @@ -254,6 +257,13 @@ private fun internalReduce(action: Action, state: AppState): WalletState { twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = false) ) } + is WalletAction.ScanCardFinished -> { + newState = if (action.scanError == null) { + newState.copy(scanCardFailsCounter = 0) + } else { + newState.copy(scanCardFailsCounter = newState.scanCardFailsCounter + 1) + } + } } return newState } @@ -288,7 +298,7 @@ private fun handleTopUpActions(action: WalletAction.TopUpAction, state: TopUpSta } private fun onWalletLoaded( - wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null + wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null, ): WalletState { val fiatCurrencySymbol = store.state.globalState.appCurrency val token = wallet.getFirstToken() 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 1b541930c7..1139758b62 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 @@ -28,7 +28,8 @@ data class WalletState( val mainButton: WalletMainButton = WalletMainButton.SendButton(false), val topUpState: TopUpState = TopUpState(), val twinCardsState: TwinCardsState? = null, - val mainWarningsList: List = mutableListOf() + val mainWarningsList: List = mutableListOf(), + val scanCardFailsCounter: Int = 0, ) : StateType { val showDetails: Boolean = currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard && @@ -52,6 +53,7 @@ sealed class WalletDialog { data class CreatePayIdDialog(val creatingPayIdState: CreatingPayIdState?) : WalletDialog() data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog() data class TwinsOnboardingFragment(val secondCardId: String): WalletDialog() + object ScanFailsDialog: WalletDialog() } enum class ProgressState { Loading, Done, Error } 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 749486e3fe..2fd33050f7 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 @@ -26,6 +26,7 @@ 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.ScanFailsDialog import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.card_balance.* @@ -208,7 +209,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber R.string.wallet_button_send @@ -301,6 +302,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + if (dialog == null) dialog = ScanFailsDialog.create(requireContext()).apply { + this.show() + } + } 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 index e7a2a559e3..aae70f9a67 100644 --- 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 @@ -8,10 +8,14 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ItemDecoration +import com.google.android.play.core.review.ReviewManagerFactory import com.tangem.tap.common.extensions.getString +import com.tangem.tap.common.extensions.hide 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.features.feedback.RateCanBeBetterEmail +import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.layout_warning.view.* @@ -40,7 +44,7 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) { fun bind(warning: WarningMessage) { setBgColor(warning.priority) setText(warning) - setupOkButton(warning) + setupControlButtons(warning) } private fun setText(warning: WarningMessage) { @@ -59,10 +63,47 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) { 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)) + 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 { + store.dispatch(GlobalAction.HideWarningMessage(warning)) + store.dispatch(WalletAction.Warnings.AppRating.RemindLater) + } + view.btn_can_be_better.setOnClickListener { + store.dispatch(GlobalAction.HideWarningMessage(warning)) + store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail())) + } + store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) + view.btn_really_cool.setOnClickListener { + val context = view.context + val reviewManager = ReviewManagerFactory.create(context) + val flow = reviewManager.requestReviewFlow() + flow.addOnCompleteListener { + if (it.isSuccessful) { +// val info = it.result +// Toast.makeText(context, "success", Toast.LENGTH_SHORT).show() + } else { +// Toast.makeText(context, "fail", Toast.LENGTH_SHORT).show() + } + }.addOnFailureListener { +// Toast.makeText(context, "failure", Toast.LENGTH_SHORT).show() + } + store.dispatch(GlobalAction.HideWarningMessage(warning)) + } + store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt new file mode 100644 index 0000000000..9a8ff9ace5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -0,0 +1,30 @@ +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.features.feedback.ScanFailsEmail +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +class ScanFailsDialog { + + companion object { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.common_warning)) + setMessage(R.string.alert_troubleshooting_scan_card_title) + setPositiveButton(R.string.alert_button_request_support) { _, _ -> + store.dispatch(GlobalAction.SendFeedback(ScanFailsEmail())) + } + setNegativeButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { store.dispatch(WalletAction.HideDialog) } + }.create() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 08346630f3..0c0179481e 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -15,12 +15,13 @@ import com.tangem.tap.network.coinmarketcap.FiatCurrency class PreferencesStorage(applicationContext: Application) { - private val preferences: SharedPreferences by lazy { - applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) - } + private val preferences: SharedPreferences = applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + + val appRatingLaunchObserver: AppRatingLaunchObserver init { incrementLaunchCounter() + appRatingLaunchObserver = AppRatingLaunchObserver(preferences, getCountOfLaunches()) } private val fiatCurrenciesAdapter: JsonAdapter> by lazy { @@ -97,4 +98,37 @@ class PreferencesStorage(applicationContext: Application) { private const val APP_LAUNCH_COUNT_KEY = "launchCount" } +} + +class AppRatingLaunchObserver( + private val preferences: SharedPreferences, + private val launchCounts: Int, +) { + private val K_SHOW_RATING_DIALOG = "show_rating_dialog_after_app_launch_count" + + private val deferShowing = 20 + private val firstShowing = 3 + + fun isReadyToShow(): Boolean { + if (launchCounts == firstShowing) return true + + val nextShowingAt = getCounterOfNextShowing() + return launchCounts >= nextShowingAt + } + + fun applyDelayedShowing() { + if (getCounterOfNextShowing() < launchCounts) updateNextShowing(launchCounts + deferShowing) + } + + fun setNeverToShow() { + updateNextShowing(999999999) + } + + private fun updateNextShowing(at: Int) { + preferences.edit().putInt(K_SHOW_RATING_DIALOG, at).apply() + } + + private fun getCounterOfNextShowing(): Int { + return preferences.getInt(K_SHOW_RATING_DIALOG, firstShowing) + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index 7a99598875..bea24a361d 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -170,6 +170,19 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_settings_title" /> + + + app:layout_constraintTop_toBottomOf="@id/tv_send_feedback" /> + + +