diff --git a/app/build.gradle b/app/build.gradle
index d2a3c6f2f5..bd5a7bc24f 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.143.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2b7b764bfc..ee1acb1fe8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -81,6 +81,17 @@
+
+
+
+
+
\ No newline at end of file
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/analytics/AnalyticsEvent.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
index 932897e204..4395bd0922 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
@@ -4,4 +4,8 @@ enum class AnalyticsEvent(val event: String) {
CARD_IS_SCANNED("card_is_scanned"),
TRANSACTION_IS_SENT("transaction_is_sent"),
READY_TO_SCAN("ready_to_scan"),
+ APP_RATING_DISPLAYED("rate_app_warning_displayed"),
+ APP_RATING_DISMISS("dismiss_rate_app_warning"),
+ APP_RATING_NEGATIVE("negative_rate_app_feedback"),
+ APP_RATING_POSITIVE("positive_rate_app_feedback"),
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
index 9dddeee4f2..1dc3586808 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
@@ -4,5 +4,5 @@ import com.tangem.commands.common.card.Card
interface AnalyticsHandler {
- fun triggerEvent(event: AnalyticsEvent, card: Card?)
+ fun triggerEvent(event: AnalyticsEvent, card: Card? = null)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
index 0d49728dd6..d1d3ce7a38 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
@@ -8,8 +8,7 @@ import com.tangem.commands.common.card.Card
object FirebaseAnalyticsHandler : AnalyticsHandler {
override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
- Firebase.analytics
- .logEvent(event.event, setCardData(card))
+ Firebase.analytics.logEvent(event.event, setCardData(card))
}
fun logException(name: String, throwable: Throwable) {
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 8578a60304..46159e7a65 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
@@ -8,6 +8,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
@@ -15,6 +16,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.*
@@ -115,6 +117,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 44a139c4d1..73b9cd9f5f 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..a451256f60 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
@@ -12,8 +13,10 @@ import com.tangem.commands.common.network.Result
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.getType
import com.tangem.common.extensions.toHexString
+import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.copyToClipboard
+import com.tangem.tap.common.extensions.isGreaterThan
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
@@ -41,6 +44,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Action
import org.rekotlin.Middleware
+import java.math.BigDecimal
class WalletMiddleware {
private val topUpMiddleware = TopUpMiddleware()
@@ -97,6 +101,7 @@ class WalletMiddleware {
is WalletAction.LoadWallet.Success -> {
store.dispatch(WalletAction.CheckHashesCountOnline)
if (!store.state.walletState.updatingWallet) setupWalletUpdate(action.wallet)
+ tryToShowAppRatingWarning(action.wallet)
}
is WalletAction.CreatePayId.CompleteCreatingPayId -> {
scope.launch {
@@ -136,6 +141,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 +191,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 +202,6 @@ class WalletMiddleware {
}
updateWarningMessages()
}
-
}
is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline()
is WalletAction.SaveCardId -> {
@@ -198,12 +215,31 @@ 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)
}
}
}
+ private fun tryToShowAppRatingWarning(wallet: Wallet) {
+ val nonZeroWalletsCount = wallet.amounts.filter {
+ it.value.value?.isGreaterThan(BigDecimal.ZERO) ?: false
+ }.size
+ if (nonZeroWalletsCount > 0) {
+ preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds()
+ }
+ if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) {
+ FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISPLAYED)
+ addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
+ }
+ }
+
private fun setupWalletUpdate(wallet: Wallet) {
if (!wallet.recentTransactions.toPendingTransactions(wallet.address).isNullOrEmpty()) {
store.dispatch(WalletAction.UpdateWallet.ScheduleUpdatingWallet)
@@ -290,7 +326,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 c3589d9a2b..238da5b0e6 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
@@ -27,6 +27,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.*
@@ -209,7 +210,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber R.string.wallet_button_send
@@ -302,6 +303,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..c2129bfb76 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,16 @@ 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.analytics.AnalyticsEvent
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
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 +46,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 +65,50 @@ 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 {
+ FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS)
+ store.dispatch(GlobalAction.HideWarningMessage(warning))
+ store.dispatch(WalletAction.Warnings.AppRating.RemindLater)
+ }
+ view.btn_can_be_better.setOnClickListener {
+ FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
+ store.dispatch(GlobalAction.HideWarningMessage(warning))
+ store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail()))
+ }
+ store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
+ view.btn_really_cool.setOnClickListener {
+ FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
+ 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..4ab011a469 100644
--- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
+++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
@@ -11,16 +11,18 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.network.coinmarketcap.FiatCurrency
+import java.util.*
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 +99,55 @@ 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_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount"
+ private val K_FUNDS_FOUND_DATE = "aFundsFoundDate"
+
+ private val deferShowing = 20
+ private val firstShowing = 3
+ private var fundsFoundDate: Calendar? = null
+
+ init {
+ val dateTimeMs = preferences.getLong(K_FUNDS_FOUND_DATE, -1)
+ if (dateTimeMs > 0) fundsFoundDate = Calendar.getInstance().apply { timeInMillis = dateTimeMs }
+ }
+
+ fun foundWalletWithFunds() {
+ if (fundsFoundDate != null) return
+
+ fundsFoundDate = Calendar.getInstance()
+ preferences.edit().putLong(K_FUNDS_FOUND_DATE, fundsFoundDate!!.timeInMillis).apply()
+ updateNextShowing(launchCounts + firstShowing)
+ }
+
+ fun isReadyToShow(): Boolean {
+ val fundsDate = fundsFoundDate ?: return false
+
+ val diff = Calendar.getInstance().timeInMillis - fundsDate.timeInMillis
+ val diffInDays = diff / (100 * 60 * 60 * 24)
+ if (diffInDays >= firstShowing) return true
+
+ return launchCounts >= getCounterOfNextShowing()
+ }
+
+ fun applyDelayedShowing() {
+ if (getCounterOfNextShowing() < launchCounts) updateNextShowing(launchCounts + deferShowing)
+ }
+
+ fun setNeverToShow() {
+ updateNextShowing(999999999)
+ }
+
+ private fun updateNextShowing(at: Int) {
+ preferences.edit().putInt(K_SHOW_RATING_AT_LAUNCH_COUNT, at).apply()
+ }
+
+ private fun getCounterOfNextShowing(): Int {
+ return preferences.getInt(K_SHOW_RATING_AT_LAUNCH_COUNT, 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 2533020f4a..a64e5c7120 100644
--- a/app/src/main/res/layout/fragment_details.xml
+++ b/app/src/main/res/layout/fragment_details.xml
@@ -185,6 +185,19 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_settings_title" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/tv_send_feedback" />
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml
index 8418b367b6..4cbf6a80b0 100644
--- a/app/src/main/res/values/strings_untranslated.xml
+++ b/app/src/main/res/values/strings_untranslated.xml
@@ -39,5 +39,23 @@ this wallet.
Invalid destination tag. It won\'t be added to the transaction
Invalid Memo ID. It won\'t be added to the transaction
-
+ App
+ Send feedback
+ Sent successfully
+ Thank you for your feedback
+ Your suggestions were sent
+ Thank your for your feedback. We will response as soon as possible
+ Failed to send email
+ Reason: %s
+ Can’t send a transaction
+ Reason: %s. Do you want to send feedback?
+ Are you having difficulty scanning your card?
+ Please try to tap the card exactly as shown in the animation or request support.
+ Try again
+ Request support
+ Send feedback
+ Really cool!
+ Can be better
+ One question
+ How do you like Tangem?
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 0000000000..1a33b59980
--- /dev/null
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file