Updated on 2026-08-14
|
|
@ -40,6 +40,7 @@ android {
|
|||
initWith debug
|
||||
versionNameSuffix "-beta"
|
||||
applicationIdSuffix ".debug"
|
||||
buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"prod\"'
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
|
|
@ -69,8 +70,11 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
implementation "androidx.core:core-ktx:1.3.2"
|
||||
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.5'
|
||||
|
||||
|
||||
// implementation 'com.tangem:blockchain:1.141.0'
|
||||
implementation 'blockchain-sdk-kotlin:blockchain:tangem-20210302.084810-3'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name" translatable="false">DEBUG</string>
|
||||
<string name="app_name" translatable="false">DebugTangem</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -81,6 +81,17 @@
|
|||
</activity>
|
||||
|
||||
<activity android:name="com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"isWalletPayIdEnabled": true,
|
||||
"isTopUpEnabled": true,
|
||||
"isSendingToPayIdEnabled": true,
|
||||
"isTopUpEnabled": true,
|
||||
"isCreatingTwinCardsAllowed": false
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"isWalletPayIdEnabled": true,
|
||||
"isTopUpEnabled": true,
|
||||
"isWalletPayIdEnabled": false,
|
||||
"isSendingToPayIdEnabled": true,
|
||||
"isTopUpEnabled": true,
|
||||
"isCreatingTwinCardsAllowed": true
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -14,6 +15,9 @@ 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.domain.tokens.CurrenciesRepository
|
||||
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
|
||||
|
|
@ -49,10 +53,10 @@ class TapApplication : Application() {
|
|||
PicassoHelper.initPicassoWithCaching(this)
|
||||
currenciesRepository = CurrenciesRepository(this)
|
||||
|
||||
initFeedbackManager()
|
||||
loadConfigs()
|
||||
}
|
||||
|
||||
|
||||
private fun loadConfigs() {
|
||||
val moshi = createMoshi()
|
||||
val localLoader = FeaturesLocalLoader(this, moshi)
|
||||
|
|
@ -62,4 +66,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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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"),
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
sealed class GlobalAction : Action {
|
||||
|
|
@ -15,8 +17,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()
|
||||
}
|
||||
|
|
@ -26,11 +26,17 @@ val globalMiddleware: Middleware<AppState> = { dispatch, appState ->
|
|||
store.dispatch(WalletAction.CheckSignedHashes.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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
globalState
|
||||
}
|
||||
}
|
||||
is GlobalAction.SetFeedbackManager -> {
|
||||
globalState.copy(feedbackManager = action.feedbackManager)
|
||||
}
|
||||
else -> globalState
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ data class GlobalState(
|
|||
val tangemService: TangemService = TangemService(),
|
||||
val configManager: ConfigManager? = null,
|
||||
val warningManager: WarningMessagesManager? = null,
|
||||
val feedbackManager: FeedbackManager? = null,
|
||||
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY
|
||||
) : StateType
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ sealed class TapError(
|
|||
}
|
||||
|
||||
sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemError {
|
||||
final override val code: Int = 1
|
||||
final override val code: Int = 50100
|
||||
override var customMessage: String = code.toString()
|
||||
|
||||
object CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card)
|
||||
|
|
|
|||
|
|
@ -85,20 +85,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.isSendingToPayIdEnabled)
|
||||
configManager?.turnOff(ConfigManager.isTopUpEnabled)
|
||||
} else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin
|
||||
|| data.card.cardData?.blockchainName == Blockchain.Bitcoin.id) {
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
} else {
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
}
|
||||
updateConfigManager(data)
|
||||
updateFeedbackManager(data)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(WalletAction.ResetState)
|
||||
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
|
||||
|
|
@ -120,9 +110,38 @@ class TapWalletManager {
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateConfigManager(data: ScanNoteResponse) {
|
||||
val configManager = store.state.globalState.configManager
|
||||
if (TapWorkarounds.isStart2Coin) {
|
||||
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.turnOff(ConfigManager.isTopUpEnabled)
|
||||
} else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin
|
||||
|| data.card.cardData?.blockchainName == Blockchain.Bitcoin.id) {
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
} else {
|
||||
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.CheckSignedHashes.CheckIfWarningNeeded)
|
||||
//TODO: I made it to WalletAction.CheckSignedHashes.CheckIfWarningNeeded
|
||||
store.dispatch(WalletAction.Warnings.CheckIfNeeded)
|
||||
val artworkId = data.verifyResponse?.artworkInfo?.id
|
||||
if (data.walletManager != null) {
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.commands.common.card.masks.Product
|
||||
import java.util.*
|
||||
|
||||
object TapWorkarounds {
|
||||
|
|
@ -12,5 +13,26 @@ object TapWorkarounds {
|
|||
isStart2Coin = card.cardData?.issuerName?.toLowerCase(Locale.US) == START_2_COIN_ISSUER
|
||||
}
|
||||
|
||||
const val START_2_COIN_ISSUER = "start2coin"
|
||||
fun Card.isExcluded(): Boolean {
|
||||
val cardData = this.cardData ?: return false
|
||||
val productMask = cardData.productMask
|
||||
val excludedBatch = excludedBatches.contains(cardData.batchId)
|
||||
val excludedIssuerName = excludedIssuers.contains(cardData.issuerName?.capitalize(Locale.US))
|
||||
val excludedProductMask = (productMask != null && // product mask is on cards v2.30 and later
|
||||
!productMask.contains(Product.Note) && !productMask.contains(Product.TwinCard))
|
||||
return excludedBatch || excludedIssuerName || excludedProductMask
|
||||
|
||||
}
|
||||
|
||||
private const val START_2_COIN_ISSUER = "start2coin"
|
||||
|
||||
private val excludedBatches = listOf(
|
||||
"0027",
|
||||
"0030",
|
||||
"0031",
|
||||
) // Tangem tags
|
||||
|
||||
private val excludedIssuers = listOf(
|
||||
"TTM BANK"
|
||||
)
|
||||
}
|
||||
|
|
@ -40,7 +40,9 @@ data class WarningMessage(
|
|||
Permanent, // нельзя скрыть
|
||||
|
||||
@Json(name = "temporary")
|
||||
Temporary // можно скрыть (кнопка ОК)
|
||||
Temporary, // можно скрыть (кнопка ОК)
|
||||
|
||||
AppRating
|
||||
}
|
||||
|
||||
enum class Location {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.TapWorkarounds
|
||||
import com.tangem.tap.domain.TapWorkarounds.isExcluded
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -123,23 +124,13 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
}
|
||||
|
||||
private fun getErrorIfExcludedCard(card: Card): TangemError? {
|
||||
val productMask = card.cardData?.productMask
|
||||
if (productMask != null && // product mask is on cards v2.30 and later
|
||||
!productMask.contains(Product.Note) && !productMask.contains(Product.TwinCard)) {
|
||||
return TapSdkError.CardForDifferentApp
|
||||
}
|
||||
if (excludedBatches.contains(card.cardData?.batchId)) {
|
||||
return TapSdkError.CardForDifferentApp
|
||||
}
|
||||
if (card.isExcluded()) return TapSdkError.CardForDifferentApp
|
||||
if (card.status == CardStatus.Purged) return TangemSdkError.CardIsPurged()
|
||||
if (card.status == CardStatus.NotPersonalized) return TangemSdkError.NotPersonalized()
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val excludedBatches = listOf("0027", "0030", "0031")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val Card.isMultiwalletAllowed: Boolean
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.tap.domain.termsOfUse
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import com.tangem.commands.common.card.Card
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CardTou {
|
||||
private val locale: Locale = ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)
|
||||
|
||||
fun getUrl(card: Card): Uri? {
|
||||
val issuerName = card.cardData?.issuerName ?: return null
|
||||
if (issuerName.toLowerCase() != "start2coin") return null
|
||||
|
||||
val baseUrl = "https://app.tangem.com/tou/"
|
||||
val regionCode = regionCode(card.cardId) ?: "fr"
|
||||
val filename = filename(locale.language, regionCode)
|
||||
return Uri.parse(baseUrl + filename)
|
||||
}
|
||||
|
||||
private fun filename(languageCode: String, regionCode: String): String {
|
||||
return when {
|
||||
languageCode == "fr" && regionCode == "ch" -> "Start2Coin-fr-ch-tangem.pdf"
|
||||
languageCode == "de" && regionCode == "ch" -> "Start2Coin-de-ch-tangem.pdf"
|
||||
languageCode == "en" && regionCode == "ch" -> "Start2Coin-en-ch-tangem.pdf"
|
||||
languageCode == "it" && regionCode == "ch" -> "Start2Coin-it-ch-tangem.pdf"
|
||||
languageCode == "fr" && regionCode == "fr" -> "Start2Coin-fr-fr-atangem.pdf"
|
||||
languageCode == "de" && regionCode == "at" -> "Start2Coin-de-at-tangem.pdf"
|
||||
regionCode == "fr" -> "Start2Coin-fr-fr-atangem.pdf"
|
||||
regionCode == "ch" -> "Start2Coin-en-ch-tangem.pdf"
|
||||
regionCode == "at" -> "Start2Coin-de-at-tangem.pdf"
|
||||
else -> "Start2Coin-fr-fr-atangem.pdf"
|
||||
}
|
||||
}
|
||||
|
||||
private fun regionCode(cardId: String): String? = when (cardId[1]) {
|
||||
'0' -> "fr"
|
||||
'1' -> "ch"
|
||||
'2' -> "at"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.commands.common.card.Card
|
|||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.termsOfUse.CardTou
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.network.coinmarketcap.FiatCurrency
|
||||
|
|
@ -19,6 +20,7 @@ sealed class DetailsAction : Action {
|
|||
val scanNoteResponse: ScanNoteResponse,
|
||||
val wallets: List<Wallet>,
|
||||
val isCreatingTwinWalletAllowed: Boolean?,
|
||||
val cardTou: CardTou,
|
||||
val fiatCurrencyName: FiatCurrencyName,
|
||||
val fiatCurrencies: List<FiatCurrencyName>? = null,
|
||||
) : DetailsAction()
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta
|
|||
),
|
||||
securityScreenState = SecurityScreenState(currentOption = securityOption),
|
||||
createTwinWalletState = twinsState,
|
||||
cardTermsOfUseUrl = action.cardTou.getUrl(action.card)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.tap.common.entities.Button
|
||||
|
|
@ -18,13 +19,14 @@ data class DetailsState(
|
|||
val eraseWalletState: EraseWalletState? = null,
|
||||
val confirmScreenState: ConfirmScreenState? = null,
|
||||
val securityScreenState: SecurityScreenState? = null,
|
||||
val createTwinWalletState: CreateTwinWalletState? = null
|
||||
val createTwinWalletState: CreateTwinWalletState? = null,
|
||||
val cardTermsOfUseUrl: Uri? = null,
|
||||
) : StateType
|
||||
|
||||
data class CardInfo(
|
||||
val cardId: String,
|
||||
val issuer: String,
|
||||
val signedHashes: Int
|
||||
val signedHashes: Int,
|
||||
)
|
||||
|
||||
enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty }
|
||||
|
|
@ -33,7 +35,7 @@ data class SecurityScreenState(
|
|||
val currentOption: SecurityOption = SecurityOption.LongTap,
|
||||
val selectedOption: SecurityOption = currentOption,
|
||||
val allowedOptions: EnumSet<SecurityOption> = EnumSet.allOf(SecurityOption::class.java),
|
||||
val buttonProceed: Button = Button(true)
|
||||
val buttonProceed: Button = Button(true),
|
||||
)
|
||||
|
||||
enum class SecurityOption { LongTap, PassCode, AccessCode }
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class CreateTwinWalletMiddleware {
|
|||
fun handle(action: DetailsAction.CreateTwinWalletAction) {
|
||||
when (action) {
|
||||
is DetailsAction.CreateTwinWalletAction.ShowWarning -> {
|
||||
val wallet = store.state.detailsState.wallets.getOrNull(0)
|
||||
val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet
|
||||
if (wallet == null) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.details.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
|
|
@ -7,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
|
||||
|
|
@ -14,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.*
|
||||
|
|
@ -79,6 +82,13 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
|
||||
tv_disclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
|
||||
|
||||
tv_card_tou.show(state.cardTermsOfUseUrl != null)
|
||||
tv_card_tou.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = state.cardTermsOfUseUrl
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
if (state.createTwinWalletState != null) {
|
||||
if (state.createTwinWalletState.allowRecreatingWallet == true) {
|
||||
tv_erase_wallet.show()
|
||||
|
|
@ -106,6 +116,9 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
tv_app_currency_title.setOnClickListener {
|
||||
store.dispatch(DetailsAction.AppCurrencyAction.ChooseAppCurrency)
|
||||
}
|
||||
tv_send_feedback.setOnClickListener {
|
||||
store.dispatch(GlobalAction.SendFeedback(FeedbackEmail()))
|
||||
}
|
||||
|
||||
tv_security_title.setOnClickListener {
|
||||
store.dispatch(DetailsAction.ManageSecurity.OpenSecurity)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
package com.tangem.tap.features.feedback
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import com.tangem.LogMessage
|
||||
import com.tangem.LoggerInterface
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class FeedbackManager(
|
||||
val infoHolder: AdditionalEmailInfo,
|
||||
private val context: Context,
|
||||
private val logCollector: TangemLogCollector,
|
||||
private val email: String = "support@tangem.com",
|
||||
) {
|
||||
|
||||
fun send(emailData: EmailData) {
|
||||
val fileLog = if (emailData is ScanFailsEmail) createLogFile() else null
|
||||
sendTo(email, emailData.subject, emailData.joinTogether(infoHolder), fileLog)
|
||||
}
|
||||
|
||||
private fun sendTo(email: String, subject: String, message: String, fileLog: File? = null) {
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
data = Uri.parse("mailto:")
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
|
||||
putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||
putExtra(Intent.EXTRA_TEXT, message)
|
||||
fileLog?.let {
|
||||
val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", it)
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val chooserIntent = Intent.createChooser(intent, "Send mail...")
|
||||
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
ContextCompat.startActivity(context, chooserIntent, null)
|
||||
} catch (ex: ActivityNotFoundException) {
|
||||
Timber.e(ex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLogFile(): File? {
|
||||
return try {
|
||||
val file = File(context.filesDir, "logs.txt")
|
||||
file.delete()
|
||||
file.createNewFile()
|
||||
|
||||
val stringWriter = StringWriter()
|
||||
logCollector.getLogs().forEach { stringWriter.append(it) }
|
||||
val fileWriter = FileWriter(file)
|
||||
fileWriter.write(stringWriter.toString())
|
||||
fileWriter.close()
|
||||
logCollector.clearLogs()
|
||||
file
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Can't create a file for email attachment")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TangemLogCollector : LoggerInterface {
|
||||
private val logs = mutableListOf<String>()
|
||||
|
||||
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<String> = 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")
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -30,6 +31,9 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) {
|
|||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
})
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.slide_right)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
|
|
|
|||
|
|
@ -141,12 +141,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<WarningMessage>) : SendAction()
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
package com.tangem.tap.features.send.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.commands.common.network.Result
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.isPayIdSupported
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUi
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetAddressError
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetWalletAddress
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress
|
||||
import com.tangem.tap.features.send.redux.FeeAction
|
||||
import com.tangem.tap.features.send.redux.TransactionExtrasAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -50,7 +48,7 @@ internal class AddressPayIdMiddleware {
|
|||
|
||||
private fun setAddressAndCheck(data: String, isUserInput: Boolean, dispatch: (Action) -> Unit) {
|
||||
val potentialPayId = data.toLowerCase()
|
||||
if (PayIdManager.isPayId(potentialPayId) && isPayIdEnabled()) {
|
||||
if (isPayIdEnabled() && PayIdManager.isPayId(potentialPayId)) {
|
||||
dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput))
|
||||
} else {
|
||||
dispatch(SetWalletAddress(data, isUserInput))
|
||||
|
|
@ -64,7 +62,7 @@ internal class AddressPayIdMiddleware {
|
|||
val addressPayId = sendState.addressPayIdState.normalFieldValue ?: return
|
||||
val isUserInput = sendState.addressPayIdState.viewFieldValue.isFromUserInput
|
||||
|
||||
if (PayIdManager.isPayId(addressPayId) && isPayIdEnabled()) {
|
||||
if (isPayIdEnabled() && PayIdManager.isPayId(addressPayId)) {
|
||||
verifyPayId(addressPayId, wallet, isUserInput, dispatch)
|
||||
} else {
|
||||
verifyAddress(addressPayId, wallet, isUserInput, dispatch)
|
||||
|
|
@ -110,7 +108,11 @@ internal class AddressPayIdMiddleware {
|
|||
}
|
||||
|
||||
private fun verifyAddress(address: String, wallet: Wallet, isUserInput: Boolean, dispatch: (Action) -> Unit) {
|
||||
val addressSchemeSplit = address.split(":")
|
||||
val addressSchemeSplit = if (wallet.blockchain == Blockchain.BitcoinCash) {
|
||||
listOf(address)
|
||||
} else {
|
||||
address.split(":")
|
||||
}
|
||||
val noSchemeAddress = when (addressSchemeSplit.size) {
|
||||
1 -> address // no scheme
|
||||
2 -> { // scheme
|
||||
|
|
@ -131,6 +133,9 @@ internal class AddressPayIdMiddleware {
|
|||
|
||||
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress)
|
||||
if (failReason == null) {
|
||||
noSchemeAddress.getQueryParameter("amount")?.toBigDecimalOrNull()?.let {
|
||||
dispatch(AmountAction.SetAmount(it, false))
|
||||
}
|
||||
dispatch(SetWalletAddress(supposedAddress, isUserInput))
|
||||
dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, null))
|
||||
} else {
|
||||
|
|
@ -141,7 +146,7 @@ internal class AddressPayIdMiddleware {
|
|||
|
||||
private fun isValidBlockchainAddressAndNotTheSameAsWallet(wallet: Wallet, address: String): Error? {
|
||||
return if (wallet.blockchain.validateAddress(address)) {
|
||||
if (wallet.addresses.all { it.value != address } ) {
|
||||
if (wallet.addresses.all { it.value != address }) {
|
||||
null
|
||||
} else {
|
||||
Error.ADDRESS_SAME_AS_WALLET
|
||||
|
|
@ -151,14 +156,10 @@ internal class AddressPayIdMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
//TODO: move to the blockchainSDK
|
||||
private fun extractAddressFromShareUri(shareUri: String): String {
|
||||
val sharePrefix = listOf("bitcoin:", "ethereum:", "xrpl:", "litecoin:", "bnb:")
|
||||
val prefixes = sharePrefix.filter { shareUri.contains(it) }
|
||||
return if (prefixes.isEmpty()) shareUri else shareUri.replace(prefixes[0], "")
|
||||
}
|
||||
|
||||
private fun String.removeShareUriQuery(): String = this.substringBefore("?")
|
||||
private fun String.getQueryParameter(name: String): String? {
|
||||
return this.substringAfter("?").splitToMap("&", "=")[name]
|
||||
}
|
||||
|
||||
private fun verifyClipboard(input: String?, appState: AppState?, dispatch: DispatchFunction) {
|
||||
val addressPayId = input ?: return
|
||||
|
|
@ -186,4 +187,11 @@ internal class AddressPayIdMiddleware {
|
|||
private fun isPayIdEnabled(): Boolean {
|
||||
return store.state.globalState.configManager?.config?.isSendingToPayIdEnabled ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun String.splitToMap(firstDelimiter: String, secondDelimiter: String): Map<String, String> {
|
||||
return this.split(firstDelimiter)
|
||||
.map { it.split(secondDelimiter) }
|
||||
.map { it.first() to it.last().toString() }
|
||||
.toMap()
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import com.tangem.tap.features.send.redux.states.SendButtonState
|
|||
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdk
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -52,7 +53,7 @@ val sendMiddleware: Middleware<AppState> = { 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
|
||||
|
|
@ -68,7 +69,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 = {
|
||||
|
|
@ -94,7 +95,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)
|
||||
|
|
@ -144,6 +145,8 @@ 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
|
||||
|
|
@ -157,6 +160,8 @@ private fun sendTransaction(
|
|||
Timber.e(throwable)
|
||||
FirebaseCrashlytics.getInstance().recordException(throwable)
|
||||
dispatch(SendAction.SendError(TapError.CustomError(message)))
|
||||
updateFeedbackManager(walletManager, amountToSend, feeAmount, destinationAddress, card)
|
||||
dispatch(SendAction.Dialog.SendTransactionFails(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -168,6 +173,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<TransactionError>): EnumSet<TransactionError> {
|
||||
val showIntoAmountField = EnumSet.noneOf(TransactionError::class.java)
|
||||
errors.forEach {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -149,9 +149,14 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
val scannedCode = data?.getStringExtra(ScanQrCodeActivity.SCAN_RESULT) ?: ""
|
||||
if (scannedCode.isEmpty()) return
|
||||
|
||||
store.dispatch(PasteAddressPayId(scannedCode))
|
||||
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
|
||||
store.dispatch(FeeAction.RequestFee)
|
||||
// Delayed launch is needed in order for the UI to be drawn and to process the sent events.
|
||||
// If do not use the delay, then etAmount error field is not displayed when
|
||||
// inserting an incorrect amount by shareUri
|
||||
imvQrCode.postDelayed({
|
||||
store.dispatch(PasteAddressPayId(scannedCode))
|
||||
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
|
||||
store.dispatch(FeeAction.RequestFee)
|
||||
}, 200)
|
||||
}
|
||||
|
||||
private fun setupAmountLayout() {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.adapters.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
|
||||
|
|
@ -184,22 +191,6 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
val amountToSend = state.viewAmountValue
|
||||
if (!amountToSend.isFromUserInput) fg.etAmountToSend.update(amountToSend.value)
|
||||
|
||||
// fg.tvAmountToSendShadow.text = amountToSend
|
||||
// if (amountToSend.length > 10) {
|
||||
// post is needed to wait for text size changes
|
||||
// fg.tvAmountToSendShadow.post {
|
||||
// fg.etAmountToSend.setTextSize(TypedValue.COMPLEX_UNIT_PX, fg.tvAmountToSendShadow.textSize - 2)
|
||||
// fg.etAmountToSend.update(amountToSend)
|
||||
// if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length)
|
||||
// }
|
||||
// } else {
|
||||
// val textSize = fg.resources.getDimension(R.dimen.text_size_amount_to_send)
|
||||
// fg.tvAmountToSendShadow.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
// fg.etAmountToSend.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
// fg.etAmountToSend.update(amountToSend)
|
||||
// if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length)
|
||||
// }
|
||||
|
||||
fg.tvAmountCurrency.update(state.mainCurrency.currencySymbol)
|
||||
(fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.type)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package com.tangem.tap.features.wallet.redux
|
|||
|
||||
import android.content.Context
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.TangemError
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.commands.common.card.Card
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
|
|
@ -30,7 +33,8 @@ sealed class WalletAction : Action {
|
|||
|
||||
data class SetArtworkId(val artworkId: String?) : WalletAction()
|
||||
|
||||
// sealed class ProcessWallet : WalletAction() {
|
||||
|
||||
// sealed class ProcessWallet : WalletAction() {
|
||||
// data class LoadWallet(val artworkId: String?, val allowTopUp: Boolean
|
||||
// ) : ProcessWallet() {
|
||||
// data class Success(val wallet: Wallet) : ProcessWallet()
|
||||
|
|
@ -45,7 +49,6 @@ sealed class WalletAction : Action {
|
|||
// }
|
||||
// }
|
||||
|
||||
|
||||
sealed class MultiWallet : WalletAction() {
|
||||
data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet()
|
||||
data class AddWalletManagers(val walletManagers: List<WalletManager>) : MultiWallet()
|
||||
|
|
@ -68,7 +71,15 @@ sealed class WalletAction : Action {
|
|||
object SaveCardId : CheckSignedHashes()
|
||||
}
|
||||
|
||||
data class SetWarnings(val warningList: List<WarningMessage>) : WalletAction()
|
||||
object Warnings : WalletAction() {
|
||||
object CheckIfNeeded : WalletAction()
|
||||
data class SetWarnings(val warningList: List<WarningMessage>) : WalletAction()
|
||||
|
||||
object AppRating : WalletAction() {
|
||||
object SetNeverToShow : WalletAction()
|
||||
object RemindLater : WalletAction()
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateWallet(val currency: CryptoCurrencyName? = null) : WalletAction() {
|
||||
object ScheduleUpdatingWallet : WalletAction()
|
||||
|
|
@ -88,40 +99,59 @@ sealed class WalletAction : Action {
|
|||
object Failure : WalletAction()
|
||||
}
|
||||
|
||||
|
||||
data class CopyAddress(val address: String, val context: Context) : WalletAction() {
|
||||
object Success : WalletAction(), NotificationAction {
|
||||
override val messageResource = R.string.wallet_notification_address_copied
|
||||
}
|
||||
}
|
||||
|
||||
data class ShareAddress(val address: String, val context: Context) : WalletAction()
|
||||
|
||||
object ShowQrCode : WalletAction()
|
||||
object HideDialog : WalletAction()
|
||||
data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction()
|
||||
|
||||
object CreateWallet : WalletAction()
|
||||
object EmptyWallet : WalletAction()
|
||||
object Scan : WalletAction()
|
||||
class ScanCardFinished(val scanError: TangemError? = null) : WalletAction()
|
||||
|
||||
data class Send(val amount: Amount? = null) : WalletAction() {
|
||||
data class ChooseCurrency(val amounts: List<Amount>?) : WalletAction()
|
||||
object Cancel : WalletAction()
|
||||
}
|
||||
|
||||
sealed class TopUpAction : WalletAction() {
|
||||
data class TopUp(val context: Context, val toolbarColor: Int) : TopUpAction()
|
||||
}
|
||||
object CreatePayId : WalletAction() {
|
||||
data class CompleteCreatingPayId(val payId: String) : WalletAction()
|
||||
data class Success(val payId: String) : WalletAction()
|
||||
object EmptyField : WalletAction(), ErrorAction {
|
||||
override val error = TapError.PayIdEmptyField
|
||||
}
|
||||
|
||||
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
|
||||
data class CopyAddress(val address: String, val context: Context) : WalletAction() {
|
||||
object Success : WalletAction(), NotificationAction {
|
||||
override val messageResource = R.string.wallet_notification_address_copied
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TwinsAction : WalletAction() {
|
||||
object ShowOnboarding : TwinsAction()
|
||||
object SetOnboardingShown : TwinsAction()
|
||||
data class SetTwinCard(
|
||||
val secondCardId: String, val number: TwinCardNumber,
|
||||
val isCreatingTwinCardsAllowed: Boolean
|
||||
) : TwinsAction()
|
||||
}
|
||||
}
|
||||
data class ShareAddress(val address: String, val context: Context) : WalletAction()
|
||||
|
||||
object ShowDialog : WalletAction() {
|
||||
object QrCode : WalletAction()
|
||||
object ScanFails : WalletAction()
|
||||
}
|
||||
|
||||
object HideDialog : WalletAction()
|
||||
|
||||
data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction()
|
||||
|
||||
object CreateWallet : WalletAction()
|
||||
object EmptyWallet : WalletAction()
|
||||
object Scan : WalletAction()
|
||||
|
||||
data class Send(val amount: Amount? = null) : WalletAction() {
|
||||
data class ChooseCurrency(val amounts: List<Amount>?) : WalletAction()
|
||||
object Cancel : WalletAction()
|
||||
}
|
||||
|
||||
sealed class TopUpAction : WalletAction() {
|
||||
data class TopUp(val context: Context, val toolbarColor: Int) : TopUpAction()
|
||||
}
|
||||
|
||||
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
|
||||
|
||||
sealed class TwinsAction : WalletAction() {
|
||||
object ShowOnboarding : TwinsAction()
|
||||
object SetOnboardingShown : TwinsAction()
|
||||
data class SetTwinCard(
|
||||
val secondCardId: String, val number: TwinCardNumber,
|
||||
val isCreatingTwinCardsAllowed: Boolean,
|
||||
) : TwinsAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
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
|
||||
import com.tangem.commands.common.card.CardType
|
||||
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
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.TopUpHelper
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.network.NetworkStateChanged
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
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()
|
||||
|
||||
val walletMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction -> topUpMiddleware.handle(action)
|
||||
is WalletAction.LoadWallet -> {
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.loadWalletData()
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadPayId -> {
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.loadPayId()
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadFiatRate -> {
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.loadFiatRate(store.state.globalState.appCurrency)
|
||||
}
|
||||
}
|
||||
is WalletAction.CreateWallet -> {
|
||||
if (store.state.walletState.twinCardsState != null) {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning(
|
||||
store.state.globalState.scanNoteResponse?.card?.cardId?.let {
|
||||
TwinsHelper.getTwinCardNumber(it)
|
||||
},
|
||||
CreateTwinWallet.CreateWallet
|
||||
))
|
||||
} else {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createWallet(
|
||||
store.state.globalState.scanNoteResponse?.card?.cardId
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.state.globalState.tapWalletManager
|
||||
.onCardScanned(result.data)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.UpdateWallet -> {
|
||||
if (store.state.walletState.state == ProgressState.Done) {
|
||||
scope.launch { store.state.globalState.tapWalletManager.updateWallet() }
|
||||
}
|
||||
}
|
||||
is WalletAction.UpdateWallet.Success -> setupWalletUpdate(action.wallet)
|
||||
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 {
|
||||
val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
|
||||
val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet
|
||||
val publicKey = store.state.globalState.scanNoteResponse?.card?.cardPublicKey
|
||||
if (cardId != null && wallet != null && publicKey != null) {
|
||||
val result = PayIdManager().setPayId(
|
||||
cardId, publicKey.toHexString(),
|
||||
action.payId, wallet.address, wallet.blockchain
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success ->
|
||||
store.dispatch(WalletAction.CreatePayId.Success(action.payId))
|
||||
is Result.Failure -> {
|
||||
val error = result.error as? TapError
|
||||
?: TapError.PayIdCreatingError
|
||||
store.dispatch(WalletAction.CreatePayId.Failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.Scan -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
|
||||
store.state.globalState.tapWalletManager
|
||||
.onCardScanned(result.data, true)
|
||||
if (store.state.walletState.twinCardsState != null) {
|
||||
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
|
||||
if (showOnboarding) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadData -> {
|
||||
scope.launch {
|
||||
store.state.globalState.scanNoteResponse?.let {
|
||||
store.state.globalState.tapWalletManager.loadData(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
is NetworkStateChanged -> {
|
||||
store.state.globalState.scanNoteResponse?.let { scanNoteResponse ->
|
||||
store.dispatch(WalletAction.CheckHashesCountOnline)
|
||||
scope.launch {
|
||||
store.state.globalState.tapWalletManager.onCardScanned(scanNoteResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletAction.CopyAddress -> {
|
||||
store.state.walletState.walletAddresses?.selectedAddress?.address?.let {
|
||||
action.context.copyToClipboard(it)
|
||||
store.dispatch(WalletAction.CopyAddress.Success)
|
||||
}
|
||||
}
|
||||
is WalletAction.ExploreAddress -> {
|
||||
val uri = Uri.parse(store.state.walletState.walletAddresses?.selectedAddress?.exploreUrl)
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
ContextCompat.startActivity(action.context, intent, null)
|
||||
}
|
||||
is WalletAction.Send -> {
|
||||
val newAction = prepareSendAction(action.amount)
|
||||
store.dispatch(newAction)
|
||||
if (newAction is PrepareSendScreen) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
}
|
||||
}
|
||||
is WalletAction.Warnings.CheckIfNeeded -> {
|
||||
val globalState = store.state.globalState
|
||||
val validator = globalState.scanNoteResponse?.walletManager as? SignatureCountValidator
|
||||
globalState.scanNoteResponse?.card?.let { card ->
|
||||
store.state.globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
|
||||
if (card.getType() != CardType.Release) addWarningMessage(WarningMessagesManager.devCardWarning())
|
||||
if (!preferencesStorage.wasCardScannedBefore(card.cardId)) {
|
||||
checkIfWarningNeeded(card, validator)?.let { addWarningMessage(it) }
|
||||
}
|
||||
updateWarningMessages()
|
||||
}
|
||||
}
|
||||
is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline()
|
||||
is WalletAction.SaveCardId -> {
|
||||
val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
|
||||
cardId?.let { preferencesStorage.saveScannedCardId(it) }
|
||||
}
|
||||
is WalletAction.TwinsAction.SetTwinCard -> {
|
||||
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
|
||||
if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding)
|
||||
}
|
||||
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)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
delay(10000)
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(WalletAction.UpdateWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun prepareSendAction(amount: Amount?): Action {
|
||||
return if (amount != null) {
|
||||
if (amount.type is AmountType.Token) {
|
||||
PrepareSendScreen(store.state.walletState.wallet?.amounts?.get(AmountType.Coin), amount)
|
||||
} else {
|
||||
PrepareSendScreen(amount)
|
||||
}
|
||||
} else {
|
||||
val amounts = store.state.walletState.wallet?.amounts?.toSendableAmounts()
|
||||
if (amounts?.size ?: 0 > 1) {
|
||||
WalletAction.Send.ChooseCurrency(amounts)
|
||||
} else {
|
||||
val amountToSend = amounts?.first()
|
||||
PrepareSendScreen(amountToSend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfWarningNeeded(
|
||||
card: Card, signatureCountValidator: SignatureCountValidator? = null,
|
||||
): WarningMessage? {
|
||||
if (card.isTwinCard()) return null
|
||||
|
||||
return if (signatureCountValidator == null) {
|
||||
if (card.walletSignedHashes ?: 0 > 0) {
|
||||
WarningMessagesManager.alreadySignedHashesWarning()
|
||||
} else {
|
||||
store.dispatch(WalletAction.SaveCardId)
|
||||
null
|
||||
}
|
||||
} else {
|
||||
store.dispatch(WalletAction.NeedToCheckHashesCountOnline)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkHashesCountOnline() {
|
||||
if (store.state.walletState.hashesCountVerified != false) return
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return
|
||||
|
||||
val card = store.state.globalState.scanNoteResponse?.card
|
||||
if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return
|
||||
|
||||
if (card.isTwinCard()) return
|
||||
|
||||
val validator = store.state.globalState.scanNoteResponse?.walletManager
|
||||
as? SignatureCountValidator
|
||||
scope.launch {
|
||||
val result = validator?.validateSignatureCount(card.walletSignedHashes ?: 0)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
SimpleResult.Success -> {
|
||||
store.dispatch(WalletAction.ConfirmHashesCount)
|
||||
store.dispatch(WalletAction.SaveCardId)
|
||||
}
|
||||
is SimpleResult.Failure ->
|
||||
if (result.error is BlockchainSdkError.SignatureCountNotMatched) {
|
||||
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
|
||||
} else if (card.walletSignedHashes ?: 0 > 0) {
|
||||
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addWarningMessage(warning: WarningMessage, autoUpdate: Boolean = false) {
|
||||
store.state.globalState.warningManager?.addWarning(warning)
|
||||
if (autoUpdate) updateWarningMessages()
|
||||
}
|
||||
|
||||
private fun updateWarningMessages() {
|
||||
val warningManager = store.state.globalState.warningManager ?: return
|
||||
store.dispatch(WalletAction.Warnings.SetWarnings(
|
||||
warningManager.getWarnings(WarningMessage.Location.MainScreen)))
|
||||
}
|
||||
}
|
||||
|
||||
private class TopUpMiddleware {
|
||||
fun handle(action: WalletAction.TopUpAction) {
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction.TopUp -> {
|
||||
val config = store.state.globalState.configManager?.config ?: return
|
||||
val addresses = store.state.walletState.walletAddresses ?: return
|
||||
if (addresses.list.isEmpty()) return
|
||||
|
||||
val defaultAddress = addresses.list[0].address
|
||||
val url = TopUpHelper.getUrl(
|
||||
store.state.walletState.currencyData.currencySymbol!!,
|
||||
defaultAddress,
|
||||
config.moonPayApiKey,
|
||||
config.moonPayApiSecretKey
|
||||
)
|
||||
val customTabsIntent = CustomTabsIntent.Builder()
|
||||
.setToolbarColor(action.toolbarColor)
|
||||
.build()
|
||||
customTabsIntent.launchUrl(action.context, Uri.parse(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -213,7 +213,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
}
|
||||
newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl))
|
||||
}
|
||||
is WalletAction.ShowQrCode -> {
|
||||
is WalletAction.ShowDialog.QrCode -> {
|
||||
val selectedWalletData = newState.getWalletData(newState.selectedWallet)
|
||||
newState = newState.copy(
|
||||
walletDialog = WalletDialog.QrDialog(
|
||||
|
|
@ -223,6 +223,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)
|
||||
}
|
||||
|
|
@ -232,7 +235,10 @@ 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.TopUpAction -> {
|
||||
newState = newState.copy(topUpState = handleTopUpActions(action, newState.topUpState))
|
||||
}
|
||||
is WalletAction.TopUpAction -> return newState
|
||||
is WalletAction.ChangeSelectedAddress -> {
|
||||
val selectedWalletData = newState.getWalletData(newState.selectedWallet)
|
||||
|
|
@ -246,6 +252,13 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
)
|
||||
newState = newState.copy(wallets = wallets)
|
||||
}
|
||||
is WalletAction.ScanCardFinished -> {
|
||||
newState = if (action.scanError == null) {
|
||||
newState.copy(scanCardFailsCounter = 0)
|
||||
} else {
|
||||
newState.copy(scanCardFailsCounter = newState.scanCardFailsCounter + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ data class WalletState(
|
|||
val walletDialog: WalletDialog? = null,
|
||||
val twinCardsState: TwinCardsState? = null,
|
||||
val mainWarningsList: List<WarningMessage> = mutableListOf(),
|
||||
val scanCardFailsCounter: Int = 0,
|
||||
val wallets: List<WalletData> = emptyList(),
|
||||
val walletManagers: List<WalletManager> = emptyList(),
|
||||
val isMultiwalletAllowed: Boolean = false,
|
||||
|
|
@ -101,6 +102,7 @@ sealed class WalletDialog {
|
|||
) : WalletDialog()
|
||||
|
||||
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog()
|
||||
object ScanFailsDialog: WalletDialog()
|
||||
}
|
||||
|
||||
enum class ProgressState { Loading, Done, Error }
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import com.tangem.tap.common.extensions.show
|
|||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.termsOfUse.CardTou
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.ui.adapters.SpacesItemDecoration
|
||||
|
|
@ -26,6 +28,10 @@ import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
|||
import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView
|
||||
import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView
|
||||
import com.tangem.tap.features.wallet.ui.wallet.WalletView
|
||||
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.fragment_wallet.*
|
||||
|
|
@ -160,6 +166,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
scanNoteResponse.card, scanNoteResponse,
|
||||
store.state.walletState.walletManagers.map { it.wallet },
|
||||
store.state.globalState.configManager?.config?.isCreatingTwinCardsAllowed,
|
||||
CardTou(),
|
||||
store.state.globalState.appCurrency
|
||||
))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Details))
|
||||
|
|
@ -176,3 +183,78 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
//TODO: handle addition of testnets
|
||||
//class MultipleAddressUiHelper {
|
||||
// companion object {
|
||||
// fun typeToId(type: AddressType): Int {
|
||||
// return when (type) {
|
||||
// is BitcoinAddressType.Legacy -> R.id.chip_legacy
|
||||
// is BitcoinAddressType.Segwit -> R.id.chip_default
|
||||
// is CardanoAddressType.Byron -> R.id.chip_legacy
|
||||
// is CardanoAddressType.Shelley -> R.id.chip_default
|
||||
// else -> View.NO_ID
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun idToType(id: Int, blockchain: Blockchain?): AddressType? {
|
||||
// return when (id) {
|
||||
// R.id.chip_default -> {
|
||||
// when (blockchain) {
|
||||
// Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> BitcoinAddressType.Segwit
|
||||
// Blockchain.CardanoShelley -> CardanoAddressType.Shelley
|
||||
// else -> null
|
||||
// }
|
||||
// }
|
||||
// R.id.chip_legacy -> {
|
||||
// when (blockchain) {
|
||||
// Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> BitcoinAddressType.Legacy
|
||||
// Blockchain.CardanoShelley -> CardanoAddressType.Byron
|
||||
// else -> null
|
||||
// }
|
||||
// }
|
||||
// else -> null
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//TODO: handle scan failed dialog:
|
||||
//private fun handleDialogs(walletDialog: WalletDialog?) {
|
||||
// when (walletDialog) {
|
||||
// is WalletDialog.QrDialog -> {
|
||||
// if (walletDialog.qrCode != null && walletDialog.shareUrl != null) {
|
||||
// if (dialog == null) dialog = QrDialog(requireContext()).apply {
|
||||
// this.showQr(
|
||||
// walletDialog.qrCode, walletDialog.shareUrl, walletDialog.currencyName
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// is WalletDialog.CreatePayIdDialog -> {
|
||||
// when (walletDialog.creatingPayIdState) {
|
||||
// CreatingPayIdState.EnterPayId -> {
|
||||
// if (dialog == null) dialog = PayIdDialog(requireContext()).apply {
|
||||
// this.show()
|
||||
// }
|
||||
// (dialog as? PayIdDialog)?.stopProgress()
|
||||
// }
|
||||
// CreatingPayIdState.Waiting -> (dialog as? PayIdDialog)?.showProgress()
|
||||
// }
|
||||
// }
|
||||
// is WalletDialog.SelectAmountToSendDialog -> {
|
||||
// if (dialog == null) dialog = AmountToSendDialog(requireContext()).apply {
|
||||
// this.show(walletDialog.amounts)
|
||||
// }
|
||||
// }
|
||||
// is WalletDialog.ScanFailsDialog -> {
|
||||
// if (dialog == null) dialog = ScanFailsDialog.create(requireContext()).apply {
|
||||
// this.show()
|
||||
// }
|
||||
// }
|
||||
// null -> {
|
||||
// dialog?.dismiss()
|
||||
// dialog = null
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -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(WalletAction.Warnings.AppRating.SetNeverToShow)
|
||||
store.dispatch(GlobalAction.HideWarningMessage(warning))
|
||||
store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail()))
|
||||
}
|
||||
view.btn_really_cool.setOnClickListener {
|
||||
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
|
||||
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<List<FiatCurrency>> by lazy {
|
||||
|
|
@ -97,4 +99,60 @@ 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 = "fundsFoundDate"
|
||||
private val K_USER_WAS_INTERACT_WITH_RATING = "userWasInteractWithRating"
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fun isReadyToShow(): Boolean {
|
||||
val fundsDate = fundsFoundDate ?: return false
|
||||
|
||||
if (!userWasInteractWithRating()) {
|
||||
val diff = Calendar.getInstance().timeInMillis - fundsDate.timeInMillis
|
||||
val diffInDays = diff / (100 * 60 * 60 * 24)
|
||||
if (diffInDays >= firstShowing) return true
|
||||
}
|
||||
|
||||
val nextShowing = getCounterOfNextShowing()
|
||||
return launchCounts >= nextShowing
|
||||
}
|
||||
|
||||
fun applyDelayedShowing() {
|
||||
updateNextShowing(launchCounts + deferShowing)
|
||||
}
|
||||
|
||||
fun setNeverToShow() {
|
||||
updateNextShowing(999999999)
|
||||
}
|
||||
|
||||
private fun updateNextShowing(at: Int) {
|
||||
val editor = preferences.edit()
|
||||
editor.putInt(K_SHOW_RATING_AT_LAUNCH_COUNT, at)
|
||||
editor.putBoolean(K_USER_WAS_INTERACT_WITH_RATING, true)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun userWasInteractWithRating(): Boolean = preferences.getBoolean(K_USER_WAS_INTERACT_WITH_RATING, false)
|
||||
private fun getCounterOfNextShowing(): Int = preferences.getInt(K_SHOW_RATING_AT_LAUNCH_COUNT, firstShowing)
|
||||
}
|
||||
|
|
@ -126,6 +126,21 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_signed_hashes_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_card_tou"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:drawablePadding="15dp"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
android:visibility="gone"
|
||||
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_disclaimer"
|
||||
android:text="@string/details_row_title_card_tou" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_settings_title"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -137,7 +152,7 @@
|
|||
android:textColor="@color/colorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_disclaimer" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_card_tou" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -170,6 +185,19 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_settings_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_send_feedback"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="@string/details_row_title_send_feedback"
|
||||
android:textColor="@color/darkGray6"
|
||||
android:textSize="16sp"
|
||||
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_app_currency_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_card_title"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -181,7 +209,7 @@
|
|||
android:textColor="@color/colorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_app_currency_title" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_send_feedback" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -20,14 +20,25 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Test title"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_close"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/btn_close"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:tint="@android:color/white"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/tv_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/tv_title"
|
||||
app:srcCompat="@drawable/ic_close" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_message"
|
||||
android:layout_width="0dp"
|
||||
|
|
@ -54,9 +65,46 @@
|
|||
android:text="@string/how_to_got_it_button"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/white"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_message" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_can_be_better"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/warning_button_can_be_better"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/white"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_really_cool"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_message" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_really_cool"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/warning_button_really_cool"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/white"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_message" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_controls_temporary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="btn_got_it" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_controls_rating"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="visible"
|
||||
app:constraint_referenced_ids="btn_close, btn_can_be_better, btn_really_cool" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file → Executable file
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 372 B |
|
Before Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file → Executable file
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 237 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file → Executable file
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 515 B |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file → Executable file
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 902 B |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file → Executable file
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
|
@ -12,7 +12,7 @@
|
|||
<string name="common_camera_denied_alert_message">Sie haben keinen Zugang zur Kamera erteilt, bitte passen Sie Ihre Datenschutzeinstellungen an</string>
|
||||
<string name="common_camera_alert_button_settings">Einstellungen</string>
|
||||
<string name="home_welcome">Willkommen bei Tangem. Haben Sie eine dieser Karten?</string>
|
||||
<string name="home_welcome_back">Willkommen bei Tangem Tap. Scannen Sie Ihre Karten um zu starten.</string>
|
||||
<string name="home_welcome_back">Willkommen bei Tangem. Scannen Sie Ihre Karten um zu starten.</string>
|
||||
<string name="home_button_yes">Ja! Bitte scannen</string>
|
||||
<string name="home_button_shop">Einkaufen</string>
|
||||
<string name="home_ready_title">Halten Sie die \ncard hinter dem \nphone bereit und scannen Sie sie.</string>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<string name="wallet_button_scan">Scannen</string>
|
||||
<string name="wallet_button_send">Absenden</string>
|
||||
<string name="wallet_button_create_wallet">ein Wallet erstellen</string>
|
||||
<string name="wallet_title">Tangem Tap</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="wallet_choice_wallet_option_title">eine Walletoption wählen</string>
|
||||
<string name="wallet_balance_token_title">Token</string>
|
||||
<string name="wallet_balance_tokens_title">Tokens</string>
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
<string name="alert_card_signed_transactions">Diese Karte wurde früher bereits aufgeladen und Transaktionen wurden damit signiert. Ziehen Sie eine sofortige Auszahlung aller Beträge in Betracht, wenn Sie diese Karte von einer nicht vertrauenswürdigen Quelle erhalten haben.</string>
|
||||
<string name="alert_title">Warnung</string>
|
||||
|
||||
<string name="alert_unsupported_card">Diese Karte ist für die Zusammenarbeit mit Tangem Tap nicht geeignet</string>
|
||||
<string name="alert_unsupported_card">Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet</string>
|
||||
<string name="alert_developer_card">Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel</string>
|
||||
<string name="alert_old_card">Die vor September 2019 ausgestellten Karten können mit iPhone momentan nicht extrahiert werden. Wir arbeiten eng mit Apple zusammen, damit es in den nächsten Versionen von iOS möglich wäre.</string>
|
||||
<string name="initial_message_sign_header">Tippen um zu signieren</string>
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
|
||||
<string name="disclaimer_text">
|
||||
RECHTLICHER HAFTUNGSAUSSCHLUSS
|
||||
\n\n1.Tangem Tap App (Software)
|
||||
\n\n1.Tangem App (Software)
|
||||
|
||||
\n\nDie Software ist nur für die Verwendung mit Tangem hardware Wallets (Karten) über NFC-Schnittstelle geeignet. Die Software:
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
<string name="common_camera_denied_alert_message">Vous n\'avez pas octroyé l’accès à votre caméra, veuillez modifier vos paramètres de confidentialité </string>
|
||||
<string name="common_camera_alert_button_settings">Paramètres </string>
|
||||
<string name="home_welcome">Bienvenue à Tangem. Avez-vous une de nos cartes</string>
|
||||
<string name="home_welcome_back"> Encore une fois, bienvenue dans Tangem Tap. \nScannez votre carte pour commencer.</string>
|
||||
<string name="home_welcome_back"> Encore une fois, bienvenue dans Tangem. \nScannez votre carte pour commencer.</string>
|
||||
<string name="home_button_yes">Oui! Scannez-la. </string>
|
||||
<string name="home_button_shop">Magasin </string>
|
||||
<string name="home_ready_title">Préparez-vous à appuyer votre carte au dos de votre téléphone.</string>
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
<string name="wallet_button_scan">Scanner </string>
|
||||
<string name="wallet_button_send">Envoyer </string>
|
||||
<string name="wallet_button_create_wallet">Créer un portefeuille </string>
|
||||
<string name="wallet_title">Tangem Tap</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="wallet_choice_wallet_option_title">Choisir une variante de portefeuille </string>
|
||||
<string name="wallet_balance_token_title"> Jeton d\'authentification </string>
|
||||
<string name="wallet_balance_tokens_title">Jetons d\'authentification </string>
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
<string name="alert_old_device_this_card">Vous pouvez rencontrer des problèmes NFC avec certains iPhone 7/7 + lors de l’extraction </string>
|
||||
<string name="alert_card_signed_transactions">Cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable.</string>
|
||||
<string name="alert_title">Attention</string>
|
||||
<string name="alert_unsupported_card">Cette carte n\'est pas conçue pour fonctionner avec Tangem Tap </string>
|
||||
<string name="alert_unsupported_card">Cette carte n\'est pas conçue pour fonctionner avec Tangem</string>
|
||||
<string name="alert_developer_card">La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement </string>
|
||||
<string name="alert_old_card">Les cartes Tangem émises avant septembre 2019 ne peuvent actuellement pas être extraites à l\'aide de l\'iPhone. Nous travaillons activement avec Apple pour rendre cela possible dans les futures versions d\'iOS. </string>
|
||||
<string name="initial_message_sign_header">Touchez pour signer </string>
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
|
||||
Avertissement
|
||||
|
||||
\n\n1.Application Tangem Tap (Logiciel)
|
||||
\n\n1.Application Tangem (Logiciel)
|
||||
|
||||
\n\nLe logiciel est destiné à l\’utilisation exclusivement avec les e-portefeuilles Tangem (Cartes) à l\’aide de l\’interface NFC. Le logiciel NE réalise pas ce qui suit :
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<string name="common_camera_denied_alert_message">Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy</string>
|
||||
<string name="common_camera_alert_button_settings">Impostazioni</string>
|
||||
<string name="home_welcome">Benvenuti in Tangem. Hai una delle nostre schede?</string>>
|
||||
<string name="home_welcome_back">Benevenuti di nuovo su Tangem Tap. Scansiona la tua carta per iniziare.</string>
|
||||
<string name="home_welcome_back">Benevenuti di nuovo su Tangem. Scansiona la tua carta per iniziare.</string>
|
||||
<string name="home_button_yes">Si! Scansiona carta.</string>
|
||||
<string name="home_button_shop">Shop</string>
|
||||
<string name="home_ready_title">Preparati ad avvicinare la tua carta sul retro del telefono.</string>
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
<string name="wallet_button_scan">Scansiona</string>
|
||||
<string name="wallet_button_send">Invia</string>
|
||||
<string name="wallet_button_create_wallet">Crea portafoglio</string>
|
||||
<string name="wallet_title">Tangem Tap</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="wallet_choice_wallet_option_title">Scegli l\'opzione del portafoglio</string>
|
||||
<string name="wallet_balance_token_title">Token</string>
|
||||
<string name="wallet_balance_tokens_title">Token</string>
|
||||
|
|
@ -95,7 +95,7 @@
|
|||
<string name="alert_old_device_this_card">Potresti riscontrare problemi con l\'NFC su alcuni iPhone 7/7 + durante la rimozione</string>
|
||||
<string name="alert_card_signed_transactions">Questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile.</string>
|
||||
<string name="alert_title">Attenzione</string>
|
||||
<string name="alert_unsupported_card">Questa carta non è progettata per funzionare con Tangem Tap</string>
|
||||
<string name="alert_unsupported_card">Questa carta non è progettata per funzionare con Tangem</string>
|
||||
<string name="alert_developer_card">La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento</string>
|
||||
<string name="alert_old_card">Le schede emesse prima di settembre 2019 non possono attualmente essere utilizzate con un iPhone. Stiamo lavorando a stretto contatto con Apple per renderle disponibili nelle future versioni di iOS. </string>
|
||||
<string name="initial_message_sign_header">Avvicina per firmare</string>
|
||||
|
|
@ -110,7 +110,7 @@
|
|||
|
||||
NOTA LEGALE\n
|
||||
\n
|
||||
App Tangem Tap (software)\n
|
||||
App Tangem (software)\n
|
||||
\n
|
||||
1. Il software può essere utilizzato solo con i portafogli hardware Tangem (schede) tramite l\'interfaccia NFC. Il software NON esegue le seguenti operazioni:\n
|
||||
\n
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<resources>
|
||||
|
||||
<string name="app_name" translatable="false">Tangem Tap</string>
|
||||
<string name="app_name" translatable="false">Tangem</string>
|
||||
|
||||
<string name="common_no">No</string>
|
||||
<string name="common_save_changes">Save changes</string>
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
<string name="common_camera_alert_button_settings">Settings</string>
|
||||
|
||||
<string name="home_welcome">Welcome to Tangem.\nDo you have one of our cards?</string>
|
||||
<string name="home_welcome_back">Welcome back to Tangem Tap. \nScan your card to start.</string>
|
||||
<string name="home_welcome_back">Welcome back to Tangem. \nScan your card to start.</string>
|
||||
<string name="home_button_shop">Shop</string>
|
||||
<string name="home_ready_title">Get ready to tap your \ncard to the back of the \nphone.</string>
|
||||
<string name="home_button_tapin">Tap in</string>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<string name="wallet_button_scan">Scan</string>
|
||||
<string name="wallet_button_send">Send</string>
|
||||
<string name="wallet_button_create_wallet">Create wallet</string>
|
||||
<string name="wallet_title">Tangem Tap</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="wallet_choice_wallet_option_title">Choice wallet option</string>
|
||||
|
||||
<string name="wallet_balance_token_title">Token</string>
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
<string name="alert_old_device_this_card">You may experience NFC problems with some iPhone 7/7+ during the extraction</string>
|
||||
<string name="alert_card_signed_transactions">This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source.</string>
|
||||
<string name="alert_title">Warning</string>
|
||||
<string name="alert_unsupported_card">This card it is not designed to work with Tangem Tap</string>
|
||||
<string name="alert_unsupported_card">This card it is not designed to work with Tangem</string>
|
||||
<string name="alert_developer_card">The card you scanned is a development card. Don’t accept it as a payment</string>
|
||||
<string name="alert_old_card">Tangem cards manufactured before September 2019 cannot currently be extracted with an iPhone. We’re working hard with Apple to make it possible in future versions of iOS.</string>
|
||||
|
||||
|
|
@ -124,7 +124,7 @@
|
|||
|
||||
<string name="disclaimer_text">Legal Disclaimer
|
||||
\n
|
||||
\n1. Tangem Tap application (Software)
|
||||
\n1. Tangem application (Software)
|
||||
\n
|
||||
\nThe Software is intended for usage only with Tangem hardware wallets (Cards) via NFC interface. The Software DOES NOT:
|
||||
\n
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ this wallet.
|
|||
<string name="details_twins_recreate_subtitle" translatable="false">The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning.</string>
|
||||
<string name="details_twins_recreate_button_format" translatable="false">Tap the card #%s</string>
|
||||
<string name="details_twins_recreate_alert" translatable="false">You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet.</string>
|
||||
<string name="details_row_title_card_tou" translatable="false">Card terms of use</string>
|
||||
|
||||
<string name="notification_twins_recreate_success" translatable="false">The twin address was successfully created</string>
|
||||
|
||||
|
|
@ -43,6 +44,25 @@ this wallet.
|
|||
<string name="send_error_invalid_destination_tag" translatable="false">Invalid destination tag. It won\'t be added to the transaction</string>
|
||||
<string name="send_error_invalid_memo_id" translatable="false">Invalid Memo ID. It won\'t be added to the transaction</string>
|
||||
|
||||
<string name="details_section_title_app" translatable="false">App</string>
|
||||
<string name="details_row_title_send_feedback" translatable="false">Send feedback</string>
|
||||
<string name="alert_app_feedback_sent_title" translatable="false">Sent successfully</string>
|
||||
<string name="alert_app_feedback_sent_message" translatable="false">Thank you for your feedback</string>
|
||||
<string name="alert_negative_app_rate_sent_title" translatable="false">Your suggestions were sent</string>
|
||||
<string name="alert_negative_app_rate_sent_message" translatable="false">Thank your for your feedback. We will response as soon as possible</string>
|
||||
<string name="alert_failed_to_send_email_title" translatable="false">Failed to send email</string>
|
||||
<string name="alert_failed_to_send_email_message" translatable="false">Reason: %s</string>
|
||||
<string name="alert_failed_to_send_transaction_title" translatable="false">Can’t send a transaction</string>
|
||||
<string name="alert_failed_to_send_transaction_message" translatable="false">Reason: %s. Do you want to send feedback?</string>
|
||||
<string name="alert_troubleshooting_scan_card_title" translatable="false">Are you having difficulty scanning your card?</string>
|
||||
<string name="alert_troubleshooting_scan_card_message" translatable="false">Please try to tap the card exactly as shown in the animation or request support.</string>
|
||||
<string name="alert_button_try_again" translatable="false">Try again</string>
|
||||
<string name="alert_button_request_support" translatable="false">Request support</string>
|
||||
<string name="alert_button_send_feedback" translatable="false">Send feedback</string>
|
||||
<string name="warning_button_really_cool" translatable="false">Really cool!</string>
|
||||
<string name="warning_button_can_be_better" translatable="false">Can be better</string>
|
||||
<string name="warning_rate_app_title" translatable="false">One question</string>
|
||||
<string name="warning_rate_app_message" translatable="false">How do you like Tangem?</string>
|
||||
|
||||
<string name="custom_token_creation_error_empty_field" translatable="false">All fields are required</string>
|
||||
|
||||
|
|
@ -70,7 +90,7 @@ this wallet.
|
|||
//Details
|
||||
<string name="details_section_title_blockchain" translatable="false">Blockchain</string>
|
||||
<string name="details_row_title_manage_tokens" translatable="false">Manage tokens</string>
|
||||
|
||||
|
||||
<string name="add_tokens_subtitle_blockchains" translatable="false">Blockchains</string>
|
||||
<string name="add_tokens_subtitle_tokens" translatable="false">Ethereum Tokens</string>
|
||||
|
||||
|
|
|
|||
4
app/src/main/res/xml/provider_paths.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<files-path name="files" path="/"/>
|
||||
</paths>
|
||||