diff --git a/app/build.gradle b/app/build.gradle
index 549774d931..c65890f80b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -69,21 +69,25 @@ dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
- implementation 'com.google.android.material:material:1.2.1'
- coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
+ implementation 'com.google.android.material:material:1.3.0'
+ implementation "androidx.core:core-ktx:1.3.2"
+ implementation 'com.google.android.play:core:1.10.0'
+ 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 'com.tangem:core:1.101.0'
- implementation 'com.tangem:sdk:1.101.0'
+
+ implementation 'com.tangem:blockchain:1.157.0'
+ implementation 'com.tangem:core:1.108.0'
+ implementation 'com.tangem:sdk:1.108.0'
// WebView
implementation "androidx.browser:browser:1.3.0"
//lifecycle
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
- implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
+ implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "org.rekotlin:rekotlin:1.0.4"
@@ -103,7 +107,7 @@ dependencies {
implementation 'com.google.firebase:firebase-config-ktx'
implementation 'com.google.firebase:firebase-analytics-ktx'
- testImplementation 'junit:junit:4.13.1'
+ testImplementation 'junit:junit:4.13.2'
testImplementation "com.google.truth:truth:1.0.1"
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml
index 4cc2b19474..bf9ef4d842 100644
--- a/app/src/debug/res/values/strings.xml
+++ b/app/src/debug/res/values/strings.xml
@@ -1,6 +1,6 @@
- DEBUG
+ DebugTangem
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2b7b764bfc..ee1acb1fe8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -81,6 +81,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt
index 2a20c88628..732ca5892e 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -5,10 +5,10 @@ import android.content.pm.ActivityInfo
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Bundle
+import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.tangem.CardFilter
import com.tangem.Config
-import com.tangem.Log
import com.tangem.TangemSdk
import com.tangem.commands.common.card.CardType
import com.tangem.tangem_sdk_new.extensions.init
@@ -46,7 +46,7 @@ private fun initCoroutineExceptionHandler(): CoroutineExceptionHandler {
val sw = StringWriter()
throwable.printStackTrace(PrintWriter(sw))
val exceptionAsString: String = sw.toString()
- Log.e("TangemSdk", exceptionAsString)
+ Log.e("Coroutine", exceptionAsString)
throw throwable
}
}
diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt
index cf26114c15..0f609b7328 100644
--- a/app/src/main/java/com/tangem/tap/TapApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TapApplication.kt
@@ -1,14 +1,23 @@
package com.tangem.tap
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
import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.domain.config.ConfigLoader
-import com.tangem.tap.domain.config.ConfigManager
-import com.tangem.tap.domain.config.LocalLoader
-import com.tangem.tap.domain.config.RemoteLoader
+import com.tangem.tap.domain.configurable.config.ConfigManager
+import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
+import com.tangem.tap.domain.configurable.config.FeaturesRemoteLoader
+import com.tangem.tap.domain.configurable.warningMessage.RemoteWarningLoader
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
+import com.tangem.tap.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
@@ -22,27 +31,50 @@ val store = Store(
state = AppState()
)
lateinit var preferencesStorage: PreferencesStorage
+lateinit var currenciesRepository: CurrenciesRepository
class TapApplication : Application() {
override fun onCreate() {
super.onCreate()
- if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
+ if (BuildConfig.DEBUG) {
+ Timber.plant(Timber.DebugTree())
+ Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
+ this.minimumFetchIntervalInSeconds = 60
+ })
+ } else {
+ Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
+ this.minimumFetchIntervalInSeconds = 3600
+ })
+ }
- ConfigLoader.init()
NetworkConnectivity.createInstance(store, this)
preferencesStorage = PreferencesStorage(this)
PicassoHelper.initPicassoWithCaching(this)
+ currenciesRepository = CurrenciesRepository(this)
+ initFeedbackManager()
loadConfigs()
}
-
private fun loadConfigs() {
val moshi = createMoshi()
- val localLoader = LocalLoader(this, moshi)
- val remoteLoader = RemoteLoader(moshi)
+ val localLoader = FeaturesLocalLoader(this, moshi)
+ val remoteLoader = FeaturesRemoteLoader(moshi)
val configManager = ConfigManager(localLoader, remoteLoader)
configManager.load { store.dispatch(GlobalAction.SetConfigManager(configManager)) }
+ val warningsManager = WarningMessagesManager(RemoteWarningLoader(moshi))
+ warningsManager.load { store.dispatch(GlobalAction.SetWarningManager(warningsManager)) }
+ }
+
+ private fun initFeedbackManager() {
+ val infoHolder = AdditionalEmailInfo()
+ infoHolder.updateAppVersion(this)
+
+ val logWriter = TangemLogCollector()
+ Log.addLogger(logWriter)
+
+ val feedbackManager = FeedbackManager(infoHolder, this, logWriter)
+ store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager))
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
index 932897e204..4395bd0922 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
@@ -4,4 +4,8 @@ enum class AnalyticsEvent(val event: String) {
CARD_IS_SCANNED("card_is_scanned"),
TRANSACTION_IS_SENT("transaction_is_sent"),
READY_TO_SCAN("ready_to_scan"),
+ APP_RATING_DISPLAYED("rate_app_warning_displayed"),
+ APP_RATING_DISMISS("dismiss_rate_app_warning"),
+ APP_RATING_NEGATIVE("negative_rate_app_feedback"),
+ APP_RATING_POSITIVE("positive_rate_app_feedback"),
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
index 9dddeee4f2..1dc3586808 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
@@ -4,5 +4,5 @@ import com.tangem.commands.common.card.Card
interface AnalyticsHandler {
- fun triggerEvent(event: AnalyticsEvent, card: Card?)
+ fun triggerEvent(event: AnalyticsEvent, card: Card? = null)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
index 0d49728dd6..d1d3ce7a38 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
@@ -8,8 +8,7 @@ import com.tangem.commands.common.card.Card
object FirebaseAnalyticsHandler : AnalyticsHandler {
override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
- Firebase.analytics
- .logEvent(event.event, setCardData(card))
+ Firebase.analytics.logEvent(event.event, setCardData(card))
}
fun logException(name: String, throwable: Throwable) {
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt
new file mode 100644
index 0000000000..b2905ec473
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt
@@ -0,0 +1,6 @@
+package com.tangem.tap.common.extensions
+
+import android.content.res.AssetManager
+
+fun AssetManager.readJsonFileToString(fileName: String): String =
+ this.open("$fileName.json").bufferedReader().readText()
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
new file mode 100644
index 0000000000..e03e96133b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
@@ -0,0 +1,23 @@
+package com.tangem.tap.common.extensions
+
+import androidx.annotation.DrawableRes
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.wallet.R
+
+@DrawableRes
+fun Blockchain.getIconRes(): Int {
+ return when (this) {
+ Blockchain.Unknown, Blockchain.Ducatus, Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet,
+ Blockchain.BinanceTestnet -> 1
+ Blockchain.Bitcoin -> R.drawable.ic_btc
+ Blockchain.BitcoinCash -> R.drawable.ic_btc_cash
+ Blockchain.Litecoin -> R.drawable.ic_ltc
+ Blockchain.Ethereum -> R.drawable.ic_eth
+ Blockchain.RSK -> R.drawable.ic_rsk
+ Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano
+ Blockchain.Binance -> R.drawable.ic_binance
+ Blockchain.Tezos -> R.drawable.ic_tezos
+ Blockchain.XRP -> R.drawable.ic_xrp
+ Blockchain.Stellar -> R.drawable.ic_stellar
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
index 56f0d70061..6e348ea8cf 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
@@ -12,6 +12,8 @@ import com.tangem.tap.features.details.ui.twins.TwinWalletWarningFragment
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
import com.tangem.tap.features.home.HomeFragment
import com.tangem.tap.features.send.ui.SendFragment
+import com.tangem.tap.features.tokens.ui.AddTokensFragment
+import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.features.wallet.ui.dialogs.TwinsOnboardingFragment
import com.tangem.wallet.R
@@ -46,5 +48,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.CreateTwinWalletWarning -> TwinWalletWarningFragment()
AppScreen.CreateTwinWallet -> CreateTwinWalletFragment()
AppScreen.TwinsOnboarding -> TwinsOnboardingFragment()
+ AppScreen.AddTokens -> AddTokensFragment()
+ AppScreen.WalletDetails -> WalletDetailsFragment()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
index 42fc659da8..8ed277a345 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
@@ -1,11 +1,5 @@
package com.tangem.tap.common.extensions
-import android.graphics.Bitmap
-import android.graphics.Color
-import com.google.zxing.BarcodeFormat
-import com.google.zxing.EncodeHintType
-import com.google.zxing.qrcode.QRCodeWriter
-import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.network.coinmarketcap.FiatCurrency
@@ -15,27 +9,6 @@ import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.*
-
-fun String.toQrCode(): Bitmap {
- val hintMap = Hashtable()
- hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
- hintMap[EncodeHintType.MARGIN] = 2
-
- val qrCodeWriter = QRCodeWriter()
-
- val size = 256
-
- val bitMatrix = qrCodeWriter.encode(this, BarcodeFormat.QR_CODE, size, size, hintMap)
- val width = bitMatrix.width
- val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565)
- for (x in 0 until width) {
- for (y in 0 until width) {
- bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE)
- }
- }
- return bmp
-}
-
fun BigDecimal.toFormattedString(
decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US
): String {
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt
new file mode 100644
index 0000000000..f0f4ef1d00
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt
@@ -0,0 +1,62 @@
+package com.tangem.tap.common.extensions
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.text.Spannable
+import android.text.style.ForegroundColorSpan
+import androidx.core.content.ContextCompat
+import androidx.core.text.toSpannable
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.EncodeHintType
+import com.google.zxing.qrcode.QRCodeWriter
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
+import java.util.*
+
+fun String?.ellipsizeBeforeSpace(allowedSize: Int): String {
+ if (this.isNullOrBlank()) return ""
+ val size = this.length
+ val sizeDifference = size - allowedSize
+ val endIndex = this.indexOf(" ")
+ val startIndex = endIndex - sizeDifference
+ val newString = this.removeRange(startIndex, endIndex)
+ return newString.substring(0 until startIndex) + "..." +
+ newString.substring(startIndex until newString.length)
+}
+
+fun String.colorSegment(
+ context: Context,
+ color: Int,
+ startIndex: Int = 0,
+ endIndex: Int = this.length
+): Spannable {
+ return this.toSpannable()
+ .also { spannable ->
+ spannable.setSpan(
+ ForegroundColorSpan(ContextCompat.getColor(context, color)),
+ startIndex,
+ endIndex,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ }
+}
+
+fun String.toQrCode(): Bitmap {
+ val hintMap = Hashtable()
+ hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
+ hintMap[EncodeHintType.MARGIN] = 2
+
+ val qrCodeWriter = QRCodeWriter()
+
+ val size = 256
+
+ val bitMatrix = qrCodeWriter.encode(this, BarcodeFormat.QR_CODE, size, size, hintMap)
+ val width = bitMatrix.width
+ val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565)
+ for (x in 0 until width) {
+ for (y in 0 until width) {
+ bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE)
+ }
+ }
+ return bmp
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
index 5774e94b61..48ee67afc7 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
@@ -56,4 +56,13 @@ fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) {
error = null
isErrorEnabled = false
}
+}
+
+fun TextView.isEllipsized(): Boolean {
+ val layout = this.layout
+ if (layout != null) {
+ val lines: Int = layout.lineCount
+ return (lines > 0) && (layout.getEllipsisCount(lines - 1) > 0)
+ }
+ return false
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt
new file mode 100644
index 0000000000..f9499f8e70
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt
@@ -0,0 +1,11 @@
+package com.tangem.tap.common.extensions
+
+import androidx.annotation.ColorInt
+import androidx.core.graphics.toColorInt
+import com.tangem.blockchain.common.Token
+
+@ColorInt
+fun Token.getColor(): Int {
+ return ("#" + this.contractAddress.subSequence(2..7).toString())
+ .toColorInt()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
index c7bb0daf0f..b43c49b307 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
@@ -1,15 +1,10 @@
package com.tangem.tap.common.extensions
import android.app.Activity
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.content.Intent
+import android.content.*
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.os.Build
-import android.text.Spannable
-import android.text.style.ForegroundColorSpan
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
@@ -19,7 +14,6 @@ import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
-import androidx.core.text.toSpannable
import androidx.fragment.app.Fragment
import com.google.android.material.card.MaterialCardView
@@ -77,6 +71,9 @@ fun Context.dpToPixels(dp: Int): Int =
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics
).toInt()
+tailrec fun Context?.getActivity(): Activity? = this as? Activity ?:
+ (this as? ContextWrapper)?.baseContext?.getActivity()
+
fun MaterialCardView.setMargins(
marginLeftDp: Int = 16,
marginTopDp: Int = 8,
@@ -106,24 +103,6 @@ fun Activity.setSystemBarTextColor(setTextDark: Boolean) {
}
}
-
-fun String.colorSegment(
- context: Context,
- color: Int,
- startIndex: Int = 0,
- endIndex: Int = this.length
-): Spannable {
- return this.toSpannable()
- .also { spannable ->
- spannable.setSpan(
- ForegroundColorSpan(ContextCompat.getColor(context, color)),
- startIndex,
- endIndex,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- }
-}
-
fun View.hideKeyboard() {
val inputMethodManager = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? InputMethodManager
inputMethodManager?.hideSoftInputFromWindow(this.windowToken, 0)
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
index 5e80adec68..03dcb4a441 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
@@ -6,7 +6,8 @@ import com.tangem.tap.features.details.redux.DetailsReducer
import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
import com.tangem.tap.features.home.redux.HomeReducer
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
-import com.tangem.tap.features.wallet.redux.WalletReducer
+import com.tangem.tap.features.tokens.redux.TokensReducer
+import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
import org.rekotlin.Action
fun appReducer(action: Action, state: AppState?): AppState {
@@ -20,7 +21,8 @@ fun appReducer(action: Action, state: AppState?): AppState {
walletState = WalletReducer.reduce(action, state),
sendState = SendScreenReducer.reduce(action, state.sendState),
detailsState = DetailsReducer.reduce(action, state),
- disclaimerState = DisclaimerReducer.reduce(action, state)
+ disclaimerState = DisclaimerReducer.reduce(action, state),
+ tokensState = TokensReducer.reduce(action, state),
)
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
index e60ae97fa7..4548ca046f 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
@@ -12,8 +12,10 @@ import com.tangem.tap.features.home.redux.HomeMiddleware
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.send.redux.middlewares.sendMiddleware
import com.tangem.tap.features.send.redux.states.SendState
-import com.tangem.tap.features.wallet.redux.WalletMiddleware
+import com.tangem.tap.features.tokens.redux.TokensMiddleware
+import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
import org.rekotlin.Middleware
import org.rekotlin.StateType
@@ -24,7 +26,8 @@ data class AppState(
val walletState: WalletState = WalletState(),
val sendState: SendState = SendState(),
val detailsState: DetailsState = DetailsState(),
- val disclaimerState: DisclaimerState = DisclaimerState()
+ val disclaimerState: DisclaimerState = DisclaimerState(),
+ val tokensState: TokensState = TokensState(),
) : StateType {
companion object {
@@ -35,7 +38,8 @@ data class AppState(
WalletMiddleware().walletMiddleware,
sendMiddleware,
DetailsMiddleware().detailsMiddleware,
- DisclaimerMiddleware().disclaimerMiddleware
+ DisclaimerMiddleware().disclaimerMiddleware,
+ TokensMiddleware().tokensMiddleware,
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
index fc4f5bba9e..45bc0c2707 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
@@ -1,22 +1,28 @@
package com.tangem.tap.common.redux.global
-import com.tangem.tap.domain.config.ConfigManager
+import com.tangem.tap.domain.configurable.config.ConfigManager
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.features.details.redux.SecurityOption
+import com.tangem.tap.features.feedback.EmailData
+import com.tangem.tap.features.feedback.FeedbackManager
import org.rekotlin.Action
-import java.math.BigDecimal
sealed class GlobalAction : Action {
data class SaveScanNoteResponse(val scanNoteResponse: ScanNoteResponse) : GlobalAction()
- data class SetFiatRate(
- val fiatRates: Pair
- ) : GlobalAction()
data class ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction()
object RestoreAppCurrency : GlobalAction() {
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()
}
data class UpdateWalletSignedHashes(val walletSignedHashes: Int?) : GlobalAction()
- data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
+ data class HideWarningMessage(val warning: WarningMessage) : GlobalAction()
data class UpdateSecurityOptions(val securityOption: SecurityOption) : GlobalAction()
+
+ data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
+ data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
+ data class SetFeedbackManager(val feedbackManager: FeedbackManager): GlobalAction()
+
+ data class SendFeedback(val emailData: EmailData): GlobalAction()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
index d7ec41960f..0c8de5dc8d 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
@@ -1,6 +1,10 @@
package com.tangem.tap.common.redux.global
import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
+import com.tangem.tap.features.send.redux.SendAction
+import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.preferencesStorage
import com.tangem.tap.store
import org.rekotlin.Middleware
@@ -14,6 +18,25 @@ val globalMiddleware: Middleware = { dispatch, appState ->
preferencesStorage.getAppCurrency()
))
}
+ is GlobalAction.HideWarningMessage -> {
+ store.state.globalState.warningManager?.let {
+ if (it.hideWarning(action.warning)) {
+ if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
+ //TODO: No appropriate warningMessage identification. Make it better later
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
+ }
+
+ store.dispatch(WalletAction.Warnings.SetWarnings(
+ it.getWarnings(WarningMessage.Location.MainScreen)))
+ store.dispatch(SendAction.SetWarnings(
+ it.getWarnings(WarningMessage.Location.SendScreen)))
+ }
+ }
+ }
+
+ is GlobalAction.SendFeedback -> {
+ store.state.globalState.feedbackManager?.send(action.emailData)
+ }
}
nextDispatch(action)
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
index 7ab9334181..8df35e1064 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
@@ -13,16 +13,11 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
return when (action) {
is GlobalAction.SaveScanNoteResponse ->
globalState.copy(scanNoteResponse = action.scanNoteResponse)
- is GlobalAction.SetFiatRate -> {
- val rates = globalState.conversionRates.rates.toMutableMap()
- rates[action.fiatRates.first] = action.fiatRates.second
- globalState.copy(conversionRates = ConversionRates(rates))
- }
is GlobalAction.ChangeAppCurrency -> {
- globalState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
+ globalState.copy(appCurrency = action.appCurrency)
}
is GlobalAction.RestoreAppCurrency.Success -> {
- globalState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
+ globalState.copy(appCurrency = action.appCurrency)
}
is GlobalAction.UpdateWalletSignedHashes -> {
val card = globalState.scanNoteResponse?.card?.copy(
@@ -37,6 +32,8 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.SetConfigManager -> {
globalState.copy(configManager = action.configManager)
}
+ is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
+ is GlobalAction.HideWarningMessage -> globalState
is GlobalAction.UpdateSecurityOptions -> {
val card = when (action.securityOption) {
SecurityOption.LongTap -> globalState.scanNoteResponse?.card?.copy(
@@ -55,6 +52,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
globalState
}
}
+ is GlobalAction.SetFeedbackManager -> {
+ globalState.copy(feedbackManager = action.feedbackManager)
+ }
else -> globalState
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
index f9e05cdeac..52340df26d 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
@@ -4,11 +4,12 @@ import com.tangem.commands.common.network.TangemService
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapWalletManager
-import com.tangem.tap.domain.config.ConfigManager
+import com.tangem.tap.domain.configurable.config.ConfigManager
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tasks.ScanNoteResponse
+import com.tangem.tap.features.feedback.FeedbackManager
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
import org.rekotlin.StateType
-import java.math.BigDecimal
data class GlobalState(
val scanNoteResponse: ScanNoteResponse? = null,
@@ -16,20 +17,12 @@ data class GlobalState(
val payIdManager: PayIdManager = PayIdManager(),
val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(),
val tangemService: TangemService = TangemService(),
- val conversionRates: ConversionRates = ConversionRates(emptyMap()),
val configManager: ConfigManager? = null,
+ val warningManager: WarningMessagesManager? = null,
+ val feedbackManager: FeedbackManager? = null,
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY
) : StateType
-data class ConversionRates(
- val rates: Map,
-) {
-
- fun getRate(cryptoCurrency: CryptoCurrencyName): BigDecimal? {
- return rates[cryptoCurrency]
- }
-}
-
typealias CryptoCurrencyName = String
typealias FiatCurrencyName = String
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
index 393a4e7e77..26344b0175 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
@@ -10,6 +10,6 @@ data class NavigationState(
) : StateType
enum class AppScreen {
- Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer,
- CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding
+ Home, Wallet, WalletDetails, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer,
+ CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding, AddTokens
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
index 3773bd41b7..a6e329ca04 100644
--- a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
@@ -2,46 +2,13 @@ package com.tangem.tap.domain
import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.network.Result
-import com.tangem.tap.network.payid.PayIdService
import com.tangem.tap.network.payid.PayIdVerifyService
-import com.tangem.tap.network.payid.SetPayIdResponse
import com.tangem.tap.network.payid.VerifyPayIdResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-import retrofit2.HttpException
import java.util.*
class PayIdManager {
- private val payIdService = PayIdService()
-
- suspend fun getPayId(cardId: String, publicKey: String): Result = withContext(Dispatchers.IO) {
- val result = payIdService.getPayId(cardId, publicKey)
- when (result) {
- is Result.Success -> return@withContext Result.Success(result.data.payId)
- is Result.Failure -> {
- (result.error as? HttpException)?.let {
- if (it.code() == 404) return@withContext Result.Success(null)
- }
- return@withContext result
- }
- }
- }
-
-
- suspend fun setPayId(
- cardId: String, publicKey: String, payId: String, address: String, blockchain: Blockchain
- ): Result = withContext(Dispatchers.IO) {
- val result = payIdService.setPayId(cardId, publicKey, payId, address, blockchain.getPayIdNetwork())
- when (result) {
- is Result.Success -> return@withContext result
- is Result.Failure -> {
- (result.error as? HttpException)?.let {
- if (it.code() == 409) return@withContext Result.Failure(TapError.PayIdAlreadyCreated)
- }
- return@withContext result
- }
- }
- }
suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result = withContext(Dispatchers.IO) {
val splitPayId = payId.split("\$")
diff --git a/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt b/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt
new file mode 100644
index 0000000000..b9ec4705a5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt
@@ -0,0 +1,13 @@
+package com.tangem.tap.domain
+
+class TapBlockchainConfig {
+ companion object {
+ val blockchairApiKey: String = "A___0Shpsu4KagE7oSabrw20DfXAqWlT"
+ val blockcypherTokens: Set = setOf(
+ "aa8184b0e0894b88a5688e01b3dc1e82",
+ "56c4ca23c6484c8f8864c32fde4def8d",
+ "66a8a37c5e9d4d2c9bb191acfe7f93aa"
+ )
+ val infuraProjectId: String = "613a0b14833145968b1f656240c7d245"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
index 37f159fdb8..521ed462b4 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
@@ -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)
diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
index 36dd3c238c..0d6662d4f0 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
@@ -1,23 +1,21 @@
package com.tangem.tap.domain
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.Token
-import com.tangem.blockchain.common.Wallet
-import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.common.*
import com.tangem.commands.common.card.CardStatus
import com.tangem.commands.common.network.Result
-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.redux.global.CryptoCurrencyName
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.domain.config.ConfigManager
+import com.tangem.tap.currenciesRepository
+import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.extensions.isNoAccountError
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.domain.twins.TwinsHelper
import com.tangem.tap.domain.twins.isTwinCard
+import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
@@ -28,29 +26,25 @@ import java.math.BigDecimal
class TapWalletManager {
- private val payIdManager = PayIdManager()
private val coinMarketCapService = CoinMarketCapService()
- suspend fun loadWalletData() {
- val walletManager = store.state.globalState.scanNoteResponse?.walletManager
- if (walletManager == null) {
- store.dispatch(WalletAction.LoadWallet.Failure())
- return
+ private val blockchainSdkConfig by lazy {
+ store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
+ }
+ private val walletManagerFactory: WalletManagerFactory
+ by lazy { WalletManagerFactory(blockchainSdkConfig) }
+
+ suspend fun loadWalletData(walletManager: WalletManager) {
+ val result = try {
+ walletManager.update()
+ Result.Success(walletManager.wallet)
+ } catch (exception: Exception) {
+ Result.Failure(exception)
}
- loadWallet(walletManager)
+ handleUpdateWalletResult(result, walletManager)
}
- suspend fun loadPayId() {
- val result = loadPayIdIfNeeded()
- result?.let { handlePayIdResult(it) }
- }
-
- suspend fun updateWallet() {
- val walletManager = store.state.globalState.scanNoteResponse?.walletManager
- if (walletManager == null) {
- withContext(Dispatchers.Main) { store.dispatch(WalletAction.UpdateWallet.Failure()) }
- return
- }
+ suspend fun updateWallet(walletManager: WalletManager) {
val result = try {
walletManager.update()
Result.Success(walletManager.wallet)
@@ -68,12 +62,18 @@ class TapWalletManager {
}
- suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName) {
- val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet ?: return
-
+ suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
val currencyList = wallet.getTokens().map { it.symbol }.toMutableList()
currencyList.add(wallet.blockchain.currency)
+ loadFiatRate(fiatCurrency, currencyList)
+ }
+ suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, cryptoCurrencyName: CryptoCurrencyName) {
+ val currencyList = listOf(cryptoCurrencyName)
+ loadFiatRate(fiatCurrency, currencyList)
+ }
+
+ private suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, currencyList: List) {
val results = mutableListOf?>>()
currencyList.forEach { results.add(it to coinMarketCapService.getRate(it, fiatCurrency)) }
handleFiatRatesResult(results)
@@ -84,24 +84,14 @@ class TapWalletManager {
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card)
}
TapWorkarounds.updateCard(data.card)
- val configManager = store.state.globalState.configManager
- if (TapWorkarounds.isStart2Coin) {
- configManager?.turnOff(ConfigManager.isWalletPayIdEnabled)
- configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
- configManager?.turnOff(ConfigManager.isTopUpEnabled)
- } else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin
- || data.card.cardData?.blockchainName == Blockchain.Bitcoin.id){
- configManager?.turnOff(ConfigManager.isWalletPayIdEnabled)
- configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
- configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
- } else {
- configManager?.resetToDefault(ConfigManager.isWalletPayIdEnabled)
- configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
- configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
- }
+ store.state.globalState.warningManager?.setBlockchain(data.walletManager?.wallet?.blockchain)
+ updateConfigManager(data)
+ updateFeedbackManager(data)
+
withContext(Dispatchers.Main) {
store.dispatch(WalletAction.ResetState)
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
+ store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
if (data.card.isTwinCard()) {
val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId)
val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId)
@@ -111,13 +101,45 @@ class TapWalletManager {
))
}
}
+ if (data.walletManager?.wallet?.blockchain == Blockchain.Ethereum ||
+ data.walletManager?.wallet?.blockchain == Blockchain.EthereumTestnet) {
+ store.dispatch(TokensAction.LoadCardTokens)
+ }
loadData(data)
}
}
+ 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.CheckIfWarningNeeded)
+ store.dispatch(WalletAction.Warnings.CheckIfNeeded)
val artworkId = data.verifyResponse?.artworkInfo?.id
if (data.walletManager != null) {
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
@@ -126,11 +148,42 @@ class TapWalletManager {
}
val config = store.state.globalState.configManager?.config ?: return@withContext
- store.dispatch(WalletAction.LoadWallet(
- data.walletManager.wallet, data.verifyResponse?.artworkInfo?.id, config.isTopUpEnabled))
+ val primaryWalletManager = data.walletManager
+ val primaryBlockchain = listOf(data.walletManager.wallet.blockchain)
+ val primaryTokenSymbol = data.card.cardData?.tokenSymbol
+ val primaryToken = primaryWalletManager.presetTokens.toList()
+ .firstOrNull { it.symbol == primaryTokenSymbol }
+
+ store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(primaryBlockchain[0]))
+ if (primaryToken != null) {
+ store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
+ }
+
+ if (data.card.isMultiwalletAllowed) {
+ val savedCurrencies = currenciesRepository.loadCardCurrencies(data.card.cardId)
+
+ val walletManagers = listOf(primaryWalletManager) +
+ currenciesRepository.getBlockchains()
+ .filterNot { it == primaryWalletManager.wallet.blockchain }
+ .mapNotNull { walletManagerFactory.makeWalletManager(data.card, it) }
+ val otherBlockhains = savedCurrencies.blockchains
+
+ val activeTokens = walletManagers.first { it.wallet.blockchain == Blockchain.Ethereum}
+ .presetTokens.toList()
+ val tokens = activeTokens + savedCurrencies.tokens
+
+ store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))
+ store.dispatch(WalletAction.MultiWallet.AddBlockchains(primaryBlockchain + otherBlockhains))
+ store.dispatch(WalletAction.MultiWallet.AddTokens(tokens))
+ store.dispatch(WalletAction.MultiWallet.FindTokensInUse)
+ } else {
+ store.dispatch(WalletAction.MultiWallet.AddWalletManagers(listOf(primaryWalletManager)))
+ store.dispatch(WalletAction.MultiWallet.AddBlockchains(primaryBlockchain))
+ }
+ store.dispatch(WalletAction.SetArtworkId(data.verifyResponse?.artworkInfo?.id))
+ store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
- store.dispatch(WalletAction.LoadFiatRate)
- store.dispatch(WalletAction.LoadPayId)
+ store.dispatch(WalletAction.LoadFiatRate())
} else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) {
store.dispatch(WalletAction.EmptyWallet)
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
@@ -141,14 +194,22 @@ class TapWalletManager {
}
}
- private suspend fun loadWallet(walletManager: WalletManager) {
- val result = try {
- walletManager.update()
- Result.Success(walletManager.wallet)
- } catch (exception: Exception) {
- Result.Failure(exception)
+ suspend fun reloadData(data: ScanNoteResponse) {
+ withContext(Dispatchers.Main) {
+ if (data.walletManager != null) {
+ if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
+ store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
+ return@withContext
+ }
+ val config = store.state.globalState.configManager?.config ?: return@withContext
+ store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
+ store.dispatch(WalletAction.LoadFiatRate())
+ } else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) {
+ store.dispatch(WalletAction.EmptyWallet)
+ } else {
+ store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
+ }
}
- handleUpdateWalletResult(result, walletManager)
}
private suspend fun handleUpdateWalletResult(result: Result, walletManager: WalletManager) {
@@ -162,59 +223,24 @@ class TapWalletManager {
val token = walletManager.wallet.getFirstToken()
val amountToCreateAccount = blockchain.amountToCreateAccount(token)
if (amountToCreateAccount != null) {
- store.dispatch(WalletAction.LoadWallet.NoAccount(amountToCreateAccount.toString()))
+ store.dispatch(WalletAction.LoadWallet.NoAccount(
+ walletManager.wallet,
+ amountToCreateAccount.toString()))
return@withContext
}
}
- store.dispatch(WalletAction.LoadWallet.Failure(result.error?.localizedMessage))
+ store.dispatch(WalletAction.LoadWallet.Failure(walletManager.wallet, result.error?.localizedMessage))
}
}
}
}
-
- private suspend fun loadPayIdIfNeeded(): Result? {
- val scanNoteResponse = store.state.globalState.scanNoteResponse
- if (store.state.globalState.configManager?.config?.isWalletPayIdEnabled == false
- || scanNoteResponse?.walletManager?.wallet?.blockchain?.isPayIdSupported() == false) {
- return null
- }
- val cardId = scanNoteResponse?.card?.cardId
- val publicKey = scanNoteResponse?.card?.cardPublicKey
- if (cardId == null || publicKey == null) {
- return null
- }
- return payIdManager.getPayId(cardId, publicKey.toHexString())
- }
-
- private suspend fun handlePayIdResult(result: Result) {
- withContext(Dispatchers.Main) {
- when (result) {
- is Result.Success -> {
- val config = store.state.globalState.configManager?.config
- if (config?.isWalletPayIdEnabled == false) {
- store.dispatch(WalletAction.DisablePayId)
- return@withContext
- }
- val payId = result.data
- if (payId == null) {
- store.dispatch(WalletAction.LoadPayId.NotCreated)
- } else {
- store.dispatch(WalletAction.LoadPayId.Success(payId))
- }
- }
- is Result.Failure -> store.dispatch(WalletAction.LoadPayId.Failure)
- }
- }
- }
-
private suspend fun handleFiatRatesResult(results: List?>>) {
withContext(Dispatchers.Main) {
results.map {
when (it.second) {
is Result.Success -> {
val rate = it.first to (it.second as Result.Success).data
- store.dispatch(GlobalAction.SetFiatRate(rate))
store.dispatch(WalletAction.LoadFiatRate.Success(rate))
}
is Result.Failure -> store.dispatch(WalletAction.LoadFiatRate.Failure)
diff --git a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
index d0cc8b64b2..48526204e8 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.domain
import com.tangem.commands.common.card.Card
+import com.tangem.commands.common.card.EllipticCurve
import com.tangem.commands.common.card.masks.Product
import java.util.*
@@ -17,7 +18,7 @@ object TapWorkarounds {
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 excludedIssuerName = excludedIssuers.contains(cardData.issuerName?.toUpperCase(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
@@ -35,4 +36,11 @@ object TapWorkarounds {
private val excludedIssuers = listOf(
"TTM BANK"
)
-}
\ No newline at end of file
+}
+
+val Card.isMultiwalletAllowed: Boolean
+ get() {
+ return cardData?.productMask?.contains(Product.TwinCard) != true
+ && !TapWorkarounds.isStart2Coin
+ && this.curve == EllipticCurve.Secp256k1
+ }
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt b/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt
new file mode 100644
index 0000000000..e0d0a6a52c
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt
@@ -0,0 +1,16 @@
+package com.tangem.tap.domain.configurable
+
+import com.tangem.wallet.BuildConfig
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface Loader {
+ fun load(onComplete: (T) -> Unit)
+
+ companion object {
+ const val featuresName = "features_${BuildConfig.CONFIG_ENVIRONMENT}"
+ const val configValuesName = "config_${BuildConfig.CONFIG_ENVIRONMENT}"
+ const val warnings = "warnings_${BuildConfig.CONFIG_ENVIRONMENT}"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt
similarity index 58%
rename from app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt
rename to app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt
index 2e90f4cb16..a47e8fec1c 100644
--- a/app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt
@@ -1,52 +1,29 @@
-package com.tangem.tap.domain.config
+package com.tangem.tap.domain.configurable.config
import android.content.Context
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
-import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
-import com.tangem.wallet.BuildConfig
+import com.tangem.tap.domain.configurable.Loader
import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
-interface ConfigLoader {
- fun loadConfig(onComplete: (ConfigModel) -> Unit)
-
- companion object {
- const val featuresName = "features_${BuildConfig.CONFIG_ENVIRONMENT}"
- const val configValuesName = "config_${BuildConfig.CONFIG_ENVIRONMENT}"
-
- fun init() {
- if (BuildConfig.CONFIG_ENVIRONMENT == "prod") {
- Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
- this.minimumFetchIntervalInSeconds = 1200
- })
- } else {
- Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
- this.minimumFetchIntervalInSeconds = 60
- })
- }
- }
- }
-}
-
-
-class LocalLoader(
+class FeaturesLocalLoader(
private val context: Context,
private val moshi: Moshi,
-) : ConfigLoader {
+) : Loader {
- override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
+ override fun load(onComplete: (ConfigModel) -> Unit) {
val config = try {
val featureAdapter: JsonAdapter = moshi.adapter(FeatureModel::class.java)
val valuesAdapter: JsonAdapter = moshi.adapter(ConfigValueModel::class.java)
- val jsonFeatures = readAssetAsString(ConfigLoader.featuresName)
- val jsonConfigValues = readAssetAsString(ConfigLoader.configValuesName)
+ val jsonFeatures = readAssetAsString(Loader.featuresName)
+ val jsonConfigValues = readAssetAsString(Loader.configValuesName)
ConfigModel(featureAdapter.fromJson(jsonFeatures), valuesAdapter.fromJson(jsonConfigValues))
} catch (ex: Exception) {
@@ -61,16 +38,16 @@ class LocalLoader(
}
}
-class RemoteLoader(
+class FeaturesRemoteLoader(
private val moshi: Moshi,
-) : ConfigLoader {
+) : Loader {
- override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
+ override fun load(onComplete: (ConfigModel) -> Unit) {
val emptyConfig = ConfigModel.empty()
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener {
if (it.isSuccessful) {
- val config = remoteConfig.getValue(ConfigLoader.featuresName)
+ val config = remoteConfig.getValue(Loader.featuresName)
val jsonConfig = config.asString()
if (jsonConfig.isEmpty()) {
onComplete(emptyConfig)
@@ -82,7 +59,7 @@ class RemoteLoader(
onComplete(emptyConfig)
}
}.addOnFailureListener {
- FirebaseAnalyticsHandler.logException("remote_config_error", it)
+ FirebaseAnalyticsHandler.logException("remote_config_error.features", it)
onComplete(emptyConfig)
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
similarity index 75%
rename from app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt
rename to app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
index c7acd653c7..b1ccd85929 100644
--- a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
@@ -1,6 +1,8 @@
-package com.tangem.tap.domain.config
+package com.tangem.tap.domain.configurable.config
+import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
+import com.tangem.tap.domain.configurable.Loader
/**
[REDACTED_AUTHOR]
@@ -9,15 +11,15 @@ data class Config(
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
- val isWalletPayIdEnabled: Boolean = true,
+ val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
val isSendingToPayIdEnabled: Boolean = true,
val isTopUpEnabled: Boolean = false,
val isCreatingTwinCardsAllowed: Boolean = false
)
class ConfigManager(
- private val localLoader: ConfigLoader,
- private val remoteLoader: ConfigLoader
+ private val localLoader: Loader,
+ private val remoteLoader: Loader
) {
var config: Config = Config()
@@ -26,11 +28,11 @@ class ConfigManager(
private var defaultConfig = Config()
fun load(onComplete: VoidCallback? = null) {
- localLoader.loadConfig { config ->
+ localLoader.load { config ->
setupFeature(config.features)
setupKey(config.configValues)
}
- remoteLoader.loadConfig { config ->
+ remoteLoader.load { config ->
setupFeature(config.features)
onComplete?.invoke()
}
@@ -38,7 +40,6 @@ class ConfigManager(
fun turnOff(name: String) {
when (name) {
- isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = false)
isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = false)
isTopUpEnabled -> config = config.copy(isTopUpEnabled = false)
isCreatingTwinCardsAllowed -> config = config.copy(isCreatingTwinCardsAllowed = false)
@@ -47,7 +48,6 @@ class ConfigManager(
fun resetToDefault(name: String) {
when (name) {
- isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = defaultConfig.isWalletPayIdEnabled)
isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
isCreatingTwinCardsAllowed -> config =
@@ -59,13 +59,11 @@ class ConfigManager(
val model = featureModel ?: return
config = config.copy(
- isWalletPayIdEnabled = model.isWalletPayIdEnabled,
isTopUpEnabled = model.isTopUpEnabled,
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
)
defaultConfig = defaultConfig.copy(
- isWalletPayIdEnabled = model.isWalletPayIdEnabled,
isTopUpEnabled = model.isTopUpEnabled,
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
@@ -77,22 +75,28 @@ class ConfigManager(
config = config.copy(
coinMarketCapKey = values.coinMarketCapKey,
moonPayApiKey = values.moonPayApiKey,
- moonPayApiSecretKey = values.moonPayApiSecretKey
+ moonPayApiSecretKey = values.moonPayApiSecretKey,
+ blockchainSdkConfig = BlockchainSdkConfig(
+ blockchairApiKey = values.blockchairApiKey,
+ blockcypherTokens = values.blockcypherTokens,
+ infuraProjectId = values.infuraProjectId
+ )
)
defaultConfig = defaultConfig.copy(
coinMarketCapKey = values.coinMarketCapKey,
moonPayApiKey = values.moonPayApiKey,
- moonPayApiSecretKey = values.moonPayApiSecretKey
+ moonPayApiSecretKey = values.moonPayApiSecretKey,
+ blockchainSdkConfig = BlockchainSdkConfig(
+ blockchairApiKey = values.blockchairApiKey,
+ blockcypherTokens = values.blockcypherTokens,
+ infuraProjectId = values.infuraProjectId
+ )
)
}
companion object {
- const val isWalletPayIdEnabled = "isWalletPayIdEnabled"
const val isSendingToPayIdEnabled = "isSendingToPayIdEnabled"
const val isCreatingTwinCardsAllowed = "isCreatingTwinCardsAllowed"
const val isTopUpEnabled = "isTopUpEnabled"
- const val coinMarketCapKey = "coinMarketCapKey"
- const val moonPayApiKey = "moonPayApiKey"
- const val moonPayApiSecretKey = "moonPayApiSecretKey"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
similarity index 75%
rename from app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt
rename to app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
index 4724f8b2c3..69c5f0a8b0 100644
--- a/app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
@@ -1,4 +1,4 @@
-package com.tangem.tap.domain.config
+package com.tangem.tap.domain.configurable.config
/**
[REDACTED_AUTHOR]
@@ -15,6 +15,9 @@ class ConfigValueModel(
val coinMarketCapKey: String,
val moonPayApiKey: String,
val moonPayApiSecretKey: String,
+ val blockchairApiKey: String?,
+ val blockcypherTokens: Set?,
+ val infuraProjectId: String?,
)
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningLoader.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningLoader.kt
new file mode 100644
index 0000000000..c95fc39a24
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningLoader.kt
@@ -0,0 +1,52 @@
+package com.tangem.tap.domain.configurable.warningMessage
+
+import com.google.firebase.ktx.Firebase
+import com.google.firebase.remoteconfig.ktx.remoteConfig
+import com.squareup.moshi.JsonAdapter
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.Types
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.domain.configurable.Loader
+
+/**
+[REDACTED_AUTHOR]
+ */
+class RemoteWarningLoader(
+ private val moshi: Moshi,
+) : Loader> {
+
+ override fun load(onComplete: (List) -> Unit) {
+ val emptyConfig = listOf()
+ val remoteConfig = Firebase.remoteConfig
+ remoteConfig.fetchAndActivate().addOnCompleteListener {
+ if (!it.isSuccessful) {
+ onComplete(emptyConfig)
+ return@addOnCompleteListener
+ }
+
+ val config = remoteConfig.getValue(Loader.warnings)
+ val jsonConfig = config.asString()
+ if (jsonConfig.isEmpty()) {
+ onComplete(emptyConfig)
+ return@addOnCompleteListener
+ }
+
+ val adapterType = Types.newParameterizedType(List::class.java, WarningMessage::class.java)
+ val warningsAdapter: JsonAdapter> = moshi.adapter(adapterType)
+ try {
+ val warnings = warningsAdapter.fromJson(jsonConfig) ?: listOf()
+ onComplete(warnings)
+ } catch (ex: Exception) {
+ handleError(ex)
+ onComplete(emptyConfig)
+ }
+ }.addOnFailureListener {
+ handleError(it)
+ onComplete(emptyConfig)
+ }
+ }
+
+ private fun handleError(ex: Exception) {
+ FirebaseAnalyticsHandler.logException("remote_config_error.warnings", ex)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt
new file mode 100644
index 0000000000..ab94c0d563
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt
@@ -0,0 +1,59 @@
+package com.tangem.tap.domain.configurable.warningMessage
+
+import com.squareup.moshi.Json
+import com.tangem.blockchain.common.Blockchain
+
+/**
+[REDACTED_AUTHOR]
+ */
+data class WarningMessage(
+ val title: String,
+ val message: String,
+ val type: Type,
+ val priority: Priority,
+ val location: List,
+ private val blockchains: List?,
+ val titleResId: Int? = null,
+ val messageResId: Int? = null,
+ val origin: Origin = Origin.Remote,
+) {
+ val blockchainList: List? by lazy {
+ blockchains?.map { Blockchain.fromId(it.toUpperCase()) }
+ }
+
+
+ var isHidden = false
+
+ enum class Priority {
+ @Json(name = "critical")
+ Critical,
+
+ @Json(name = "warning")
+ Warning,
+
+ @Json(name = "info")
+ Info
+ }
+
+ enum class Type {
+ @Json(name = "permanent")
+ Permanent, // нельзя скрыть
+
+ @Json(name = "temporary")
+ Temporary, // можно скрыть (кнопка ОК)
+
+ AppRating
+ }
+
+ enum class Location {
+ @Json(name = "main")
+ MainScreen,
+
+ @Json(name = "send")
+ SendScreen
+ }
+
+ enum class Origin {
+ Local, Remote
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
new file mode 100644
index 0000000000..1ee151033f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
@@ -0,0 +1,121 @@
+package com.tangem.tap.domain.configurable.warningMessage
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+class WarningMessagesManager(
+ private val warningLoader: RemoteWarningLoader,
+) {
+
+ private var blockchain: Blockchain? = null
+ private val warningsList: MutableList = mutableListOf()
+
+ fun load(onComplete: VoidCallback? = null) {
+ warningLoader.load { remoteList ->
+ warningsList.clear()
+ warningsList.addAll(remoteList)
+ sortByPriority()
+ onComplete?.invoke()
+ }
+ }
+
+ fun setBlockchain(blockchain: Blockchain?) {
+ this.blockchain = blockchain
+ }
+
+ fun addWarning(warning: WarningMessage) {
+ if (findWarning(warning) == null) {
+ warningsList.add(warning)
+ sortByPriority()
+ }
+ }
+
+ fun getWarnings(location: WarningMessage.Location): List {
+ return warningsList
+ .filter { !it.isHidden && it.location.contains(location) }
+ .filter {
+ val blockchainList = it.blockchainList
+ when {
+ blockchainList == null -> true
+ blockchainList.contains(blockchain) -> true
+ else -> false
+ }
+ }
+ }
+
+ fun hideWarning(warning: WarningMessage): Boolean {
+ val foundWarning = findWarning(warning)
+ return when {
+ foundWarning == null -> false
+ foundWarning.type == WarningMessage.Type.Temporary
+ || foundWarning.type == WarningMessage.Type.AppRating -> {
+ if (foundWarning.isHidden) {
+ false
+ } else {
+ foundWarning.isHidden = true
+ true
+ }
+ }
+ else -> false
+ }
+ }
+
+ fun removeWarnings(origin: WarningMessage.Origin) {
+ warningsList.removeIf { it.origin == origin }
+ sortByPriority()
+ }
+
+ private fun sortByPriority() {
+ warningsList.sortBy { it.priority.ordinal }
+ }
+
+ private fun findWarning(warning: WarningMessage): WarningMessage? {
+ return warningsList.firstOrNull { it == warning }
+ }
+
+ companion object {
+ fun devCardWarning(): WarningMessage = WarningMessage(
+ "",
+ "",
+ type = WarningMessage.Type.Permanent,
+ priority = WarningMessage.Priority.Critical,
+ listOf(WarningMessage.Location.MainScreen),
+ null,
+ R.string.alert_title,
+ R.string.alert_developer_card,
+ WarningMessage.Origin.Local
+ )
+
+ fun alreadySignedHashesWarning(): WarningMessage = WarningMessage(
+ "",
+ "",
+ type = WarningMessage.Type.Temporary,
+ priority = WarningMessage.Priority.Info,
+ listOf(WarningMessage.Location.MainScreen),
+ null,
+ R.string.alert_title,
+ R.string.alert_card_signed_transactions,
+ WarningMessage.Origin.Local
+ )
+
+ fun 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
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt
index c0e33bb929..e2ae2aef19 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt
@@ -4,6 +4,7 @@ import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.TangemError
import com.tangem.TangemSdkError
+import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.commands.CommandResponse
@@ -18,18 +19,23 @@ import com.tangem.tap.domain.TapSdkError
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
import com.tangem.tasks.ScanTask
data class ScanNoteResponse(
val walletManager: WalletManager?,
val card: Card,
val verifyResponse: VerifyCardResponse? = null,
- val secondTwinPublicKey: String? = null
+ val secondTwinPublicKey: String? = null,
) : CommandResponse
class ScanNoteTask(val card: Card? = null) : CardSessionRunnable {
override val requiresPin2 = false
+ private val blockchainSdkConfig = store.state.globalState.configManager?.config
+ ?.blockchainSdkConfig ?: BlockchainSdkConfig()
+ private val walletManagerFactory = WalletManagerFactory(blockchainSdkConfig)
+
override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) {
ScanTask().run(session) { result ->
when (result) {
@@ -53,7 +59,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable {
callback(CompletionResult.Success(ScanNoteResponse(
- walletManager, card, verifyResult.data, publicKey
- )))
+ walletManager, card, verifyResult.data, publicKey)))
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(TangemSdkError.CardVerificationFailed()))
@@ -95,7 +100,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable> = moshi.adapter(
+ Types.newParameterizedType(List::class.java, TokenDao::class.java)
+ )
+ private val blockchainsAdapter: JsonAdapter> = moshi.adapter(
+ Types.newParameterizedType(List::class.java, Blockchain::class.java)
+ )
+
+ fun loadCardCurrencies(cardId: String): CardCurrencies {
+ return CardCurrencies(loadSavedTokens(cardId), loadSavedBlockchains(cardId))
+ }
+
+ fun saveCardCurrencies(cardId: String, currencies: CardCurrencies) {
+ saveTokens(cardId, currencies.tokens)
+ saveBlockchains(cardId, currencies.blockchains)
+ }
+
+ fun saveAddedToken(cardId: String, token: Token) {
+ val tokens = loadSavedTokens(cardId) + token
+ saveTokens(cardId, tokens)
+ }
+
+ fun saveAddedBlockchain(cardId: String, blockchain: Blockchain) {
+ val blockchains = loadSavedBlockchains(cardId) + blockchain
+ saveBlockchains(cardId, blockchains)
+ }
+
+ fun removeToken(cardId: String, token: Token) {
+ val tokens = loadSavedTokens(cardId).filterNot { it == token }
+ saveTokens(cardId, tokens)
+ }
+
+ fun removeBlockchain(cardId: String, blockchain: Blockchain) {
+ val blockchains = loadSavedBlockchains(cardId).filterNot { it == blockchain }
+ saveBlockchains(cardId, blockchains)
+ }
+
+ private fun loadSavedTokens(cardId: String): List {
+ return try {
+ val json = context.readFileText(getFileNameForTokens(cardId))
+ tokensAdapter.fromJson(json)!!.map { it.toToken() }
+ } catch (exception: Exception) {
+ emptyList()
+ }
+ }
+
+ private fun saveTokens(cardId: String, tokens: List) {
+ val json = tokensAdapter.toJson(tokens.map { TokenDao.fromToken(it) })
+ context.rewriteFile(json, getFileNameForTokens(cardId))
+ }
+
+ private fun loadSavedBlockchains(cardId: String): List {
+ return try {
+ val json = context.readFileText(getFileNameForBlockchains(cardId))
+ blockchainsAdapter.fromJson(json) ?: emptyList()
+ } catch (exception: Exception) {
+ emptyList()
+ }
+ }
+
+ private fun saveBlockchains(cardId: String, blockchains: List) {
+ val json = blockchainsAdapter.toJson(blockchains)
+ context.rewriteFile(json, getFileNameForBlockchains(cardId))
+ }
+
+ private fun Context.readFileText(fileName: String): String =
+ this.openFileInput(fileName).bufferedReader().readText()
+
+ private fun Context.rewriteFile(content: String, fileName: String) {
+ this.openFileOutput(fileName, Context.MODE_PRIVATE).use {
+ it.write(content.toByteArray(), 0, content.length)
+ }
+ }
+
+ fun getPopularTokens(): List {
+ val json = context.assets.readJsonFileToString(POPULAR_TOKENS_FILE_NAME)
+ return tokensAdapter.fromJson(json)!!.map { it.toToken() }
+ }
+
+ fun getBlockchains(): List {
+ return listOf(
+ Blockchain.Bitcoin, Blockchain.BitcoinCash, Blockchain.Binance, Blockchain.Litecoin,
+ Blockchain.XRP, Blockchain.Tezos,
+ Blockchain.Ethereum, Blockchain.RSK)
+ }
+
+ companion object {
+ private const val POPULAR_TOKENS_FILE_NAME = "erc20_tokens"
+ private const val FILE_NAME_PREFIX_TOKENS = "tokens"
+ private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
+ fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
+ fun getFileNameForBlockchains(cardId: String): String = "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
+ }
+}
+
+
+@JsonClass(generateAdapter = true)
+data class TokenDao(
+ val name: String,
+ val symbol: String,
+ val contractAddress: String,
+ val decimalCount: Int
+) {
+ fun toToken(): Token {
+ return Token(name = name, symbol = symbol, contractAddress = contractAddress, decimals = decimalCount)
+ }
+
+ companion object {
+ fun fromToken(token: Token): TokenDao {
+ return TokenDao(name = token.name, symbol = token.symbol, contractAddress = token.contractAddress, decimalCount = token.decimals)
+
+ }
+ }
+}
+
+@JsonClass(generateAdapter = true)
+data class CardCurrenciesDao(
+ val tokens: List,
+ val blockchains: List
+) {
+ fun toCardCurrencies(): CardCurrencies {
+ return CardCurrencies(tokens = tokens.map { it.toToken() }, blockchains = blockchains)
+ }
+
+ companion object {
+ fun fromCardCurrencies(cardCurrencies: CardCurrencies): CardCurrenciesDao {
+ return CardCurrenciesDao(tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }, blockchains = cardCurrencies.blockchains)
+ }
+ }
+}
+
+data class CardCurrencies(
+ val tokens: List,
+ val blockchains: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
index 65a2c7dff6..f37ef1c696 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
@@ -18,7 +18,7 @@ sealed class DetailsAction : Action {
data class PrepareScreen(
val card: Card,
val scanNoteResponse: ScanNoteResponse,
- val wallet: Wallet?,
+ val wallets: List,
val isCreatingTwinWalletAllowed: Boolean?,
val cardTou: CardTou,
val fiatCurrencyName: FiatCurrencyName,
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
index 95a0a3952c..b66aa958e9 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
@@ -104,7 +104,7 @@ class DetailsMiddleware {
is DetailsAction.AppCurrencyAction.SelectAppCurrency -> {
preferencesStorage.saveAppCurrency(action.fiatCurrencyName)
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrencyName))
- store.dispatch(WalletAction.LoadFiatRate)
+ store.dispatch(WalletAction.LoadFiatRate())
}
}
}
@@ -152,9 +152,7 @@ class DetailsMiddleware {
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)
}
}
-
}
-
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
index ebb0485c03..ee44e36c9d 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
@@ -66,8 +66,9 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta
} else {
null
}
+
return DetailsState(
- card = action.card, wallet = action.wallet,
+ card = action.card, wallets = action.wallets,
cardInfo = action.card.toCardInfo(),
appCurrencyState = AppCurrencyState(
action.fiatCurrencyName
@@ -82,8 +83,9 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS
return when (action) {
DetailsAction.EraseWallet.Check -> {
val notAllowedByCard = state.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true
- val notEmpty = state.wallet?.recentTransactions?.isNullOrEmpty() != true ||
- state.wallet.amounts.toSendableAmounts().isNotEmpty()
+ val notEmpty = state.wallets.any {
+ !it.recentTransactions.isNullOrEmpty() || it.amounts.toSendableAmounts().isNotEmpty()
+ }
val eraseWalletState = when {
notAllowedByCard -> EraseWalletState.NotAllowedByCard
notEmpty -> EraseWalletState.NotEmpty
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
index 7995acdcdb..5ef1033e2f 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
@@ -13,7 +13,7 @@ import java.util.*
data class DetailsState(
val card: Card? = null,
- val wallet: Wallet? = null,
+ val wallets: List = emptyList(),
val cardInfo: CardInfo? = null,
val appCurrencyState: AppCurrencyState = AppCurrencyState(),
val eraseWalletState: EraseWalletState? = null,
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
index 8578a60304..895f379b09 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
@@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.twins.getTwinCardIdForUser
import com.tangem.tap.domain.twins.isTwinCard
@@ -15,6 +16,7 @@ import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
+import com.tangem.tap.features.feedback.FeedbackEmail
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_details.*
@@ -78,7 +80,6 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber()
+
+ override fun log(message: () -> String, level: Log.Level) {
+ logs.add(message())
+ }
+
+ fun getLogs(): List = logs.toList()
+
+ fun clearLogs() {
+ logs.clear()
+ }
+}
+
+class AdditionalEmailInfo {
+ var cardId: String = ""
+ var cardFirmwareVersion: String = ""
+ var blockchain: Blockchain = Blockchain.Unknown
+
+ var phoneModel: String = Build.MODEL
+ var osVersion: String = Build.VERSION.SDK_INT.toString()
+ var appVersion: String = ""
+
+ var token: String = ""
+ var sourceAddress: String = ""
+ var destinationAddress: String = ""
+ var amount: String = ""
+ var fee: String = ""
+
+ // var transactionHex: String = ""
+ var signedHashesCount: String = ""
+ var explorerLink: String = ""
+// var outputsCount: String = ""
+
+ fun updateAppVersion(context: Context) {
+ try {
+ val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
+ appVersion = pInfo.versionName
+ } catch (e: PackageManager.NameNotFoundException) {
+ e.printStackTrace()
+ }
+ }
+}
+
+interface EmailData {
+ val subject: String
+ val mainMessage: String
+ fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String
+
+ fun joinTogether(infoHolder: AdditionalEmailInfo): String {
+ return "$mainMessage\n\n\n\n\n" +
+ "Following information is optional. You can erase it if you don’t want to share it.\n" +
+ createOptionalMessage(infoHolder)
+ }
+}
+
+class RateCanBeBetterEmail : EmailData {
+ override val subject: String = "My suggestions"
+ override val mainMessage: String = "Tell us what functions you are missing, and we will try to help you."
+
+ override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
+ return StringBuilder().apply {
+ appendKeyValue("Card ID", infoHolder.cardId)
+ appendKeyValue("Blockchain", infoHolder.blockchain.fullName)
+ appendKeyValue("Phone model", infoHolder.phoneModel)
+ appendKeyValue("OS version", infoHolder.osVersion)
+ appendKeyValue("App version", infoHolder.appVersion)
+ }.toString()
+ }
+}
+
+class ScanFailsEmail : EmailData {
+ override val subject: String = "Can’t scan a card"
+ override val mainMessage: String = "Please tell us what card do you have?"
+ override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
+ return StringBuilder().apply {
+ appendKeyValue("Phone model", infoHolder.phoneModel)
+ appendKeyValue("OS version", infoHolder.osVersion)
+ appendKeyValue("App version", infoHolder.appVersion)
+ }.toString()
+ }
+}
+
+class SendTransactionFailedEmail(private val error: String) : EmailData {
+ override val subject: String = "Can’t send a transaction"
+ override val mainMessage: String = "Please tell us more about your issue. Every small detail can help."
+ override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
+ return StringBuilder().apply {
+ appendKeyValue("Error", error)
+ appendKeyValue("Card ID", infoHolder.cardId)
+ appendKeyValue("Blockchain", infoHolder.blockchain.fullName)
+ appendKeyValue("Token", infoHolder.token)
+ appendKeyValue("Source address", infoHolder.sourceAddress)
+ appendKeyValue("Destination address", infoHolder.destinationAddress)
+ appendKeyValue("Amount", infoHolder.amount)
+ appendKeyValue("Fee", infoHolder.fee)
+ appendKeyValue("Phone model", infoHolder.phoneModel)
+ appendKeyValue("OS version", infoHolder.osVersion)
+ appendKeyValue("App version", infoHolder.appVersion)
+ appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
+// appendKeyValue("Transaction HEX", infoHolder.transactionHex)
+ }.toString()
+ }
+}
+
+class FeedbackEmail : EmailData {
+ override val subject: String = "Tangem Tap feedback"
+ override val mainMessage: String = "Hi Tangem,"
+ override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
+ return StringBuilder().apply {
+ appendKeyValue("Card ID", infoHolder.cardId)
+ appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
+ appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
+ appendKeyValue("Blockchain", infoHolder.blockchain.fullName)
+ appendKeyValue("Wallet address", infoHolder.sourceAddress)
+ appendKeyValue("Explorer link", infoHolder.explorerLink)
+// appendKeyValue("Outputs count", infoHolder.outputsCount)
+ appendKeyValue("Phone model", infoHolder.phoneModel)
+ appendKeyValue("OS version", infoHolder.osVersion)
+ }.toString()
+ }
+}
+
+fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder {
+ return this.append("$key: $value\n")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
index bcb227de3b..8a87b6108f 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
@@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.ToastNotificationAction
import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.redux.states.SendButtonState
@@ -23,7 +24,9 @@ object ReleaseSendState : Action
data class PrepareSendScreen(
val coinAmount: Amount?,
+ val coinRate: BigDecimal?,
val tokenAmount: Amount? = null,
+ val tokenRate: BigDecimal? = null
) : SendScreenAction
// Address or PayId
@@ -138,12 +141,13 @@ sealed class SendAction : SendScreenAction {
data class SendError(override val error: TapError) : SendAction(), ErrorAction
sealed class Dialog : SendAction() {
- data class ShowTezosWarningDialog(
+ data class TezosWarningDialog(
val reduceCallback: () -> Unit,
val sendAllCallback: () -> Unit,
val reduceAmount: BigDecimal,
) : Dialog()
-
+ data class SendTransactionFails(val errorMessage: String): Dialog()
object Hide : Dialog()
}
+ data class SetWarnings(val warningList: List) : SendAction()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
index 2af762a25c..115c1c2033 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
@@ -20,9 +20,12 @@ import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
import com.tangem.tap.features.send.redux.states.SendButtonState
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
+import com.tangem.tap.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
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Action
@@ -50,7 +53,7 @@ val sendMiddleware: Middleware = { dispatch, appState ->
}
private fun verifyAndSendTransaction(
- action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit
+ action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit,
) {
val sendState = appState?.sendState ?: return
val walletManager = appState.globalState.scanNoteResponse?.walletManager ?: return
@@ -66,7 +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 = {
@@ -92,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)
@@ -116,6 +119,12 @@ private fun sendTransaction(
dispatch(SendAction.SendSuccess)
dispatch(GlobalAction.UpdateWalletSignedHashes(result.data.walletSignedHashes))
dispatch(NavigationAction.PopBackTo())
+ scope.launch(Dispatchers.IO) {
+ delay(10000)
+ withContext(Dispatchers.Main) {
+ dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain.currency))
+ }
+ }
}
is Result.Failure -> {
when (result.error) {
@@ -136,19 +145,23 @@ private fun sendTransaction(
when {
message == null -> {
dispatch(SendAction.SendError(TapError.UnknownError))
+ updateFeedbackManager(walletManager, amountToSend, feeAmount, destinationAddress, card)
+ dispatch(SendAction.Dialog.SendTransactionFails("unknown error"))
}
message.contains("50002") -> {
// user was cancelled the operation by closing the Sdk bottom sheet
}
// make it easier latter by handling an appropriate enumError or, like on iOS,
// accept a string identifier of the error message
- message.contains("Target account is not created. To create account send 1+ XLM.")-> {
+ message.contains("Target account is not created. To create account send 1+ XLM.") -> {
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
else -> {
Timber.e(throwable)
FirebaseCrashlytics.getInstance().recordException(throwable)
dispatch(SendAction.SendError(TapError.CustomError(message)))
+ updateFeedbackManager(walletManager, amountToSend, feeAmount, destinationAddress, card)
+ dispatch(SendAction.Dialog.SendTransactionFails(message))
}
}
}
@@ -160,6 +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): EnumSet {
val showIntoAmountField = EnumSet.noneOf(TransactionError::class.java)
errors.forEach {
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
index 16da618931..527d3f6545 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
@@ -42,8 +42,10 @@ 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
}
@@ -62,12 +64,10 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer {
val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!!
val decimals = amountToExtract.decimals
- val coinConverter = createCurrencyConverter(walletManager.wallet.blockchain.currency, decimals)
- val tokenConverter = createCurrencyConverter(prepareAction.tokenAmount?.currencySymbol ?: "", decimals)
return sendState.copy(
walletManager = walletManager,
- coinConverter = coinConverter,
- tokenConverter = tokenConverter,
+ coinConverter = action.coinRate?.let { CurrencyConverter(it, decimals) },
+ tokenConverter = action.tokenRate?. let { CurrencyConverter(it, decimals) },
amountState = sendState.amountState.copy(
amountToExtract = amountToExtract,
typeOfAmount = amountToExtract.type,
@@ -78,11 +78,6 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer {
)
)
}
-
- private fun createCurrencyConverter(currency: String, decimals: Int): CurrencyConverter? {
- val rate = store.state.globalState.conversionRates.getRate(currency)
- return if (rate == null) null else CurrencyConverter(rate, decimals)
- }
}
internal fun updateLastState(sendState: SendState, lastChangedState: IdStateHolder): SendState {
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
index b85eb86bdc..c4e57d708a 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
@@ -8,6 +8,7 @@ import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.common.entities.TapCurrency
import com.tangem.tap.common.text.DecimalDigitsInputFilter
import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.store
import org.rekotlin.StateType
@@ -37,6 +38,7 @@ data class SendState(
val amountState: AmountState = AmountState(),
val feeState: FeeState = FeeState(),
val receiptState: ReceiptState = ReceiptState(),
+ val sendWarningsList: List = listOf(),
val sendButtonState: SendButtonState = SendButtonState.DISABLED,
val dialog: SendAction.Dialog? = null
) : SendScreenState {
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
index 6dae105162..6ee78a5baf 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
@@ -9,9 +9,12 @@ import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.core.view.postDelayed
import androidx.core.widget.addTextChangedListener
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputEditText
import com.tangem.Message
import com.tangem.merchant.common.toggleWidget.ToggleWidget
+import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard
import com.tangem.tap.common.KeyboardObserver
import com.tangem.tap.common.entities.TapCurrency
@@ -22,6 +25,7 @@ import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity
import com.tangem.tap.common.snackBar.MaxAmountSnackbar
import com.tangem.tap.common.text.truncateMiddleWith
import com.tangem.tap.common.toggleWidget.*
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.features.send.BaseStoreFragment
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.*
@@ -30,10 +34,14 @@ import com.tangem.tap.features.send.redux.FeeActionUi.*
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
+import com.tangem.tap.features.wallet.ui.adapters.SpacesItemDecoration
+import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_send.*
+import kotlinx.android.synthetic.main.fragment_send.rv_warning_messages
+import kotlinx.android.synthetic.main.fragment_wallet.*
import kotlinx.android.synthetic.main.layout_send_address_payid.*
import kotlinx.android.synthetic.main.layout_send_amount.*
import kotlinx.android.synthetic.main.layout_send_fee.*
@@ -50,6 +58,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
lateinit var sendBtn: ToggleWidget
private lateinit var etAmountToSend: TextInputEditText
+ private lateinit var warningsAdapter: WarningMessagesAdapter
private fun initSendButtonStates() {
sendBtn = ToggleWidget(flSendButtonContainer, btnSend, progress, ProgressState.None())
@@ -70,6 +79,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
setupTransactionExtrasLayout()
setupAmountLayout()
setupFeeLayout()
+ setupWarningMessages()
btnSend.setOnClickListener {
store.dispatch(SendActionUi.SendAmountToRecipient(
@@ -232,6 +242,17 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
+ private fun setupWarningMessages() {
+ warningsAdapter = WarningMessagesAdapter()
+ val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
+ rv_warning_messages.layoutManager = layoutManager
+ rv_warning_messages.addItemDecoration(SpacesItemDecoration(rv_warning_messages.dpToPx(16f).toInt()))
+ rv_warning_messages.adapter = warningsAdapter
+
+ val warnings = store.state.globalState.warningManager?.getWarnings(WarningMessage.Location.SendScreen) ?: listOf()
+ store.dispatch(SendAction.SetWarnings(warnings))
+ }
+
override fun subscribeToStore() {
store.subscribe(sendSubscriber) { appState ->
appState.skipRepeats { oldState, newState ->
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
new file mode 100644
index 0000000000..cb65943200
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
@@ -0,0 +1,30 @@
+package com.tangem.tap.features.send.ui.dialogs
+
+import android.content.Context
+import androidx.appcompat.app.AlertDialog
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.features.feedback.SendTransactionFailedEmail
+import com.tangem.tap.features.send.redux.SendAction
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+class SendTransactionFailsDialog {
+
+ companion object {
+ fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails): AlertDialog {
+ return AlertDialog.Builder(context).apply {
+ setTitle(R.string.alert_failed_to_send_transaction_title)
+ setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, dialog.errorMessage))
+ setNeutralButton(R.string.alert_button_send_feedback) { _, _ ->
+ store.dispatch(GlobalAction.SendFeedback(SendTransactionFailedEmail(dialog.errorMessage)))
+ }
+ setPositiveButton(R.string.common_no) { _, _ -> }
+ setOnDismissListener { store.dispatch(WalletAction.HideDialog) }
+ }.create()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt
index 45b092ea8c..afb6276cb7 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt
@@ -9,10 +9,10 @@ import com.tangem.wallet.R
class TezosWarningDialog(context: Context) : AlertDialog(context) {
companion object {
- fun create(context: Context, showDialogData: SendAction.Dialog.ShowTezosWarningDialog): AlertDialog {
+ fun create(context: Context, showDialogData: SendAction.Dialog.TezosWarningDialog): AlertDialog {
val reduceAmount = showDialogData.reduceAmount.toPlainString()
return Builder(context).apply {
- setTitle(context.getString(R.string.common_warning))
+ setTitle(R.string.common_warning)
setMessage(context.getString(R.string.xtz_withdrawal_message_warning, reduceAmount))
setNegativeButton(R.string.xtz_withdrawal_message_ignore) { _, _ ->
showDialogData.sendAllCallback()
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
index 3476f1ae19..973ed08f8b 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
@@ -21,7 +21,9 @@ 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
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.btn_expand_collapse.*
@@ -96,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
@@ -122,6 +130,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
sendFragment.sendBtn.setState(ProgressState.Progress(), true)
}
}
+
+ val rv = fg.rv_warning_messages
+ val adapter = rv.adapter as? WarningMessagesAdapter ?: return
+
+ adapter.submitList(state.sendWarningsList)
+ rv.show(state.sendWarningsList.isNotEmpty())
}
private fun handleAddressPayIdState(fg: BaseStoreFragment, state: AddressPayIdState) {
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt
new file mode 100644
index 0000000000..e0c9abe119
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt
@@ -0,0 +1,21 @@
+package com.tangem.tap.features.tokens.redux
+
+import com.tangem.blockchain.common.Token
+import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
+import com.tangem.tap.features.wallet.redux.WalletData
+import org.rekotlin.Action
+
+sealed class TokensAction : Action {
+
+ object LoadCurrencies : TokensAction() {
+ data class Success(val currencies: List) : TokensAction()
+ }
+
+ object LoadCardTokens : TokensAction() {
+ data class Success(val tokens: List) : TokensAction()
+ }
+
+ data class SetAddedCurrencies(val wallets: List) : TokensAction()
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
new file mode 100644
index 0000000000..7923d17bd4
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
@@ -0,0 +1,28 @@
+package com.tangem.tap.features.tokens.redux
+
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.currenciesRepository
+import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
+import com.tangem.tap.store
+import org.rekotlin.Middleware
+
+class TokensMiddleware {
+
+ val tokensMiddleware: Middleware = { dispatch, state ->
+ { next ->
+ { action ->
+ when (action) {
+ is TokensAction.LoadCurrencies -> {
+ val tokens = currenciesRepository.getPopularTokens()
+ val blockchains = currenciesRepository.getBlockchains()
+ val currencies = CurrencyListItem.createListOfCurrencies(
+ blockchains, tokens
+ )
+ store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
+ }
+ }
+ next(action)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt
new file mode 100644
index 0000000000..e0c5600ad1
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt
@@ -0,0 +1,30 @@
+package com.tangem.tap.features.tokens.redux
+
+import com.tangem.tap.common.redux.AppState
+import org.rekotlin.Action
+
+class TokensReducer {
+ companion object {
+ fun reduce(action: Action, state: AppState): TokensState = internalReduce(action, state)
+ }
+}
+
+private fun internalReduce(action: Action, state: AppState): TokensState {
+ if (action !is TokensAction) return state.tokensState
+
+ val tokensState = state.tokensState
+ return when (action) {
+ is TokensAction.LoadCurrencies.Success -> {
+ tokensState.copy(currencies = action.currencies)
+ }
+ is TokensAction.SetAddedCurrencies -> {
+ tokensState.copy(addedCurrencies = action.wallets.mapNotNull { it.currencyData.currencySymbol })
+ }
+ is TokensAction.LoadCardTokens.Success -> {
+ tokensState.copy(addedTokens = LinkedHashSet(
+ action.tokens.map { TokenWithAmount(it, null) }
+ ))
+ }
+ else -> tokensState
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt
new file mode 100644
index 0000000000..5169e63b96
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt
@@ -0,0 +1,18 @@
+package com.tangem.tap.features.tokens.redux
+
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.Token
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
+import org.rekotlin.StateType
+
+data class TokensState(
+ val addedTokens: LinkedHashSet = LinkedHashSet(),
+ val addedCurrencies: List = emptyList(),
+ val currencies: List = emptyList(),
+) : StateType
+
+data class TokenWithAmount(
+ val token: Token,
+ val amount: Amount? = null
+)
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt
new file mode 100644
index 0000000000..e3d8eb80fb
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt
@@ -0,0 +1,119 @@
+package com.tangem.tap.features.tokens.ui
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import androidx.activity.OnBackPressedCallback
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.SearchView
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.transition.TransitionInflater
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.tokens.redux.TokensState
+import com.tangem.tap.features.tokens.ui.adapters.CurrenciesAdapter
+import com.tangem.tap.mainScope
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.fragment_add_tokens.*
+import kotlinx.android.synthetic.main.fragment_details_twin_cards.toolbar
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.*
+import org.rekotlin.StoreSubscriber
+
+
+class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
+ StoreSubscriber {
+
+ private lateinit var viewAdapter: CurrenciesAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ store.dispatch(NavigationAction.PopBackTo())
+ }
+ })
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(R.transition.slide_right)
+ exitTransition = inflater.inflateTransition(R.transition.fade)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.tokensState == newState.tokensState
+ }.select { it.tokensState }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ (activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
+ toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
+ setupPopularTokensRecyclerView()
+ }
+
+
+ private fun setupPopularTokensRecyclerView() {
+ viewAdapter = CurrenciesAdapter()
+ rv_popular_tokens.layoutManager = LinearLayoutManager(context)
+ rv_popular_tokens.adapter = viewAdapter
+ }
+
+
+ override fun newState(state: TokensState) {
+ if (activity == null) return
+ viewAdapter.addedCurrencies = state.addedCurrencies
+ viewAdapter.submitUnfilteredList(state.currencies)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.menu_search -> {
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.popular_tokens, menu)
+
+ val menuItem = menu.findItem(R.id.menu_search)
+ val searchView: SearchView = menuItem.actionView as SearchView
+ searchView.queryHint = "Type here to search"
+ searchView.maxWidth = android.R.attr.width
+ searchView.inputtedTextAsFlow()
+ .debounce(400)
+ .distinctUntilChanged()
+ .onEach {
+ viewAdapter.filter(searchView.query)
+ }
+ .launchIn(mainScope)
+ return super.onCreateOptionsMenu(menu, inflater);
+ }
+}
+
+fun SearchView.inputtedTextAsFlow(): Flow = callbackFlow {
+ val watcher = setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String?): Boolean {
+ return false
+ }
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ offer(newText ?: "")
+ return false
+ }
+ })
+ awaitClose { (watcher) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt
new file mode 100644
index 0000000000..9fe1ba05bd
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt
@@ -0,0 +1,165 @@
+package com.tangem.tap.features.tokens.ui.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.StringRes
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.item_currency_subtitle.view.*
+import kotlinx.android.synthetic.main.item_popular_token.view.*
+import java.util.*
+
+class CurrenciesAdapter : ListAdapter(DiffUtilCallback) {
+
+ var addedCurrencies: List = emptyList()
+
+ private var unfilteredList = listOf()
+
+ fun submitUnfilteredList(list: List) {
+ unfilteredList = list
+ submitList(list)
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return when (currentList[position]) {
+ is CurrencyListItem.TitleListItem -> 0
+ is CurrencyListItem.BlockchainListItem, is CurrencyListItem.TokenListItem -> 1
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return when (viewType) {
+ 0 -> TitleViewHolder(LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_currency_subtitle, parent, false))
+ 1 -> CurrenciesViewHolder(LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_popular_token, parent, false))
+ else -> CurrenciesViewHolder(LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_popular_token, parent, false))
+ }
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ val listItem = currentList[position]
+ if (holder is TitleViewHolder && listItem is CurrencyListItem.TitleListItem) {
+ holder.bind(listItem)
+ } else if (holder is CurrenciesViewHolder) {
+ holder.bind(listItem, addedCurrencies)
+ }
+ }
+
+ object DiffUtilCallback : DiffUtil.ItemCallback() {
+ override fun areContentsTheSame(
+ oldItem: CurrencyListItem, newItem: CurrencyListItem
+ ) = oldItem == newItem
+
+ override fun areItemsTheSame(
+ oldItem: CurrencyListItem, newItem: CurrencyListItem
+ ) = oldItem == newItem
+ }
+
+ fun filter(query: CharSequence?) {
+ val list = mutableListOf()
+
+ if (!query.isNullOrEmpty()) {
+ val queryNormalized = query.toString().toLowerCase(Locale.US)
+ list.addAll(
+ unfilteredList.filter { element ->
+ when (element) {
+ is CurrencyListItem.BlockchainListItem -> {
+ element.blockchain.currency.toLowerCase(Locale.US).contains(queryNormalized)
+ || element.blockchain.fullName.toLowerCase(Locale.US).contains(queryNormalized)
+
+ }
+ is CurrencyListItem.TokenListItem -> {
+ element.token.name.toLowerCase(Locale.US).contains(queryNormalized)
+ || element.token.symbol.toLowerCase(Locale.US).contains(queryNormalized)
+ }
+ else -> false
+ }
+ })
+ } else {
+ list.addAll(unfilteredList)
+ }
+ submitList(list)
+ }
+
+ class CurrenciesViewHolder(val view: View) :
+ RecyclerView.ViewHolder(view) {
+ fun bind(currency: CurrencyListItem, addedCurrencies: List) {
+ when (currency) {
+ is CurrencyListItem.BlockchainListItem -> {
+ val blockchain = currency.blockchain
+ view.tv_currency_name.text = blockchain.fullName
+ view.tv_currency_symbol.text = blockchain.currency
+ val isAdded = addedCurrencies.any { it == blockchain.currency }
+ view.btn_add_token.show(!isAdded)
+ view.btn_token_added.show(isAdded)
+
+ view.iv_currency.setImageResource(currency.blockchain.getIconRes())
+ view.iv_currency.colorFilter = null
+ view.tv_token_letter.text = null
+
+ view.btn_add_token.setOnClickListener {
+ store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain))
+ view.btn_add_token.hide()
+ view.btn_token_added.show()
+ }
+ }
+ is CurrencyListItem.TokenListItem -> {
+ val token = currency.token
+ view.tv_currency_name.text = token.name
+ view.tv_currency_symbol.text = token.symbol
+
+ val isAdded = addedCurrencies.any { it == token.symbol }
+ view.btn_add_token.show(!isAdded)
+ view.btn_token_added.show(isAdded)
+
+ view.iv_currency.setImageResource(R.drawable.shape_circle)
+ view.iv_currency.setColorFilter(token.getColor())
+ view.tv_token_letter.text = token.symbol.take(1)
+
+ view.btn_add_token.setOnClickListener {
+ store.dispatch(WalletAction.MultiWallet.AddToken(token))
+ view.btn_add_token.hide()
+ view.btn_token_added.show()
+ }
+ }
+ }
+ }
+ }
+
+ class TitleViewHolder(val view: View) :
+ RecyclerView.ViewHolder(view) {
+ fun bind(title: CurrencyListItem.TitleListItem) {
+ view.tv_subtitle.text = view.getString(title.titleResId).toUpperCase(Locale.US)
+ }
+ }
+}
+
+
+sealed class CurrencyListItem {
+ data class TokenListItem(val token: Token) : CurrencyListItem()
+ data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
+ data class TitleListItem(@StringRes val titleResId: Int) : CurrencyListItem()
+
+ companion object {
+ fun createListOfCurrencies(blockchains: List, tokens: List): List {
+ val blockchainsTitle = R.string.add_tokens_subtitle_blockchains
+ val tokensTitle = R.string.add_tokens_subtitle_tokens
+ return listOf(TitleListItem(blockchainsTitle)) +
+ blockchains.map { BlockchainListItem(it) } +
+ listOf(TitleListItem(tokensTitle)) +
+ tokens.map { TokenListItem(it) }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt
index 21cdc9d649..0ade915979 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.wallet.models
+import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.tap.common.extensions.toFormattedString
@@ -47,4 +48,13 @@ fun List.toPendingTransactions(walletAddress: String): List.removeUnknownTransactions(): List {
return this.filter { it.type != PendingTransactionType.Unknown }
+}
+
+fun TransactionData.toPendingTransactionForToken(token: Token, walletAddress: String): PendingTransaction? {
+ if (this.amount.currencySymbol != token.symbol) return null
+ return this.toPendingTransaction(walletAddress)
+}
+
+fun List.toPendingTransactionsForToken(token: Token, walletAddress: String): List {
+ return this.mapNotNull { it.toPendingTransactionForToken(token, walletAddress) }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
index 0656a9583f..b85af64448 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
@@ -1,14 +1,15 @@
package com.tangem.tap.features.wallet.redux
import android.content.Context
-import com.tangem.blockchain.common.Amount
-import com.tangem.blockchain.common.Wallet
+import com.tangem.TangemError
+import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.AddressType
import com.tangem.commands.common.card.Card
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.wallet.R
import org.rekotlin.Action
@@ -22,71 +23,111 @@ sealed class WalletAction : Action {
data class Failure(val error: TapError) : WalletAction()
}
- data class LoadWallet(
- val wallet: Wallet, val artworkId: String?, val allowTopUp: Boolean
- ) : WalletAction() {
+
+ data class LoadWallet(val allowTopUp: Boolean? = null, val currency: CryptoCurrencyName? = null) : WalletAction() {
data class Success(val wallet: Wallet) : WalletAction()
- data class NoAccount(val amountToCreateAccount: String) : WalletAction()
- data class Failure(val errorMessage: String? = null) : WalletAction()
+ data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : WalletAction()
+ data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction()
}
- object CheckIfWarningNeeded : WalletAction()
- object CheckHashesCountOnline : WalletAction()
- object NeedToCheckHashesCountOnline : WalletAction()
- object ConfirmHashesCount : WalletAction()
- data class ShowWarning(val warningType: WarningType) : WalletAction()
- object SaveCardId : WalletAction()
+ data class SetArtworkId(val artworkId: String?) : WalletAction()
- object UpdateWallet : WalletAction() {
+
+ // sealed class ProcessWallet : WalletAction() {
+// data class LoadWallet(val artworkId: String?, val allowTopUp: Boolean
+// ) : ProcessWallet() {
+// data class Success(val wallet: Wallet) : ProcessWallet()
+// data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : ProcessWallet()
+// data class Failure(val wallet: Wallet, val errorMessage: String? = null) : ProcessWallet()
+// }
+//
+// data class UpdateWallet(val currency: CryptoCurrencyName? = null) : ProcessWallet() {
+// object ScheduleUpdatingWallet : ProcessWallet()
+// data class Success(val wallet: Wallet) : ProcessWallet()
+// data class Failure(val errorMessage: String? = null) : ProcessWallet()
+// }
+// }
+
+ sealed class MultiWallet : WalletAction() {
+ data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet()
+ data class AddWalletManagers(val walletManagers: List) : MultiWallet()
+ data class AddBlockchain(val blockchain: Blockchain) : MultiWallet()
+ data class AddBlockchains(val blockchains: List) : MultiWallet()
+ data class AddTokens(val tokens: List) : MultiWallet()
+ data class AddToken(val token: Token) : MultiWallet()
+ object FindTokensInUse : MultiWallet()
+ data class TokenLoaded(val amount: Amount) : MultiWallet()
+ data class SelectWallet(val walletData: WalletData?) : MultiWallet()
+ data class RemoveWallet(val walletData: WalletData) : MultiWallet()
+ data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet()
+ data class SetPrimaryToken(val token: Token) : MultiWallet()
+ }
+
+ sealed class Warnings : WalletAction() {
+ object CheckHashesCount : Warnings() {
+ object CheckHashesCountOnline : Warnings()
+ object NeedToCheckHashesCountOnline : Warnings()
+ object ConfirmHashesCount : Warnings()
+ object SaveCardId : Warnings()
+ }
+
+ object CheckIfNeeded : Warnings()
+ data class SetWarnings(val warningList: List) : Warnings()
+
+ object AppRating : Warnings() {
+ object SetNeverToShow : Warnings()
+ object RemindLater : Warnings()
+ }
+ }
+
+ data class UpdateWallet(val currency: CryptoCurrencyName? = null) : WalletAction() {
object ScheduleUpdatingWallet : WalletAction()
data class Success(val wallet: Wallet) : WalletAction()
data class Failure(val errorMessage: String? = null) : WalletAction()
}
- object LoadFiatRate : WalletAction() {
- data class Success(val fiatRates: Pair) : WalletAction()
+ data class LoadFiatRate(
+ val wallet: Wallet? = null, val currency: CryptoCurrencyName? = null
+ ) : WalletAction() {
+ data class Success(val fiatRate: Pair) : WalletAction()
object Failure : WalletAction()
}
- object LoadPayId : WalletAction() {
- data class Success(val payId: String) : WalletAction()
- object NotCreated : WalletAction()
- object Failure : WalletAction()
- }
-
- object DisablePayId : WalletAction()
-
data class LoadArtwork(val card: Card, val artworkId: String?) : WalletAction() {
data class Success(val artwork: Artwork) : WalletAction()
object Failure : 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?) : WalletAction()
object Cancel : WalletAction()
}
- 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
- }
- class Failure(override val error: TapError) : WalletAction(), ErrorAction
- object Cancel : WalletAction()
+ object EmptyField : WalletAction(), ErrorAction {
+ override val error = TapError.PayIdEmptyField
}
- data class CopyAddress(val context: Context) : WalletAction() {
+ data class CopyAddress(val address: String, val context: Context) : WalletAction() {
object Success : WalletAction(), NotificationAction {
override val messageResource = R.string.wallet_notification_address_copied
}
}
- object ShowQrCode : WalletAction()
+ 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 context: Context) : WalletAction()
+
+ data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction()
+
object CreateWallet : WalletAction()
object EmptyWallet : WalletAction()
@@ -101,7 +142,7 @@ sealed class WalletAction : Action {
object SetOnboardingShown : TwinsAction()
data class SetTwinCard(
val secondCardId: String, val number: TwinCardNumber,
- val isCreatingTwinCardsAllowed: Boolean
+ val isCreatingTwinCardsAllowed: Boolean,
) : TwinsAction()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt
deleted file mode 100644
index 2f2c041924..0000000000
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt
+++ /dev/null
@@ -1,304 +0,0 @@
-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.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.FirebaseAnalyticsHandler
-import com.tangem.tap.common.extensions.copyToClipboard
-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.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
-
-class WalletMiddleware {
- private val topUpMiddleware = TopUpMiddleware()
-
- val walletMiddleware: Middleware = { 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)
- }
- 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))
- }
- }
- }
- }
- }
- }
- 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.CheckIfWarningNeeded -> {
- val card = store.state.globalState.scanNoteResponse?.card
- val validator = store.state.globalState.scanNoteResponse?.walletManager
- as? SignatureCountValidator
- if (card != null && !preferencesStorage.wasCardScannedBefore(card.cardId)) {
- val result = checkIfWarningNeeded(card, validator)
- if (result != null) store.dispatch(WalletAction.ShowWarning(result))
- }
- }
- 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()
- }
- }
- next(action)
- }
- }
- }
-
- 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
- ): WarningType? {
-
- if (card.getType() != CardType.Release) {
- return WarningType.DevCard
- }
- if (card.isTwinCard()) return null
-
- return if (signatureCountValidator == null) {
- if (card.walletSignedHashes ?: 0 > 0) {
- WarningType.CardSignedHashesBefore
- } 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) {
- store.dispatch(WalletAction.ShowWarning(WarningType.CardSignedHashesBefore))
- } else if (card.walletSignedHashes ?: 0 > 0) {
- store.dispatch(WalletAction.ShowWarning(WarningType.CardSignedHashesBefore))
- }
- }
- }
- }
- }
-}
-
-private class TopUpMiddleware {
- fun handle(action: WalletAction.TopUpAction) {
- when (action) {
- is WalletAction.TopUpAction.TopUp -> {
- val config = store.state.globalState.configManager?.config ?: return
- val defaultAddress = store.state.walletState.walletAddresses!!.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));
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt
deleted file mode 100644
index 533121bbe8..0000000000
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt
+++ /dev/null
@@ -1,337 +0,0 @@
-package com.tangem.tap.features.wallet.redux
-
-import com.tangem.blockchain.common.AmountType
-import com.tangem.blockchain.common.Wallet
-import com.tangem.commands.common.network.TangemService
-import com.tangem.common.extensions.isZero
-import com.tangem.common.extensions.toHexString
-import com.tangem.tap.common.extensions.toFiatString
-import com.tangem.tap.common.extensions.toFormattedCurrencyString
-import com.tangem.tap.common.extensions.toFormattedString
-import com.tangem.tap.common.extensions.toQrCode
-import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.domain.TapError
-import com.tangem.tap.domain.getFirstToken
-import com.tangem.tap.domain.twins.TwinCardNumber
-import com.tangem.tap.features.wallet.models.removeUnknownTransactions
-import com.tangem.tap.features.wallet.models.toPendingTransactions
-import com.tangem.tap.features.wallet.ui.BalanceStatus
-import com.tangem.tap.features.wallet.ui.BalanceWidgetData
-import com.tangem.tap.features.wallet.ui.TokenData
-import com.tangem.tap.store
-import org.rekotlin.Action
-
-class WalletReducer {
- companion object {
- fun reduce(action: Action, state: AppState): WalletState = internalReduce(action, state)
- }
-}
-
-private fun internalReduce(action: Action, state: AppState): WalletState {
-
- if (action !is WalletAction) return state.walletState
-
- var newState = state.walletState
-
- when (action) {
- is WalletAction.ResetState -> newState = WalletState()
- is WalletAction.EmptyWallet -> {
- val creatingWalletAllowed = !(newState.twinCardsState != null &&
- newState.twinCardsState?.isCreatingTwinCardsAllowed != true)
-
- newState = newState.copy(
- state = ProgressState.Done,
- currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
- mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed),
- topUpState = TopUpState(false)
- )
- }
- is WalletAction.LoadData.Failure -> {
- when (action.error) {
- is TapError.NoInternetConnection -> {
- val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet
- newState = newState.copy(
- state = ProgressState.Error,
- error = ErrorType.NoInternetConnection,
- walletAddresses = createAddressList(wallet, newState.walletAddresses),
- currencyData = BalanceWidgetData(
- status = BalanceStatus.Unreachable,
- currency = wallet?.blockchain?.fullName,
- token = wallet?.getFirstToken()?.symbol?.let {
- TokenData("", tokenSymbol = it)
- }),
- mainButton = WalletMainButton.SendButton(false),
- topUpState = TopUpState(false)
- )
- }
- is TapError.UnknownBlockchain -> {
- newState = newState.copy(
- state = ProgressState.Done,
- currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
- topUpState = TopUpState(false)
- )
- }
- }
-
- }
- is WalletAction.LoadWallet -> {
- val wallet = action.wallet
- val cardImage = if (newState.cardImage?.artworkId == action.artworkId) {
- newState.cardImage
- } else {
- null
- }
- newState = newState.copy(
- state = ProgressState.Loading,
- cardImage = cardImage,
- currencyData = BalanceWidgetData(
- BalanceStatus.Loading,
- wallet.blockchain.fullName,
- currencySymbol = wallet.blockchain.currency,
- token = wallet.getFirstToken()?.symbol?.let {
- TokenData("", tokenSymbol = it)
- }
- ),
- walletAddresses = createAddressList(wallet, newState.walletAddresses),
- mainButton = WalletMainButton.SendButton(false),
- topUpState = TopUpState(allowed = action.allowTopUp)
- )
- }
- is WalletAction.LoadWallet.Success -> newState = onWalletLoaded(action.wallet, newState)
-
- is WalletAction.LoadWallet.NoAccount -> {
- val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet
- newState = newState.copy(
- state = ProgressState.Done, wallet = wallet,
- currencyData = BalanceWidgetData(
- BalanceStatus.NoAccount, wallet?.blockchain?.fullName,
- currencySymbol = wallet?.blockchain?.currency,
- amountToCreateAccount = action.amountToCreateAccount
- )
- )
- }
- is WalletAction.LoadWallet.Failure -> newState = newState.copy(
- state = ProgressState.Done,
- currencyData = newState.currencyData.copy(
- status = BalanceStatus.Unreachable,
- errorMessage = action.errorMessage
- ),
- topUpState = TopUpState(false)
- )
- is WalletAction.UpdateWallet -> {
- if (store.state.walletState.state == ProgressState.Done) {
- newState = newState.copy(updatingWallet = true)
- }
- }
- is WalletAction.UpdateWallet.ScheduleUpdatingWallet ->
- newState = newState.copy(updatingWallet = true)
- is WalletAction.UpdateWallet.Success -> {
- newState = onWalletLoaded(action.wallet, newState)
- newState = newState.copy(updatingWallet = newState.pendingTransactions.isNotEmpty())
- }
- is WalletAction.UpdateWallet.Failure -> newState = newState.copy(updatingWallet = false)
- is WalletAction.LoadFiatRate -> {
- newState.copy(currencyData = newState.currencyData.copy(
- fiatAmount = null,
- token = newState.currencyData.token?.copy(fiatAmount = null))
- )
- }
- is WalletAction.LoadFiatRate.Success -> {
- val rate = action.fiatRates.second ?: return newState
- val currency = action.fiatRates.first
- val fiatAmount = if (currency == newState.wallet?.blockchain?.currency) {
- newState.wallet?.amounts?.get(AmountType.Coin)?.value
- ?.toFiatString(rate, state.globalState.appCurrency)
- } else {
- newState.currencyData.fiatAmount
- }
- val token = newState.wallet?.getFirstToken()
- val tokenFiatAmount = if (currency == token?.symbol) {
- newState.wallet?.getTokenAmount(token)?.value?.toFiatString(rate, state.globalState.appCurrency)
- } else {
- newState.currencyData.token?.fiatAmount
- }
- newState = newState.copy(currencyData = newState.currencyData.copy(
- fiatAmount = fiatAmount,
- token = newState.currencyData.token?.copy(fiatAmount = tokenFiatAmount)
- ))
- }
- is WalletAction.LoadArtwork -> {
- val cardId = action.card.cardId
- val cardPublicKey = action.card.cardPublicKey?.toHexString()
- val artworkUrl = if (cardPublicKey != null && action.artworkId != null) {
- TangemService.getUrlForArtwork(cardId, cardPublicKey, action.artworkId)
- } else if (action.card.cardId.startsWith(Artwork.SERGIO_CARD_ID)) {
- Artwork.SERGIO_CARD_URL
- } else if (action.card.cardId.startsWith(Artwork.MARTA_CARD_ID)) {
- Artwork.MARTA_CARD_URL
- } else if (newState.twinCardsState?.cardNumber != null) {
- when (newState.twinCardsState?.cardNumber) {
- TwinCardNumber.First -> Artwork.TWIN_CARD_1
- TwinCardNumber.Second -> Artwork.TWIN_CARD_2
- null -> Artwork.DEFAULT_IMG_URL
- }
- } else {
- Artwork.DEFAULT_IMG_URL
- }
- newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl))
- }
- is WalletAction.ShowQrCode -> {
- newState = newState.copy(
- walletDialog = WalletDialog.QrDialog(
- newState.walletAddresses?.selectedAddress?.shareUrl?.toQrCode(),
- newState.walletAddresses?.selectedAddress?.shareUrl,
- newState.currencyData.currency
- )
- )
- }
- is WalletAction.HideDialog -> {
- newState = newState.copy(walletDialog = null)
- }
- is WalletAction.LoadPayId.Success -> newState = newState.copy(
- payIdData = PayIdData(PayIdState.Created, action.payId)
- )
- is WalletAction.LoadPayId.NotCreated -> newState = newState.copy(
- payIdData = PayIdData(PayIdState.NotCreated, null)
- )
- is WalletAction.DisablePayId -> newState = newState.copy(
- payIdData = PayIdData(PayIdState.Disabled, null)
- )
- is WalletAction.CreatePayId, is WalletAction.CreatePayId.Failure ->
- newState = newState.copy(
- walletDialog = WalletDialog.CreatePayIdDialog(CreatingPayIdState.EnterPayId)
- )
- is WalletAction.CreatePayId.CompleteCreatingPayId -> newState = newState.copy(
- walletDialog = WalletDialog.CreatePayIdDialog(CreatingPayIdState.Waiting)
- )
- is WalletAction.CreatePayId.Success -> newState = newState.copy(
- payIdData = PayIdData(PayIdState.Created, action.payId),
- walletDialog = null
- )
- is WalletAction.CreatePayId.Cancel -> newState = newState.copy(walletDialog = null)
- is WalletAction.Send.ChooseCurrency -> {
- newState = newState.copy(
- walletDialog = WalletDialog.SelectAmountToSendDialog(action.amounts)
- )
- }
- is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null)
- is WalletAction.ShowWarning ->
- newState = newState.copy(walletDialog = WalletDialog.WarningDialog(action.warningType))
- is WalletAction.NeedToCheckHashesCountOnline ->
- newState = newState.copy(hashesCountVerified = false)
- is WalletAction.ConfirmHashesCount ->
- newState = newState.copy(hashesCountVerified = true)
- is WalletAction.TopUpAction -> {
- newState = newState.copy(topUpState = handleTopUpActions(action, newState.topUpState))
- }
- is WalletAction.ChangeSelectedAddress -> {
- val walletAddresses = newState.walletAddresses ?: return newState
- val address = walletAddresses.list.firstOrNull { it.type == action.type }
- ?: return newState
-
- newState = newState.copy(walletAddresses = WalletAddresses(address, walletAddresses.list))
- }
- is WalletAction.TwinsAction.SetTwinCard -> {
- newState = newState.copy(
- twinCardsState = TwinCardsState(
- secondCardId = action.secondCardId,
- cardNumber = action.number,
- showTwinOnboarding = newState.twinCardsState?.showTwinOnboarding
- ?: false,
- isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed
- )
- )
- }
- is WalletAction.TwinsAction.ShowOnboarding -> {
- newState = newState.copy(
- twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = true)
- ?: TwinCardsState(null, null,
- showTwinOnboarding = true,
- isCreatingTwinCardsAllowed = false)
- )
- }
- is WalletAction.TwinsAction.SetOnboardingShown -> {
- newState = newState.copy(
- twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = false)
- )
- }
- }
- return newState
-}
-
-fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? {
- if (wallet == null) return null
-
- val listOfAddressData = mutableListOf()
- // put a defaultAddress at the first place
- wallet.addresses.forEach {
- val addressData = AddressData(it.value, it.type, wallet.getShareUri(it.value), wallet.getExploreUrl(it.value))
- if (it.type == wallet.blockchain.defaultAddressType()) {
- listOfAddressData.add(0, addressData)
- } else {
- listOfAddressData.add(addressData)
- }
- }
-
- // restore a selected wallet address
- var indexOfSelectedWallet = 0
- walletAddresses?.let {
- val index = listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address }
- if (index != -1) indexOfSelectedWallet = index
- }
- return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData)
-}
-
-private fun handleTopUpActions(action: WalletAction.TopUpAction, state: TopUpState): TopUpState {
- return when (action) {
- is WalletAction.TopUpAction.TopUp -> state
- }
-}
-
-private fun onWalletLoaded(
- wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null
-): WalletState {
- val fiatCurrencySymbol = store.state.globalState.appCurrency
- val token = wallet.getFirstToken()
- val tokenData = if (token != null) {
- val tokenAmount = wallet.getTokenAmount(token)
- if (tokenAmount != null) {
- val tokenFiatRate = store.state.globalState.conversionRates.getRate(tokenAmount.currencySymbol)
- val tokenFiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
- TokenData(tokenAmount.value?.toFormattedString(tokenAmount.decimals) ?: "",
- tokenAmount.currencySymbol, tokenFiatAmount)
- } else {
- null
- }
- } else {
- null
- }
- val amount = wallet.amounts[AmountType.Coin]?.value
- val formattedAmount = amount?.toFormattedCurrencyString(
- wallet.blockchain.decimals(),
- wallet.blockchain.currency)
- val fiatRate = store.state.globalState.conversionRates.getRate(wallet.blockchain.currency)
- val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
-
- val pendingTransactions = wallet.recentTransactions
- .toPendingTransactions(wallet.address)
-
- val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
- val balanceStatus = if (pendingTransactions.isNotEmpty()) {
- BalanceStatus.TransactionInProgress
- } else {
- BalanceStatus.VerifiedOnline
- }
- return walletState.copy(
- state = ProgressState.Done, wallet = wallet,
- currencyData = BalanceWidgetData(
- balanceStatus, wallet.blockchain.fullName,
- currencySymbol = wallet.blockchain.currency,
- formattedAmount,
- token = tokenData,
- fiatAmount = fiatAmount
- ),
- pendingTransactions = pendingTransactions.removeUnknownTransactions(),
- mainButton = WalletMainButton.SendButton(sendButtonEnabled)
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
index ec4c134bab..6bf6a41ffc 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
@@ -1,45 +1,99 @@
package com.tangem.tap.features.wallet.redux
import android.graphics.Bitmap
-import com.tangem.blockchain.common.Amount
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.AddressType
+import com.tangem.blockchain.extensions.isAboveZero
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.wallet.models.PendingTransaction
+import com.tangem.tap.features.wallet.models.toPendingTransactions
+import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
+import com.tangem.tap.store
import org.rekotlin.StateType
+import java.math.BigDecimal
data class WalletState(
val state: ProgressState = ProgressState.Done,
val error: ErrorType? = null,
val cardImage: Artwork? = null,
- val wallet: Wallet? = null,
- val pendingTransactions: List = emptyList(),
val hashesCountVerified: Boolean? = null,
- val walletAddresses: WalletAddresses? = null,
- val currencyData: BalanceWidgetData = BalanceWidgetData(),
- val payIdData: PayIdData = PayIdData(),
val walletDialog: WalletDialog? = null,
- val updatingWallet: Boolean = false,
- val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
- val topUpState: TopUpState = TopUpState(),
val twinCardsState: TwinCardsState? = null,
+ val mainWarningsList: List = mutableListOf(),
+ val scanCardFailsCounter: Int = 0,
+ val wallets: List = emptyList(),
+ val walletManagers: List = emptyList(),
+ val isMultiwalletAllowed: Boolean = false,
+ val cardCurrency: CryptoCurrencyName? = null,
+ val selectedWallet: CryptoCurrencyName? = null,
+ val primaryBlockchain: Blockchain? = null,
+ val primaryToken: Token? = null
) : StateType {
- val showDetails: Boolean =
- currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard &&
- currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain
- val showMultipleAddress: Boolean
- get() {
- val listOfAddresses = walletAddresses?.list ?: return false
- return (wallet?.blockchain == Blockchain.Bitcoin ||
- wallet?.blockchain == Blockchain.BitcoinTestnet ||
- wallet?.blockchain == Blockchain.CardanoShelley) &&
- listOfAddresses.size > 1
+ val primaryWallet = if (wallets.isNotEmpty()) wallets[0] else null
+// val primaryWalletManager = if (walletManagers.isNotEmpty()) walletManagers[0] else null
+
+ val shouldShowDetails: Boolean =
+ primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard &&
+ primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain
+
+ fun getWalletManager(currencyName: CryptoCurrencyName?): WalletManager? {
+ if (currencyName == null) return null
+ val walletManager = walletManagers.find { it.wallet.blockchain.currency == currencyName }
+ if (walletManager != null) return walletManager
+
+ val ethereumWalletManager = walletManagers.find { it.wallet.blockchain == Blockchain.Ethereum }
+ return if (ethereumWalletManager?.presetTokens?.find { it.symbol == currencyName } != null) {
+ ethereumWalletManager
+ } else {
+ null
}
+ }
+
+ fun getWalletData(currencyName: CryptoCurrencyName?): WalletData? {
+ if (currencyName == null) return null
+ return wallets.find { it.currencyData.currencySymbol == currencyName }
+ }
+
+ fun getSelectedWalletData(): WalletData? {
+ return wallets.find { it.currencyData.currencySymbol == selectedWallet }
+ }
+
+ fun canBeRemoved(walletData: WalletData?): Boolean {
+ if (walletData == null) return false
+
+ if (walletData.blockchain != store.state.walletState.primaryBlockchain
+ && (walletData.token == null || walletData.token != store.state.walletState.primaryToken)) {
+ val walletManager = getWalletManager(walletData.currencyData.currencySymbol)
+ ?: return true
+ val wallet = walletManager.wallet
+ if (walletData.blockchain != null) {
+ return wallet.recentTransactions.toPendingTransactions(wallet.address).isEmpty() &&
+ wallet.amounts.toSendableAmounts().isEmpty()
+ } else if (walletData.token != null) (
+ return wallet.recentTransactions.toPendingTransactionsForToken(
+ walletData.token, wallet.address).isEmpty()
+ && wallet.amounts[AmountType.Token(token = walletData.token)]?.isAboveZero() != true
+ )
+ }
+ return false
+ }
+
+ fun replaceWalletInWallets(walletData: WalletData?): List {
+ if (walletData == null) return wallets
+ return wallets.filter { it.currencyData.currency != walletData.currencyData.currency } + walletData
+ }
+
+ fun replaceSomeWallets(newWallets: List): List {
+ val currencies = newWallets.map { it.currencyData.currency }
+ return wallets.filter { !currencies.contains(it.currencyData.currency) } + newWallets
+ }
+
}
sealed class WalletDialog {
@@ -47,28 +101,14 @@ sealed class WalletDialog {
val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName?
) : WalletDialog()
- data class CreatePayIdDialog(val creatingPayIdState: CreatingPayIdState?) : WalletDialog()
data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog()
- data class WarningDialog(val type: WarningType) : WalletDialog()
- data class TwinsOnboardingFragment(val secondCardId: String): WalletDialog()
+ object ScanFailsDialog: WalletDialog()
}
-enum class WarningType { CardSignedHashesBefore, DevCard }
-
-
enum class ProgressState { Loading, Done, Error }
enum class ErrorType { NoInternetConnection }
-enum class PayIdState { Disabled, Loading, NotCreated, Created, ErrorLoading }
-
-data class PayIdData(
- val payIdState: PayIdState = PayIdState.Loading,
- val payId: String? = null
-)
-
-enum class CreatingPayIdState { EnterPayId, Waiting }
-
sealed class WalletMainButton(enabled: Boolean) : Button(enabled) {
class SendButton(enabled: Boolean) : WalletMainButton(enabled)
class CreateWalletButton(enabled: Boolean) : WalletMainButton(enabled)
@@ -83,7 +123,8 @@ data class AddressData(
val address: String,
val type: AddressType,
val shareUrl: String,
- val exploreUrl: String
+ val exploreUrl: String,
+ val qrCode: Bitmap? = null
)
data class Artwork(
@@ -112,4 +153,27 @@ data class TwinCardsState(
val cardNumber: TwinCardNumber?,
val showTwinOnboarding: Boolean,
val isCreatingTwinCardsAllowed: Boolean
-)
\ No newline at end of file
+)
+
+data class WalletData(
+ val pendingTransactions: List = emptyList(),
+ val hashesCountVerified: Boolean? = null,
+ val walletAddresses: WalletAddresses? = null,
+ val currencyData: BalanceWidgetData = BalanceWidgetData(),
+ val updatingWallet: Boolean = false,
+ val topUpState: TopUpState = TopUpState(),
+ val allowToSend: Boolean = true,
+ val fiatRateString: String? = null,
+ val fiatRate: BigDecimal? = null,
+ val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
+ val token: Token? = null,
+ val blockchain: Blockchain? = null,
+) {
+ fun shouldShowMultipleAddress(): Boolean {
+ val listOfAddresses = walletAddresses?.list ?: return false
+ return (currencyData.currencySymbol == Blockchain.Bitcoin.currency ||
+ currencyData.currencySymbol == Blockchain.BitcoinTestnet.currency ||
+ currencyData.currencySymbol == Blockchain.CardanoShelley.currency) &&
+ listOfAddresses.size > 1
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
new file mode 100644
index 0000000000..4f28ce1f3f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
@@ -0,0 +1,93 @@
+package com.tangem.tap.features.wallet.redux.middlewares
+
+import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.TokenFinder
+import com.tangem.blockchain.extensions.Result
+import com.tangem.tap.common.redux.global.GlobalState
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.currenciesRepository
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class MultiWalletMiddleware {
+ fun handle(
+ action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?
+ ) {
+ when (action) {
+ is WalletAction.MultiWallet.SelectWallet -> {
+ if (action.walletData != null) {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletDetails))
+ }
+ }
+ is WalletAction.MultiWallet.AddToken -> {
+ globalState?.scanNoteResponse?.card?.cardId?.let {
+ currenciesRepository.saveAddedToken(it, action.token)
+ }
+ addToken(action.token, walletState)
+ }
+ is WalletAction.MultiWallet.AddTokens -> {
+ action.tokens.map { addToken(it, walletState) }
+ }
+ is WalletAction.MultiWallet.AddBlockchain -> {
+ globalState?.scanNoteResponse?.card?.cardId?.let {
+ currenciesRepository.saveAddedBlockchain(it, action.blockchain)
+ }
+ store.dispatch(WalletAction.LoadFiatRate(currency = action.blockchain.currency))
+ }
+ is WalletAction.MultiWallet.RemoveWallet -> {
+ val cardId = globalState?.scanNoteResponse?.card?.cardId
+ if (action.walletData.token != null) {
+ cardId?.let { currenciesRepository.removeToken(it, action.walletData.token) }
+ } else if (action.walletData.blockchain != null) {
+ cardId?.let { currenciesRepository.removeBlockchain(it, action.walletData.blockchain) }
+ }
+ }
+ is WalletAction.MultiWallet.FindTokensInUse -> {
+ val walletManager = walletState?.getWalletManager(Blockchain.Ethereum.currency)
+ ?: return
+ val alreadyAddedTokens = walletManager.presetTokens
+ val tokenFinder = walletManager as TokenFinder
+ scope.launch {
+ val result = tokenFinder.findTokens()
+ withContext(Dispatchers.Main) {
+ when (result) {
+ is Result.Success -> {
+ if (result.data.isNotEmpty()) {
+ store.dispatch(WalletAction.MultiWallet.AddTokens(
+ walletManager.presetTokens
+ .filterNot { presetToken -> alreadyAddedTokens.any { it.symbol == presetToken.symbol } }
+ .toList()
+ ))
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun addToken(token: Token, walletState: WalletState?) {
+ val walletManager =
+ (walletState?.getWalletManager(Blockchain.Ethereum.currency) as? EthereumWalletManager)
+ scope.launch {
+ val result = walletManager?.addToken(token)
+ withContext(Dispatchers.Main) {
+ when (result) {
+ is Result.Success -> {
+ store.dispatch(WalletAction.LoadFiatRate(currency = token.symbol))
+ store.dispatch(WalletAction.MultiWallet.TokenLoaded(result.data))
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TopUpMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TopUpMiddleware.kt
new file mode 100644
index 0000000000..e0706570f7
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TopUpMiddleware.kt
@@ -0,0 +1,32 @@
+package com.tangem.tap.features.wallet.redux.middlewares
+
+import android.net.Uri
+import androidx.browser.customtabs.CustomTabsIntent
+import com.tangem.tap.domain.TopUpHelper
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+
+class TopUpMiddleware {
+ fun handle(action: WalletAction.TopUpAction) {
+ when (action) {
+ is WalletAction.TopUpAction.TopUp -> {
+ val selectedWalletData = store.state.walletState.getSelectedWalletData()
+ val config = store.state.globalState.configManager?.config ?: return
+ val addresses = selectedWalletData?.walletAddresses ?: return
+ if (addresses.list.isEmpty()) return
+
+ val defaultAddress = addresses.list[0].address
+ val url = TopUpHelper.getUrl(
+ selectedWalletData.currencyData.currencySymbol!!,
+ defaultAddress,
+ config.moonPayApiKey,
+ config.moonPayApiSecretKey
+ )
+ val customTabsIntent = CustomTabsIntent.Builder()
+ .setToolbarColor(action.toolbarColor)
+ .build()
+ customTabsIntent.launchUrl(action.context, Uri.parse(url));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt
new file mode 100644
index 0000000000..88ba60d1d1
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt
@@ -0,0 +1,19 @@
+package com.tangem.tap.features.wallet.redux.middlewares
+
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.preferencesStorage
+import com.tangem.tap.store
+
+class TwinsMiddleware {
+ fun handle(action: WalletAction.TwinsAction) {
+ when (action) {
+ is WalletAction.TwinsAction.SetTwinCard -> {
+ val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
+ if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding)
+ }
+ WalletAction.TwinsAction.SetOnboardingShown -> {
+ preferencesStorage.saveTwinsOnboardingShown()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
new file mode 100644
index 0000000000..54a51ff45e
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
@@ -0,0 +1,244 @@
+package com.tangem.tap.features.wallet.redux.middlewares
+
+import android.content.Intent
+import android.net.Uri
+import androidx.core.content.ContextCompat
+import com.tangem.TangemSdkError
+import com.tangem.blockchain.common.*
+import com.tangem.common.CompletionResult
+import com.tangem.tap.*
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.common.extensions.copyToClipboard
+import com.tangem.tap.common.extensions.shareText
+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.extensions.toSendableAmounts
+import com.tangem.tap.domain.twins.TwinsHelper
+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.redux.ProgressState
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletData
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.network.NetworkStateChanged
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.rekotlin.Action
+import org.rekotlin.Middleware
+
+class WalletMiddleware {
+ private val topUpMiddleware = TopUpMiddleware()
+ private val twinsMiddleware = TwinsMiddleware()
+ private val warningsMiddleware = WarningsMiddleware()
+ private val multiWalletMiddleware = MultiWalletMiddleware()
+
+ val walletMiddleware: Middleware = { dispatch, state ->
+ { next ->
+ { action ->
+ val globalState = state()?.globalState
+ val walletState = state()?.walletState
+ when (action) {
+ is WalletAction.TopUpAction -> topUpMiddleware.handle(action)
+ is WalletAction.TwinsAction -> twinsMiddleware.handle(action)
+ is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
+ is WalletAction.MultiWallet ->
+ multiWalletMiddleware.handle(action, walletState, globalState)
+
+ is WalletAction.LoadWallet -> {
+ scope.launch {
+ if (action.currency == null) {
+ walletState?.walletManagers?.map { walletManager ->
+ globalState?.tapWalletManager?.loadWalletData(walletManager)
+ }
+ } else {
+ val walletManager = walletState?.getWalletManager(action.currency)
+ walletManager?.let { globalState?.tapWalletManager?.loadWalletData(it) }
+ }
+ }
+ }
+ is WalletAction.LoadWallet.Success -> {
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
+ warningsMiddleware.tryToShowAppRatingWarning(action.wallet)
+ }
+ is WalletAction.LoadFiatRate -> {
+ scope.launch {
+ if (action.wallet != null) {
+ globalState?.tapWalletManager?.loadFiatRate(
+ globalState.appCurrency, action.wallet
+ )
+ } else if (action.currency != null) {
+ globalState?.tapWalletManager?.loadFiatRate(
+ globalState.appCurrency, action.currency
+ )
+ } else {
+ walletState?.wallets?.filter { it.blockchain != null }
+ ?.map { walletState.getWalletManager(it.blockchain?.currency) }
+ ?.mapNotNull { walletManager ->
+ walletManager?.let {
+ globalState?.tapWalletManager?.loadFiatRate(
+ globalState.appCurrency, walletManager.wallet
+ )
+ }
+ }
+ }
+ }
+ }
+ is WalletAction.CreateWallet -> {
+ if (walletState?.twinCardsState != null) {
+ store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning(
+ globalState?.scanNoteResponse?.card?.cardId?.let {
+ TwinsHelper.getTwinCardNumber(it)
+ },
+ CreateTwinWallet.CreateWallet
+ ))
+ } else {
+ scope.launch {
+ val result = tangemSdkManager.createWallet(
+ globalState?.scanNoteResponse?.card?.cardId
+ )
+ when (result) {
+ is CompletionResult.Success -> {
+ globalState?.tapWalletManager?.onCardScanned(result.data)
+ }
+ }
+ }
+ }
+ }
+ is WalletAction.UpdateWallet -> {
+ if (action.currency != null) {
+ scope.launch {
+ val walletManager = walletState?.getWalletManager(action.currency)
+ walletManager?.let { globalState?.tapWalletManager?.updateWallet(it) }
+ }
+ } else {
+ scope.launch {
+ if (walletState?.state == ProgressState.Done) {
+ walletState.walletManagers.map { walletManager ->
+ globalState?.tapWalletManager?.updateWallet(walletManager)
+ }
+ }
+ }
+ }
+
+ }
+ is WalletAction.Scan -> {
+ scope.launch {
+ val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler)
+ when (result) {
+ is CompletionResult.Success -> {
+ tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
+ globalState?.tapWalletManager
+ ?.onCardScanned(result.data, true)
+ if (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) {
+ 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 {
+ val scanNoteResponse = globalState?.scanNoteResponse
+ ?: return@launch
+ if (!walletState?.wallets.isNullOrEmpty()) {
+ globalState.tapWalletManager.reloadData(scanNoteResponse)
+ } else {
+ globalState.tapWalletManager.loadData(scanNoteResponse)
+ }
+ }
+ }
+ is NetworkStateChanged -> {
+ globalState?.scanNoteResponse?.let { scanNoteResponse ->
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
+ scope.launch {
+ globalState.tapWalletManager.loadData(scanNoteResponse)
+ }
+ }
+ }
+ is WalletAction.CopyAddress -> {
+ action.context.copyToClipboard(action.address)
+ store.dispatch(WalletAction.CopyAddress.Success)
+ }
+ is WalletAction.ShareAddress -> {
+ action.context.shareText(action.address)
+ }
+ is WalletAction.ExploreAddress -> {
+ val uri = Uri.parse(action.exploreUrl)
+ val intent = Intent(Intent.ACTION_VIEW, uri)
+ ContextCompat.startActivity(action.context, intent, null)
+ }
+ is WalletAction.Send -> {
+ val newAction = prepareSendAction(action.amount, state()?.walletState)
+ store.dispatch(newAction)
+ if (newAction is PrepareSendScreen) {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
+ }
+ }
+ }
+ next(action)
+ }
+ }
+ }
+
+ private fun prepareSendAction(amount: Amount?, state: WalletState?): Action {
+ val selectedWalletData = state?.getSelectedWalletData()
+ val currency = selectedWalletData?.currencyData?.currencySymbol
+ val wallet = currency?.let { state.getWalletManager(currency)?.wallet }
+
+ return if (amount != null) {
+ if (amount.type is AmountType.Token) {
+ prepareSendActionForToken(amount, state, selectedWalletData, wallet)
+ } else {
+ PrepareSendScreen(amount, selectedWalletData?.fiatRate)
+ }
+ } else {
+ val amounts = wallet?.amounts?.toSendableAmounts()
+ if (currency != null && state.isMultiwalletAllowed) {
+ val amountToSend = amounts?.find { it.currencySymbol == currency }
+ ?: return WalletAction.Send.ChooseCurrency(amounts)
+ if (amountToSend.type is AmountType.Token) {
+ prepareSendActionForToken(amount, state, selectedWalletData, wallet)
+ } else {
+ PrepareSendScreen(amountToSend, selectedWalletData.fiatRate)
+ }
+ } else {
+ if (amounts?.size ?: 0 > 1) {
+ WalletAction.Send.ChooseCurrency(amounts)
+ } else {
+ val amountToSend = amounts?.first()
+ PrepareSendScreen(amountToSend, selectedWalletData?.fiatRate)
+ }
+ }
+ }
+ }
+
+ private fun prepareSendActionForToken(
+ amount: Amount?, state: WalletState?, selectedWalletData: WalletData?, wallet: Wallet?
+ ): PrepareSendScreen {
+ val coinRate = state?.getWalletData(wallet?.blockchain?.currency)?.fiatRate
+ val tokenRate = if (state?.isMultiwalletAllowed == true) {
+ selectedWalletData?.fiatRate
+ } else {
+ selectedWalletData?.currencyData?.token?.fiatRate
+ }
+ return PrepareSendScreen(
+ wallet?.amounts?.get(AmountType.Coin), coinRate,
+ amount, tokenRate)
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
new file mode 100644
index 0000000000..347dce4c7f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
@@ -0,0 +1,128 @@
+package com.tangem.tap.features.wallet.redux.middlewares
+
+import com.tangem.blockchain.common.BlockchainSdkError
+import com.tangem.blockchain.common.SignatureCountValidator
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.extensions.SimpleResult
+import com.tangem.commands.common.card.Card
+import com.tangem.commands.common.card.CardType
+import com.tangem.common.extensions.getType
+import com.tangem.tap.common.analytics.AnalyticsEvent
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.common.extensions.isGreaterThan
+import com.tangem.tap.common.redux.global.GlobalState
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
+import com.tangem.tap.domain.isMultiwalletAllowed
+import com.tangem.tap.domain.twins.isTwinCard
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.network.NetworkConnectivity
+import com.tangem.tap.preferencesStorage
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.math.BigDecimal
+
+class WarningsMiddleware {
+ fun handle(action: WalletAction.Warnings, globalState: GlobalState?) {
+ when (action) {
+ is WalletAction.Warnings.CheckIfNeeded -> {
+ val validator = globalState?.scanNoteResponse?.walletManager as? SignatureCountValidator
+ globalState?.scanNoteResponse?.card?.let { card ->
+ 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()
+ }
+ val readyToShow = preferencesStorage.appRatingLaunchObserver.isReadyToShow()
+ if (readyToShow) addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
+ }
+ is WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline -> checkHashesCountOnline()
+ is WalletAction.Warnings.CheckHashesCount.SaveCardId -> {
+ val cardId = globalState?.scanNoteResponse?.card?.cardId
+ cardId?.let { preferencesStorage.saveScannedCardId(it) }
+ }
+ is WalletAction.Warnings.AppRating.RemindLater -> {
+ preferencesStorage.appRatingLaunchObserver.applyDelayedShowing()
+ }
+ is WalletAction.Warnings.AppRating.SetNeverToShow -> {
+ preferencesStorage.appRatingLaunchObserver.setNeverToShow()
+ }
+ }
+ }
+
+ 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 checkIfWarningNeeded(
+ card: Card, signatureCountValidator: SignatureCountValidator? = null,
+ ): WarningMessage? {
+ if (card.isTwinCard() || card.isMultiwalletAllowed) return null
+
+ return if (signatureCountValidator == null) {
+ if (card.walletSignedHashes ?: 0 > 0) {
+ WarningMessagesManager.alreadySignedHashesWarning()
+ } else {
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
+ null
+ }
+ } else {
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.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() || card.isMultiwalletAllowed) 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.Warnings.CheckHashesCount.ConfirmHashesCount)
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.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)))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
new file mode 100644
index 0000000000..0ce62a948d
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
@@ -0,0 +1,161 @@
+package com.tangem.tap.features.wallet.redux.reducers
+
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.common.extensions.isZero
+import com.tangem.tap.common.extensions.toFiatString
+import com.tangem.tap.common.extensions.toFormattedCurrencyString
+import com.tangem.tap.domain.getFirstToken
+import com.tangem.tap.features.wallet.models.removeUnknownTransactions
+import com.tangem.tap.features.wallet.models.toPendingTransactions
+import com.tangem.tap.features.wallet.redux.*
+import com.tangem.tap.features.wallet.ui.BalanceStatus
+import com.tangem.tap.features.wallet.ui.BalanceWidgetData
+import com.tangem.tap.features.wallet.ui.TokenData
+import com.tangem.tap.store
+
+class MultiWalletReducer {
+ fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState {
+ return when (action) {
+ is WalletAction.MultiWallet.AddWalletManagers -> {
+ state.copy(
+ walletManagers = action.walletManagers
+ )
+ }
+ is WalletAction.MultiWallet.AddBlockchains -> {
+ val wallets = action.blockchains.map { blockchain ->
+ val wallet = state.getWalletManager(blockchain.currency)?.wallet
+ val cardToken = if (!state.isMultiwalletAllowed) {
+ wallet?.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
+ } else {
+ null
+ }
+ WalletData(
+ currencyData = BalanceWidgetData(
+ BalanceStatus.Loading,
+ blockchain.fullName,
+ currencySymbol = blockchain.currency,
+ token = cardToken
+ ),
+ walletAddresses = createAddressList(wallet),
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(allowed = false),
+ blockchain = blockchain
+ )
+ }
+
+ val selectedWallet = if (!state.isMultiwalletAllowed) {
+ wallets[0].currencyData.currencySymbol
+ } else {
+ state.selectedWallet
+ }
+ state.copy(
+ wallets = wallets,
+ selectedWallet = selectedWallet
+ )
+ }
+ is WalletAction.MultiWallet.AddBlockchain -> {
+ val wallet = state.getWalletManager(action.blockchain.currency)?.wallet
+ val walletData = WalletData(
+ currencyData = BalanceWidgetData(
+ BalanceStatus.Loading,
+ action.blockchain.fullName,
+ currencySymbol = action.blockchain.currency,
+ ),
+ walletAddresses = createAddressList(wallet),
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(allowed = false),
+ blockchain = action.blockchain
+ )
+ val newState = state.copy(wallets = state.replaceWalletInWallets(walletData))
+ if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) {
+ OnWalletLoadedReducer().reduce(wallet, newState)
+ } else {
+ newState
+ }
+ }
+ is WalletAction.MultiWallet.AddTokens -> {
+ if (!state.isMultiwalletAllowed) return state
+ val walletAddresses = createAddressList(state.getWalletManager(Blockchain.Ethereum.currency)?.wallet)
+ val wallets = action.tokens.map { token ->
+ WalletData(
+ currencyData = BalanceWidgetData(
+ BalanceStatus.Loading,
+ currency = token.name,
+ currencySymbol = token.symbol
+ ),
+ walletAddresses = walletAddresses,
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(allowed = false),
+ token = token
+ )
+ }
+ state.copy(wallets = state.wallets + wallets)
+ }
+ is WalletAction.MultiWallet.AddToken -> {
+ if (!state.isMultiwalletAllowed) return state
+ val walletAddresses = createAddressList(state.getWalletManager(Blockchain.Ethereum.currency)?.wallet)
+ val wallet = WalletData(
+ currencyData = BalanceWidgetData(
+ BalanceStatus.Loading,
+ currency = action.token.name,
+ currencySymbol = action.token.symbol
+ ),
+ walletAddresses = walletAddresses,
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(allowed = false),
+ token = action.token
+ )
+ val wallets = state.replaceWalletInWallets(wallet)
+ state.copy(wallets = wallets)
+ }
+ is WalletAction.MultiWallet.TokenLoaded -> {
+ val pendingTransactions = state.getWalletManager(Blockchain.Ethereum.currency)
+ ?.wallet?.let { wallet ->
+ wallet.recentTransactions.toPendingTransactions(wallet.address)
+ } ?: emptyList()
+
+ val sendButtonEnabled = action.amount.value?.isZero() == false && pendingTransactions.isEmpty()
+ val balanceStatus = if (pendingTransactions.isNotEmpty()) {
+ BalanceStatus.TransactionInProgress
+ } else {
+ BalanceStatus.VerifiedOnline
+ }
+ val tokenWalletData = state.getWalletData(action.amount.currencySymbol)
+ val newTokenWalletData = tokenWalletData?.copy(
+ currencyData = tokenWalletData.currencyData.copy(
+ status = balanceStatus,
+ amount = action.amount.value?.toFormattedCurrencyString(
+ action.amount.decimals, action.amount.currencySymbol
+ ),
+ fiatAmount = tokenWalletData.fiatRate?.let {
+ action.amount.value
+ ?.toFiatString(it, store.state.globalState.appCurrency)
+ }
+ ),
+ pendingTransactions = pendingTransactions.removeUnknownTransactions(),
+ mainButton = WalletMainButton.SendButton(sendButtonEnabled)
+ )
+ val wallets = state.replaceWalletInWallets(newTokenWalletData)
+ state.copy(wallets = wallets)
+ }
+ is WalletAction.MultiWallet.SetIsMultiwalletAllowed ->
+ state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed)
+
+ is WalletAction.MultiWallet.SelectWallet ->
+ state.copy(selectedWallet = action.walletData?.currencyData?.currencySymbol)
+
+ is WalletAction.MultiWallet.RemoveWallet -> {
+ state.copy(wallets = state.wallets.filterNot {
+ it.currencyData.currencySymbol == action.walletData.currencyData.currencySymbol
+ })
+ }
+ is WalletAction.MultiWallet.SetPrimaryBlockchain ->
+ state.copy(primaryBlockchain = action.blockchain)
+
+ is WalletAction.MultiWallet.SetPrimaryToken ->
+ state.copy(primaryToken = action.token)
+ is WalletAction.MultiWallet.FindTokensInUse -> state
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
new file mode 100644
index 0000000000..59edbdd896
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
@@ -0,0 +1,182 @@
+package com.tangem.tap.features.wallet.redux.reducers
+
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Wallet
+import com.tangem.common.extensions.isZero
+import com.tangem.tap.common.extensions.toFiatString
+import com.tangem.tap.common.extensions.toFormattedCurrencyString
+import com.tangem.tap.common.extensions.toFormattedString
+import com.tangem.tap.domain.getFirstToken
+import com.tangem.tap.features.wallet.models.removeUnknownTransactions
+import com.tangem.tap.features.wallet.models.toPendingTransactions
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.tap.features.wallet.redux.WalletData
+import com.tangem.tap.features.wallet.redux.WalletMainButton
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.ui.BalanceStatus
+import com.tangem.tap.features.wallet.ui.BalanceWidgetData
+import com.tangem.tap.features.wallet.ui.TokenData
+import com.tangem.tap.store
+import java.math.RoundingMode
+
+class OnWalletLoadedReducer {
+ fun reduce(wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null): WalletState {
+ if (!walletState.isMultiwalletAllowed) {
+ return onSingleWalletLoaded(wallet, walletState, topUpAllowed)
+ } else {
+ val fiatCurrencySymbol = store.state.globalState.appCurrency
+ val amount = wallet.amounts[AmountType.Coin]?.value
+ if (walletState.getWalletData(wallet.blockchain.currency) == null && amount?.isZero() != false) {
+ return walletState
+ }
+ if (wallet.blockchain != Blockchain.Ethereum) {
+ val formattedAmount = amount?.toFormattedCurrencyString(
+ wallet.blockchain.decimals(),
+ wallet.blockchain.currency)
+
+ val pendingTransactions = wallet.recentTransactions
+ .toPendingTransactions(wallet.address)
+
+ val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
+ val balanceStatus = if (pendingTransactions.isNotEmpty()) {
+ BalanceStatus.TransactionInProgress
+ } else {
+ BalanceStatus.VerifiedOnline
+ }
+ val walletData = walletState.getWalletData(wallet.blockchain.currency)
+ ?: WalletData()
+ val newWalletData = walletData.copy(
+ currencyData = walletData.currencyData.copy(
+ status = balanceStatus, currency = wallet.blockchain.fullName,
+ currencySymbol = wallet.blockchain.currency,
+ amount = formattedAmount,
+ fiatAmount = walletData.fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
+ ),
+ pendingTransactions = pendingTransactions.removeUnknownTransactions(),
+ mainButton = WalletMainButton.SendButton(sendButtonEnabled)
+ )
+ val wallets = walletState.replaceWalletInWallets(newWalletData)
+ val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
+ ProgressState.Loading
+ } else {
+ ProgressState.Done
+ }
+ return walletState.copy(
+ state = state, wallets = wallets
+ )
+ } else {
+ val formattedAmount = amount?.toFormattedCurrencyString(
+ wallet.blockchain.decimals(),
+ wallet.blockchain.currency)
+
+ val pendingTransactions = wallet.recentTransactions
+ .toPendingTransactions(wallet.address)
+
+ val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
+ val balanceStatus = if (pendingTransactions.isNotEmpty()) {
+ BalanceStatus.TransactionInProgress
+ } else {
+ BalanceStatus.VerifiedOnline
+ }
+
+ val ethereumWalletData = walletState.getWalletData(wallet.blockchain.currency)
+ ?: WalletData()
+ val newEthereumWalletData = ethereumWalletData.copy(
+ currencyData = ethereumWalletData.currencyData.copy(
+ status = balanceStatus,
+ amount = formattedAmount,
+ currency = wallet.blockchain.fullName,
+ currencySymbol = wallet.blockchain.currency,
+ fiatAmount = ethereumWalletData.fiatRate?.let {
+ amount?.toFiatString(it, fiatCurrencySymbol)
+ }
+ ),
+ pendingTransactions = pendingTransactions.removeUnknownTransactions(),
+ mainButton = WalletMainButton.SendButton(sendButtonEnabled)
+ )
+
+ val tokens = wallet.getTokens().mapNotNull { token ->
+ val tokenWalletData = walletState.getWalletData(token.symbol)
+ tokenWalletData?.copy(
+ currencyData = tokenWalletData.currencyData.copy(
+ status = balanceStatus,
+ amount = wallet.getTokenAmount(token)?.value?.toFormattedCurrencyString(
+ token.decimals, token.symbol
+ ),
+ fiatAmount = tokenWalletData.fiatRate?.let {
+ wallet.getTokenAmount(token)?.value
+ ?.toFiatString(it, fiatCurrencySymbol)
+ }
+ ),
+ pendingTransactions = pendingTransactions.removeUnknownTransactions(),
+ mainButton = WalletMainButton.SendButton(sendButtonEnabled)
+ )
+ }
+ val wallets = walletState.replaceSomeWallets((tokens + newEthereumWalletData).filterNotNull())
+ val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
+ ProgressState.Loading
+ } else {
+ ProgressState.Done
+ }
+ return walletState.copy(
+ state = state, wallets = wallets
+ )
+ }
+ }
+ }
+
+ private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null
+ ): WalletState {
+ if (wallet.blockchain != walletState.primaryBlockchain) return walletState
+
+ val fiatCurrencySymbol = store.state.globalState.appCurrency
+ val token = wallet.getFirstToken()
+ val tokenData = if (token != null) {
+ val tokenAmount = wallet.getTokenAmount(token)
+ if (tokenAmount != null) {
+ val tokenFiatRate = walletState.primaryWallet?.currencyData?.token?.fiatRate
+ val tokenFiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
+ TokenData(tokenAmount.value?.toFormattedString(tokenAmount.decimals) ?: "",
+ tokenAmount.currencySymbol, tokenFiatAmount)
+ } else {
+ null
+ }
+ } else {
+ null
+ }
+ val amount = wallet.amounts[AmountType.Coin]?.value
+ val formattedAmount = amount?.toFormattedCurrencyString(
+ wallet.blockchain.decimals(),
+ wallet.blockchain.currency)
+ val fiatRate = walletState.primaryWallet?.fiatRate
+ val fiatAmountRaw = fiatRate?.multiply(amount)?.setScale(2, RoundingMode.DOWN)
+ val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
+
+ val pendingTransactions = wallet.recentTransactions
+ .toPendingTransactions(wallet.address)
+
+ val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
+ val balanceStatus = if (pendingTransactions.isNotEmpty()) {
+ BalanceStatus.TransactionInProgress
+ } else {
+ BalanceStatus.VerifiedOnline
+ }
+ val walletData = walletState.primaryWallet?.copy(
+ currencyData = BalanceWidgetData(
+ balanceStatus, wallet.blockchain.fullName,
+ currencySymbol = wallet.blockchain.currency,
+ formattedAmount,
+ token = tokenData,
+ fiatAmount = fiatAmount,
+ fiatAmountRaw = fiatAmountRaw
+ ),
+ pendingTransactions = pendingTransactions.removeUnknownTransactions(),
+ mainButton = WalletMainButton.SendButton(sendButtonEnabled)
+ )
+ val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
+ return walletState.copy(
+ state = ProgressState.Done, wallets = wallets
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt
new file mode 100644
index 0000000000..79025ca6cc
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt
@@ -0,0 +1,36 @@
+package com.tangem.tap.features.wallet.redux.reducers
+
+import com.tangem.tap.features.wallet.redux.TwinCardsState
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletState
+
+class TwinsReducer {
+ fun reduce(action: WalletAction.TwinsAction, state: WalletState): WalletState {
+ return when (action) {
+ is WalletAction.TwinsAction.SetTwinCard -> {
+ state.copy(
+ twinCardsState = TwinCardsState(
+ secondCardId = action.secondCardId,
+ cardNumber = action.number,
+ showTwinOnboarding = state.twinCardsState?.showTwinOnboarding
+ ?: false,
+ isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed
+ )
+ )
+ }
+ is WalletAction.TwinsAction.ShowOnboarding -> {
+ state.copy(
+ twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = true)
+ ?: TwinCardsState(null, null,
+ showTwinOnboarding = true,
+ isCreatingTwinCardsAllowed = false)
+ )
+ }
+ is WalletAction.TwinsAction.SetOnboardingShown -> {
+ state.copy(
+ twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = false)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
new file mode 100644
index 0000000000..f70ba93b11
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
@@ -0,0 +1,373 @@
+package com.tangem.tap.features.wallet.redux.reducers
+
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Wallet
+import com.tangem.commands.common.network.TangemService
+import com.tangem.common.extensions.toHexString
+import com.tangem.tap.common.extensions.toFiatString
+import com.tangem.tap.common.extensions.toFormattedCurrencyString
+import com.tangem.tap.common.extensions.toQrCode
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.common.redux.global.FiatCurrencyName
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.getFirstToken
+import com.tangem.tap.domain.twins.TwinCardNumber
+import com.tangem.tap.features.wallet.redux.*
+import com.tangem.tap.features.wallet.ui.BalanceStatus
+import com.tangem.tap.features.wallet.ui.BalanceWidgetData
+import org.rekotlin.Action
+import java.math.BigDecimal
+
+class WalletReducer {
+ companion object {
+ fun reduce(action: Action, state: AppState): WalletState = internalReduce(action, state)
+ }
+}
+
+private fun internalReduce(action: Action, state: AppState): WalletState {
+
+ val multiWalletReducer = MultiWalletReducer()
+ val twinsReducer = TwinsReducer()
+ val onWalletLoadedReducer = OnWalletLoadedReducer()
+
+ if (action !is WalletAction) return state.walletState
+
+ var newState = state.walletState
+
+ when (action) {
+ is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState)
+ is WalletAction.TwinsAction -> newState = twinsReducer.reduce(action, newState)
+ is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState)
+
+ is WalletAction.ResetState -> newState = WalletState()
+ is WalletAction.EmptyWallet -> {
+ val creatingWalletAllowed = !(newState.twinCardsState != null &&
+ newState.twinCardsState?.isCreatingTwinCardsAllowed != true)
+
+ newState = newState.copy(
+ state = ProgressState.Done,
+ wallets = listOf(WalletData(
+ currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
+ mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed),
+ topUpState = TopUpState(false)
+ )))
+ }
+ is WalletAction.LoadData.Failure -> {
+ when (action.error) {
+ is TapError.NoInternetConnection -> {
+ newState = newState.copy(
+ state = ProgressState.Error,
+ error = ErrorType.NoInternetConnection,
+ )
+ }
+ is TapError.UnknownBlockchain -> {
+ newState = newState.copy(
+ state = ProgressState.Done,
+ wallets = listOf(WalletData(
+ currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
+ topUpState = TopUpState(false)
+ ))
+ )
+ }
+ }
+ }
+
+ is WalletAction.LoadData -> {
+ newState = newState.copy(
+ state = ProgressState.Loading,
+ error = null,
+ )
+ }
+ is WalletAction.LoadWallet -> {
+ if (action.currency == null) {
+ val wallets = newState.wallets.map { wallet ->
+ wallet.copy(
+ currencyData = wallet.currencyData.copy(
+ status = BalanceStatus.Loading,
+ currency = wallet.currencyData.currency,
+ currencySymbol = wallet.currencyData.currencySymbol,
+ ),
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(allowed = action.allowTopUp
+ ?: wallet.topUpState.allowed)
+ )
+ }
+ newState = newState.copy(
+ state = ProgressState.Loading,
+ wallets = wallets
+ )
+ } else {
+ val walletManager = newState.getWalletManager(action.currency) ?: return newState
+ val currencies = listOf(walletManager.wallet.blockchain.currency + walletManager.presetTokens.map { it.symbol })
+ val newWallets = newState.wallets.filter { currencies.contains(it.currencyData.currencySymbol) }
+ .map { wallet ->
+ wallet.copy(
+ currencyData = wallet.currencyData.copy(
+ status = BalanceStatus.Loading,
+ currency = wallet.currencyData.currency,
+ currencySymbol = wallet.currencyData.currencySymbol,
+ ),
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(
+ allowed = action.allowTopUp ?: wallet.topUpState.allowed
+ )
+ )
+ }
+ val wallets = newState.replaceSomeWallets(newWallets)
+ newState = newState.copy(wallets = wallets)
+ }
+
+ }
+ is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce(action.wallet, newState)
+ is WalletAction.UpdateWallet.Success -> {
+ newState = onWalletLoadedReducer.reduce(action.wallet, newState)
+// newState = newState.copy(updatingWallet = newState.pendingTransactions.isNotEmpty())
+ }
+
+ is WalletAction.LoadWallet.NoAccount -> {
+ val walletData = newState.getWalletData(action.wallet.blockchain.currency)?.copy(
+ currencyData = BalanceWidgetData(
+ BalanceStatus.NoAccount, action.wallet.blockchain.fullName,
+ currencySymbol = action.wallet.blockchain.currency,
+ amountToCreateAccount = action.amountToCreateAccount
+ )
+ )
+ val wallets = newState.replaceWalletInWallets(walletData)
+ val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
+ ProgressState.Loading
+ } else {
+ ProgressState.Done
+ }
+ newState = newState.copy(
+ state = state,
+ wallets = wallets
+
+ )
+ }
+ is WalletAction.LoadWallet.Failure -> {
+ val walletData = newState.getWalletData(action.wallet.blockchain.currency)
+ val newWalletData = walletData?.copy(
+ currencyData = walletData.currencyData.copy(
+ status = BalanceStatus.Unreachable,
+ errorMessage = action.errorMessage
+ ),
+ topUpState = TopUpState(false)
+ )
+ val wallets = newState.replaceWalletInWallets(newWalletData)
+ val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
+ ProgressState.Loading
+ } else {
+ ProgressState.Done
+ }
+ newState = newState.copy(
+ state = state, wallets = wallets
+ )
+ }
+ is WalletAction.SetArtworkId -> {
+ val cardImage = if (newState.cardImage?.artworkId == action.artworkId) {
+ newState.cardImage
+ } else {
+ null
+ }
+ newState = newState.copy(cardImage = cardImage)
+ }
+
+// is WalletAction.UpdateWallet -> {
+// if (store.state.walletState.state == ProgressState.Done) {
+// newState = newState.copy(updatingWallet = true)
+// }
+// }
+// is WalletAction.UpdateWallet.ScheduleUpdatingWallet ->
+// newState = newState.copy(updatingWallet = true)
+
+// is WalletAction.UpdateWallet.Failure -> newState = newState.copy(updatingWallet = false)
+// is WalletAction.LoadFiatRate -> {
+// newState.copy(currencyData = newState.currencyData.copy(
+// fiatAmount = null,
+// token = newState.currencyData.token?.copy(fiatAmount = null))
+// )
+// }
+ is WalletAction.LoadFiatRate.Success ->
+ newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState)
+ is WalletAction.LoadArtwork -> {
+ val cardId = action.card.cardId
+ val cardPublicKey = action.card.cardPublicKey?.toHexString()
+ val artworkUrl = if (cardPublicKey != null && action.artworkId != null) {
+ TangemService.getUrlForArtwork(cardId, cardPublicKey, action.artworkId)
+ } else if (action.card.cardId.startsWith(Artwork.SERGIO_CARD_ID)) {
+ Artwork.SERGIO_CARD_URL
+ } else if (action.card.cardId.startsWith(Artwork.MARTA_CARD_ID)) {
+ Artwork.MARTA_CARD_URL
+ } else if (newState.twinCardsState?.cardNumber != null) {
+ when (newState.twinCardsState?.cardNumber) {
+ TwinCardNumber.First -> Artwork.TWIN_CARD_1
+ TwinCardNumber.Second -> Artwork.TWIN_CARD_2
+ null -> Artwork.DEFAULT_IMG_URL
+ }
+ } else {
+ Artwork.DEFAULT_IMG_URL
+ }
+ newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl))
+ }
+ is WalletAction.ShowDialog.QrCode -> {
+ val selectedWalletData = newState.getWalletData(newState.selectedWallet)
+ newState = newState.copy(
+ walletDialog = WalletDialog.QrDialog(
+ selectedWalletData?.walletAddresses?.selectedAddress?.shareUrl?.toQrCode(),
+ selectedWalletData?.walletAddresses?.selectedAddress?.shareUrl,
+ selectedWalletData?.currencyData?.currency
+ )
+ )
+ }
+ is WalletAction.ShowDialog.ScanFails -> {
+ newState = newState.copy(walletDialog = WalletDialog.ScanFailsDialog)
+ }
+ is WalletAction.HideDialog -> {
+ newState = newState.copy(walletDialog = null)
+ }
+ is WalletAction.Send.ChooseCurrency -> {
+ newState = newState.copy(
+ walletDialog = WalletDialog.SelectAmountToSendDialog(action.amounts)
+ )
+ }
+ is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null)
+ is WalletAction.TopUpAction -> return newState
+ is WalletAction.ChangeSelectedAddress -> {
+ val selectedWalletData = newState.getWalletData(newState.selectedWallet)
+
+ val walletAddresses = newState.getWalletData(selectedWalletData?.currencyData?.currencySymbol)?.walletAddresses
+ ?: return newState
+ val address = walletAddresses.list.firstOrNull { it.type == action.type }
+ ?: return newState
+ val wallets = newState.replaceWalletInWallets(
+ selectedWalletData?.copy(walletAddresses = WalletAddresses(address, walletAddresses.list))
+ )
+ 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
+}
+
+fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? {
+ if (wallet == null) return null
+
+ val listOfAddressData = mutableListOf()
+ // put a defaultAddress at the first place
+ wallet.addresses.forEach {
+ val addressData = AddressData(it.value, it.type, wallet.getShareUri(it.value), wallet.getExploreUrl(it.value))
+ if (it.type == wallet.blockchain.defaultAddressType()) {
+ listOfAddressData.add(0, addressData)
+ } else {
+ listOfAddressData.add(addressData)
+ }
+ }
+
+ // restore a selected wallet address
+ var indexOfSelectedWallet = 0
+ walletAddresses?.let {
+ val index = listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address }
+ if (index != -1) indexOfSelectedWallet = index
+ }
+ return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData)
+}
+
+private fun handleCheckSignedHashesActions(action: WalletAction.Warnings, state: WalletState): WalletState {
+ return when (action) {
+ WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline -> state
+ WalletAction.Warnings.CheckHashesCount.ConfirmHashesCount -> state.copy(hashesCountVerified = true)
+ WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline -> state.copy(hashesCountVerified = false)
+ WalletAction.Warnings.CheckHashesCount.SaveCardId -> state
+ is WalletAction.Warnings.SetWarnings -> state.copy(mainWarningsList = action.warningList)
+ WalletAction.Warnings.CheckIfNeeded -> state
+ WalletAction.Warnings.AppRating -> state
+ WalletAction.Warnings.CheckHashesCount -> state
+ WalletAction.Warnings.AppRating.RemindLater -> state
+ WalletAction.Warnings.AppRating.SetNeverToShow -> state
+ }
+}
+
+
+private fun setNewFiatRate(
+ fiatRate: Pair,
+ appCurrency: FiatCurrencyName, state: WalletState
+): WalletState {
+ val rate = fiatRate.second ?: return state
+ val rateFormatted = rate.toFormattedCurrencyString(2, appCurrency)
+ val currency = fiatRate.first
+
+ if (!state.isMultiwalletAllowed) {
+ return setSingeWalletFiatRate(rate, rateFormatted, currency, appCurrency, state)
+ } else {
+ return setMultiWalletFiatRate(rate, rateFormatted, currency, appCurrency, state)
+ }
+}
+
+private fun setMultiWalletFiatRate(
+ rate: BigDecimal, rateFormatted: String, currency: CryptoCurrencyName,
+ appCurrency: FiatCurrencyName, state: WalletState
+): WalletState {
+ val walletData = state.getWalletData(currency) ?: return state
+ val wallet = state.walletManagers.find { it.wallet.blockchain.currency == currency }?.wallet
+ if (wallet != null) {
+ val newWalletData = state.getWalletData(currency)?.copy(
+ currencyData = walletData.currencyData.copy(
+ fiatAmount = wallet.amounts[AmountType.Coin]?.value
+ ?.toFiatString(rate, appCurrency)),
+ fiatRate = rate, fiatRateString = rateFormatted
+ )
+ return state.copy(wallets = state.replaceWalletInWallets(newWalletData))
+ } else {
+ val ethereumWallet = state.walletManagers.find { it.wallet.blockchain == Blockchain.Ethereum }?.wallet
+ val token = ethereumWallet?.getTokens()?.find { it.symbol == currency } ?: return state
+ val tokenFiatAmount = ethereumWallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency)
+ val newWalletData = state.getWalletData(currency)?.copy(
+ currencyData = walletData.currencyData.copy(
+ fiatAmount = tokenFiatAmount),
+ fiatRate = rate, fiatRateString = rateFormatted
+ )
+ return state.copy(wallets = state.replaceWalletInWallets(newWalletData))
+ }
+}
+
+private fun setSingeWalletFiatRate(
+ rate: BigDecimal, rateFormatted: String, currency: CryptoCurrencyName,
+ appCurrency: FiatCurrencyName, state: WalletState
+): WalletState {
+ val wallet = state.walletManagers[0].wallet
+ val token = wallet.getFirstToken()
+
+ if (currency == state.primaryWallet?.currencyData?.currencySymbol) {
+ val fiatAmount = wallet.amounts[AmountType.Coin]?.value
+ ?.toFiatString(rate, appCurrency)
+ val walletData = state.primaryWallet.copy(
+ currencyData = state.primaryWallet.currencyData.copy(fiatAmount = fiatAmount),
+ fiatRate = rate,
+ fiatRateString = rateFormatted
+ )
+ return state.copy(wallets = listOf(walletData))
+ } else if (currency == token?.symbol) {
+ val tokenFiatAmount = wallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency)
+ val tokenData = state.primaryWallet?.currencyData?.token?.copy(
+ fiatAmount = tokenFiatAmount,
+ fiatRate = rate,
+ fiatRateString = rateFormatted
+ )
+
+ val walletData = state.primaryWallet?.copy(
+ currencyData = state.primaryWallet.currencyData.copy(
+ token = tokenData
+ ))
+ val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
+ return state.copy(wallets = wallets)
+ }
+ return state
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt
index ce7fec6959..ed446c3ee5 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt
@@ -8,6 +8,7 @@ import com.tangem.wallet.R
import kotlinx.android.synthetic.main.card_balance.*
import kotlinx.android.synthetic.main.layout_balance.*
import kotlinx.android.synthetic.main.layout_balance_error.*
+import java.math.BigDecimal
enum class BalanceStatus {
VerifiedOnline,
@@ -25,6 +26,7 @@ data class BalanceWidgetData(
val currencySymbol: String? = null,
val amount: String? = null,
val fiatAmount: String? = null,
+ val fiatAmountRaw: BigDecimal? = null,
val token: TokenData? = null,
val amountToCreateAccount: String? = null,
val errorMessage: String? = null
@@ -33,14 +35,16 @@ data class BalanceWidgetData(
data class TokenData(
val amount: String,
val tokenSymbol: String,
- val fiatAmount: String? = null
+ val fiatAmount: String? = null,
+ val fiatRateString: String? = null,
+ val fiatRate: BigDecimal? = null,
)
class BalanceWidget(
val fragment: Fragment,
val data: BalanceWidgetData,
- val isTwinCard: Boolean,
+ private val isTwinCard: Boolean,
) {
fun setup() {
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt
new file mode 100644
index 0000000000..f7b06a5b5e
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt
@@ -0,0 +1,50 @@
+package com.tangem.tap.features.wallet.ui
+
+import android.view.View
+import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
+import com.tangem.blockchain.blockchains.cardano.CardanoAddressType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.address.AddressType
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.wallet.R
+
+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, currencyName: CryptoCurrencyName?): AddressType? {
+ val blockchain = currencyName?.let { currency ->
+ Blockchain.fromCurrency(currency)
+ }
+ return idToType(id, blockchain)
+ }
+
+ 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
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
new file mode 100644
index 0000000000..0cfff7bb46
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
@@ -0,0 +1,288 @@
+package com.tangem.tap.features.wallet.ui
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.*
+import androidx.activity.OnBackPressedCallback
+import androidx.annotation.IdRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.transition.TransitionInflater
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.wallet.models.PendingTransaction
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletData
+import com.tangem.tap.features.wallet.redux.WalletDialog
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
+import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.fragment_details_twin_cards.*
+import kotlinx.android.synthetic.main.fragment_wallet.*
+import kotlinx.android.synthetic.main.fragment_wallet_details.*
+import kotlinx.android.synthetic.main.fragment_wallet_details.rv_pending_transaction
+import kotlinx.android.synthetic.main.fragment_wallet_details.toolbar
+import kotlinx.android.synthetic.main.item_currency_wallet.view.*
+import kotlinx.android.synthetic.main.layout_balance_error.*
+import kotlinx.android.synthetic.main.layout_balance_wallet_details.*
+import kotlinx.android.synthetic.main.layout_wallet_details.*
+import org.rekotlin.StoreSubscriber
+
+class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreSubscriber {
+
+ private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
+ private var dialog: Dialog? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
+ store.dispatch(NavigationAction.PopBackTo())
+ }
+ })
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(R.transition.slide_right)
+ exitTransition = inflater.inflateTransition(R.transition.fade)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.walletState == newState.walletState
+ }.select { it.walletState }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ (activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
+ toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
+
+
+ setupTransactionsRecyclerView()
+ setupButtons()
+ }
+
+
+ private fun setupTransactionsRecyclerView() {
+ pendingTransactionAdapter = PendingTransactionsAdapter()
+ rv_pending_transaction.layoutManager = LinearLayoutManager(requireContext())
+ rv_pending_transaction.adapter = pendingTransactionAdapter
+ }
+
+ private fun setupButtons() {
+ btn_confirm.text = getString(R.string.wallet_button_send)
+ btn_confirm.setOnClickListener { store.dispatch(WalletAction.Send()) }
+
+ btn_share.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
+
+ btn_top_up.setOnClickListener {
+ store.dispatch(
+ WalletAction.TopUpAction.TopUp(requireContext(), R.color.backgroundLightGray)
+ )
+ }
+ }
+
+ override fun newState(state: WalletState) {
+ if (activity == null) return
+ if (state.selectedWallet == null) return
+ val selectedWallet = state.getSelectedWalletData() ?: return
+
+ tv_currency_title.text = selectedWallet.currencyData.currency
+
+ showPendingTransactionsIfPresent(selectedWallet.pendingTransactions)
+ setupAddressCard(selectedWallet)
+ setupBalanceData(selectedWallet.currencyData)
+
+ btn_confirm.isEnabled = selectedWallet.mainButton.enabled
+ btn_copy.setOnClickListener {
+ selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
+ store.dispatch(WalletAction.CopyAddress(addressString, requireContext()))
+ }
+ }
+ btn_share.setOnClickListener {
+ selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
+ store.dispatch(WalletAction.ShareAddress(addressString, requireContext()))
+ }
+ }
+
+ handleDialogs(state.walletDialog)
+ handleCurrencyIcon(selectedWallet)
+
+ srl_wallet_details.setOnRefreshListener {
+ if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
+ store.dispatch(WalletAction.LoadWallet(
+ selectedWallet.topUpState.allowed,
+ selectedWallet.currencyData.currencySymbol
+ ))
+ }
+ }
+
+ if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
+ srl_wallet_details.isRefreshing = false
+ }
+ }
+
+ private fun handleCurrencyIcon(wallet: WalletData) {
+ val blockchain = wallet.currencyData.currencySymbol?.let { Blockchain.fromCurrency(it) }
+ if (blockchain != null && blockchain != Blockchain.Unknown) {
+ tv_token_letter.text = null
+ iv_currency.colorFilter = null
+ iv_currency.setImageResource(blockchain.getIconRes())
+ } else {
+ tv_token_letter.text = wallet.currencyData.currencySymbol?.take(1)
+ wallet.token?.getColor()?.let { iv_currency.setColorFilter(it) }
+ iv_currency.setImageResource(R.drawable.shape_circle)
+ }
+ }
+
+
+ private fun showPendingTransactionsIfPresent(pendingTransactions: List) {
+ pendingTransactionAdapter.submitList(pendingTransactions)
+ rv_pending_transaction?.show(pendingTransactions.isNotEmpty())
+ }
+
+ private fun setupAddressCard(state: WalletData) {
+ if (state.walletAddresses != null) {
+ if (state.shouldShowMultipleAddress()) {
+ (card_balance as? ViewGroup)?.beginDelayedTransition()
+ chip_group_address_type.show()
+ chip_group_address_type.fitChipsByGroupWidth()
+
+ val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
+ if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
+
+ chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
+ if (checkedId == -1) return@setOnCheckedChangeListener
+ val type = MultipleAddressUiHelper.idToType(checkedId, state.currencyData.currencySymbol)
+ type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
+ }
+ } else {
+ chip_group_address_type.hide()
+ }
+ tv_address.text = state.walletAddresses.selectedAddress.address
+ tv_explore?.setOnClickListener {
+ store.dispatch(WalletAction.ExploreAddress(
+ state.walletAddresses.selectedAddress.exploreUrl,
+ requireContext()))
+ }
+ iv_qr_code.setImageBitmap(state.walletAddresses.selectedAddress.address.toQrCode())
+ }
+ }
+
+ private fun setupBalanceData(data: BalanceWidgetData) {
+ when (data.status) {
+ BalanceStatus.Loading -> {
+ l_balance.show()
+ l_balance_error.hide()
+ tv_fiat_amount.hide()
+ tv_amount.text = ""
+
+ showStatus(R.id.tv_status_loading)
+ showBalanceWithoutToken(data, false)
+ }
+ BalanceStatus.VerifiedOnline, BalanceStatus.TransactionInProgress -> {
+ l_balance.show()
+ l_balance_error.hide()
+ val statusView = if (data.status == BalanceStatus.VerifiedOnline) {
+ R.id.tv_status_verified
+ } else {
+ tv_status_error.text =
+ getText(R.string.wallet_balance_tx_in_progress)
+ R.id.group_error
+ }
+ showStatus(statusView)
+ tv_status_error_message.hide()
+ showBalanceWithoutToken(data, true)
+ }
+ BalanceStatus.Unreachable -> {
+ l_balance.show()
+ l_balance_error.hide()
+ tv_fiat_amount.hide()
+
+ tv_amount.text = ""
+
+ tv_status_error_message.text = data.errorMessage
+ tv_status_error.text =
+ getString(R.string.wallet_balance_blockchain_unreachable)
+
+ showStatus(R.id.group_error)
+ tv_status_error_message.show(!data.errorMessage.isNullOrBlank())
+ }
+
+ BalanceStatus.NoAccount -> {
+ l_balance.hide()
+ l_balance_error.show()
+ tv_error_title.text = getText(R.string.wallet_error_no_account)
+ tv_error_descriptions.text =
+ getString(
+ R.string.wallet_error_no_account_subtitle_format,
+ data.amountToCreateAccount, data.currencySymbol
+ )
+ }
+ }
+ }
+
+ private fun showStatus(@IdRes viewRes: Int) {
+ group_error.show(viewRes == R.id.group_error)
+ tv_status_loading.show(viewRes == R.id.tv_status_loading)
+ tv_status_verified.show(viewRes == R.id.tv_status_verified)
+ }
+
+ private fun showBalanceWithoutToken(data: BalanceWidgetData, showAmount: Boolean) {
+ tv_amount.text = if (showAmount) data.amount else ""
+ if (showAmount) {
+ tv_fiat_amount.show()
+ tv_fiat_amount.text = data.fiatAmount
+ }
+ }
+
+ private fun handleDialogs(walletDialog: WalletDialog?) {
+ when (walletDialog) {
+ is WalletDialog.SelectAmountToSendDialog -> {
+ if (dialog == null) dialog = AmountToSendDialog(requireContext()).apply {
+ this.show(walletDialog.amounts)
+ }
+ }
+ null -> {
+ dialog?.dismiss()
+ dialog = null
+ }
+ }
+ }
+
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.meny_remove -> {
+ store.state.walletState.getSelectedWalletData()?.let { walletData ->
+ store.dispatch(WalletAction.MultiWallet.RemoveWallet(walletData))
+ store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
+ store.dispatch(NavigationAction.PopBackTo())
+ true
+ }
+ false
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ if (store.state.walletState.canBeRemoved(store.state.walletState.getSelectedWalletData())) {
+ inflater.inflate(R.menu.wallet_details, menu)
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
index eedcf10ca5..ec1c7cdf74 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
@@ -1,48 +1,45 @@
package com.tangem.tap.features.wallet.ui
-import android.app.Dialog
import android.os.Bundle
-import android.view.*
+import android.view.Menu
import android.view.Menu.NONE
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionInflater
import com.google.android.material.snackbar.Snackbar
import com.squareup.picasso.Picasso
-import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
-import com.tangem.blockchain.blockchains.cardano.CardanoAddressType
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.address.AddressType
-import com.tangem.tap.common.extensions.*
+import com.tangem.tangem_sdk_new.extensions.dpToPx
+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.dialogs.AmountToSendDialog
-import com.tangem.tap.features.wallet.ui.dialogs.PayIdDialog
-import com.tangem.tap.features.wallet.ui.dialogs.QrDialog
-import com.tangem.tap.features.wallet.ui.dialogs.WarningDialog
+import com.tangem.tap.features.wallet.ui.adapters.SpacesItemDecoration
+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.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.card_balance.*
import kotlinx.android.synthetic.main.fragment_wallet.*
-import kotlinx.android.synthetic.main.layout_address.*
-import kotlinx.android.synthetic.main.layout_send_fee.*
-import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
-import kotlinx.android.synthetic.main.layout_wallet_short_buttons.*
import org.rekotlin.StoreSubscriber
class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber {
- private var dialog: Dialog? = null
private var snackbar: Snackbar? = null
- private lateinit var viewAdapter: PendingTransactionsAdapter
+ private lateinit var warningsAdapter: WarningMessagesAdapter
+
+ private var walletView: WalletView = SingleWalletView()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -64,12 +61,14 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber
- tv_twin_card_number.show()
- iv_twin_card.show()
- val number = when (cardNumber) {
- TwinCardNumber.First -> "1"
- TwinCardNumber.Second -> "2"
- }
- tv_twin_card_number.text = getString(R.string.wallet_twins_chip_format, number)
- }
- if (state.twinCardsState?.cardNumber == null) {
- tv_twin_card_number.hide()
- iv_twin_card.hide()
+ if (state.isMultiwalletAllowed && walletView is SingleWalletView) {
+ walletView = MultiWalletView()
+ walletView.changeWalletView(this)
+ } else if (!state.isMultiwalletAllowed && walletView is MultiWalletView) {
+ walletView = SingleWalletView()
+ walletView.changeWalletView(this)
}
+ walletView.onNewState(state)
- if (!state.showDetails) {
+ if (!state.shouldShowDetails) {
toolbar.menu.removeItem(R.id.details_menu)
} else if (toolbar.menu.findItem(R.id.details_menu) == null) {
toolbar.menu.add(R.menu.wallet, R.id.details_menu, NONE, R.string.details_title)
}
- srl_wallet.setOnRefreshListener {
- store.dispatch(WalletAction.LoadData)
- }
-
setupNoInternetHandling(state)
-
- setupButtons(state)
- setupAddressCard(state)
setupCardImage(state.cardImage)
- viewAdapter.submitList(state.pendingTransactions)
- if (state.pendingTransactions.isEmpty()) {
- rv_pending_transaction.hide()
- } else {
- rv_pending_transaction.show()
+ showWarningsIfPresent(state.mainWarningsList)
+
+ srl_wallet.setOnRefreshListener {
+ if (state.state != ProgressState.Loading) {
+ store.dispatch(WalletAction.LoadData)
+ }
}
- handleDialogs(state.walletDialog)
-
- l_balance.show()
- BalanceWidget(this, state.currencyData, state.twinCardsState != null).setup()
-
- if (state.state == ProgressState.Done) {
+ if (state.state != ProgressState.Loading) {
srl_wallet.isRefreshing = false
}
+ }
- when (state.payIdData.payIdState) {
- PayIdState.Disabled, PayIdState.Loading -> group_payid.hide()
- PayIdState.NotCreated -> {
- group_payid.show()
- tv_create_payid.show()
- tv_create_payid.setOnClickListener { store.dispatch(WalletAction.CreatePayId) }
- tv_payid_address.hide()
- }
- PayIdState.Created -> {
- group_payid.show()
- tv_create_payid.hide()
- tv_payid_address.show()
- tv_payid_address.text = state.payIdData.payId
-
- }
- PayIdState.ErrorLoading -> {
- }
- }
+ private fun showWarningsIfPresent(warnings: List) {
+ warningsAdapter.submitList(warnings)
+ rv_warning_messages.show(warnings.isNotEmpty())
}
private fun setupNoInternetHandling(state: WalletState) {
@@ -166,98 +137,14 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber R.string.wallet_button_send
- is WalletMainButton.CreateWalletButton -> {
- if (state.twinCardsState == null) {
- R.string.wallet_button_create_wallet
- } else {
- R.string.wallet_button_create_twin_wallet
- }
- }
- }
- btnConfirm.text = getString(buttonTitle)
- btnConfirm.isEnabled = state.mainButton.enabled
-
- btnConfirm.setOnClickListener {
- when (state.mainButton) {
- is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send())
- is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet)
- }
- }
- btn_top_up.setOnClickListener {
- store.dispatch(
- WalletAction.TopUpAction.TopUp(requireContext(), R.color.backgroundLightGray)
- )
- }
- }
-
- private fun setupAddressCard(state: WalletState) {
- if (state.walletAddresses != null) {
- l_address?.show()
- if (state.showMultipleAddress) {
- (l_address as? ViewGroup)?.beginDelayedTransition()
- chip_group_address_type.show()
- chip_group_address_type.fitChipsByGroupWidth()
-
- val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
- if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
-
- chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
- if (checkedId == -1) return@setOnCheckedChangeListener
-
- MultipleAddressUiHelper.idToType(checkedId, state.wallet?.blockchain)?.let {
- store.dispatch(WalletAction.ChangeSelectedAddress(it))
- }
- }
- } else {
- chip_group_address_type.hide()
- }
- tv_address.text = state.walletAddresses.selectedAddress.address
- tv_explore?.setOnClickListener {
- store.dispatch(WalletAction.ExploreAddress(requireContext()))
- }
- } else {
- l_address?.hide()
- }
- }
-
private fun setupCardImage(cardImage: Artwork?) {
Picasso.get()
.load(cardImage?.artworkId)
@@ -266,52 +153,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber {
- 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.WarningDialog -> {
- if (dialog == null) dialog = WarningDialog(requireContext()).apply {
- this.show(walletDialog.type)
- }
- }
- null -> {
- dialog?.dismiss()
- dialog = null
- }
- }
- }
-
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.details_menu -> {
store.state.globalState.scanNoteResponse?.let { scanNoteResponse ->
store.dispatch(DetailsAction.PrepareScreen(
scanNoteResponse.card, scanNoteResponse,
- store.state.walletState.wallet,
+ store.state.walletState.walletManagers.map { it.wallet },
store.state.globalState.configManager?.config?.isCreatingTwinCardsAllowed,
CardTou(),
store.state.globalState.appCurrency
@@ -326,41 +174,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber 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 -> BitcoinAddressType.Segwit
- Blockchain.CardanoShelley -> CardanoAddressType.Shelley
- else -> null
- }
- }
- R.id.chip_legacy -> {
- when (blockchain) {
- Blockchain.Bitcoin -> BitcoinAddressType.Legacy
- Blockchain.CardanoShelley -> CardanoAddressType.Byron
- else -> null
- }
- }
- else -> null
- }
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/PendingTransactionsAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/PendingTransactionsAdapter.kt
similarity index 98%
rename from app/src/main/java/com/tangem/tap/features/wallet/ui/PendingTransactionsAdapter.kt
rename to app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/PendingTransactionsAdapter.kt
index 93e0202458..09c6094d38 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/PendingTransactionsAdapter.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/PendingTransactionsAdapter.kt
@@ -1,4 +1,4 @@
-package com.tangem.tap.features.wallet.ui
+package com.tangem.tap.features.wallet.ui.adapters
import android.view.LayoutInflater
import android.view.View
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt
new file mode 100644
index 0000000000..f8c337d1f1
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt
@@ -0,0 +1,137 @@
+package com.tangem.tap.features.wallet.ui.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.tap.common.extensions.*
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletData
+import com.tangem.tap.features.wallet.ui.BalanceStatus
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.item_currency_wallet.view.*
+import java.math.BigDecimal
+
+class WalletAdapter
+ : ListAdapter(DiffUtilCallback) {
+
+ override fun getItemId(position: Int): Long {
+ return currentList[position].currencyData.currencySymbol?.hashCode()?.toLong() ?: 0
+ }
+
+ fun submitList(list: List, primaryBlockchain: Blockchain?, primaryToken: Token? = null) {
+ val listModified = list.toMutableList()
+ val primaryBlockchainWallet = when (
+ val index = listModified.indexOfFirst { it.blockchain == primaryBlockchain }
+ ) {
+ -1 -> null
+ else -> listModified.removeAt(index)
+ }
+
+
+ val primaryTokenWallet = if (primaryToken == null) null else when (
+ val index = listModified.indexOfFirst { it.token == primaryToken }
+ ) {
+ -1 -> null
+ else -> listModified.removeAt(index)
+ }
+
+ if (list.all { it.currencyData.fiatAmount == null }) {
+ val sortedList = listOfNotNull(primaryBlockchainWallet, primaryTokenWallet) + listModified
+ super.submitList(sortedList)
+ return
+ }
+
+ val sorted = listModified.sortedWith(
+ compareByDescending { it.currencyData.fiatAmountRaw ?: BigDecimal.ZERO }
+ .thenBy { it.currencyData.currencySymbol }
+ )
+ val sortedList = listOfNotNull(primaryBlockchainWallet, primaryTokenWallet) + sorted
+ super.submitList(sortedList)
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder {
+ val layout = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_currency_wallet, parent, false)
+ return WalletsViewHolder(layout)
+ }
+
+ override fun onBindViewHolder(holder: WalletsViewHolder, position: Int) {
+ holder.bind(currentList[position])
+ }
+
+ object DiffUtilCallback : DiffUtil.ItemCallback() {
+ override fun areContentsTheSame(
+ oldItem: WalletData, newItem: WalletData
+ ) = oldItem == newItem
+
+ override fun areItemsTheSame(
+ oldItem: WalletData, newItem: WalletData
+ ) = oldItem == newItem
+ }
+
+ class WalletsViewHolder(val view: View) :
+ RecyclerView.ViewHolder(view) {
+
+ fun bind(wallet: WalletData) {
+ view.tv_currency.text = wallet.currencyData.currency
+ view.tv_amount.text = wallet.currencyData.amount
+ if (view.tv_amount.isEllipsized()) {
+ val allowedSize = view.tv_amount.text.length - 4
+ view.tv_amount.text = wallet.currencyData.amount?.ellipsizeBeforeSpace(allowedSize)
+ }
+ view.tv_amount_fiat.text = wallet.currencyData.fiatAmount
+ view.tv_exchange_rate.text = wallet.fiatRateString
+ view.card_wallet.setOnClickListener {
+ store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
+ }
+ val blockchain = wallet.currencyData.currencySymbol?.let { Blockchain.fromCurrency(it) }
+ if (blockchain != null && blockchain != Blockchain.Unknown) {
+ view.tv_token_letter.text = null
+ view.iv_currency.colorFilter = null
+ view.iv_currency.setImageResource(blockchain.getIconRes())
+ } else {
+ view.tv_token_letter.text = wallet.currencyData.currencySymbol?.take(1)
+ wallet.token?.getColor()?.let { view.iv_currency.setColorFilter(it) }
+ view.iv_currency.setImageResource(R.drawable.shape_circle)
+ }
+ when (wallet.currencyData.status) {
+ BalanceStatus.VerifiedOnline -> hideWarning()
+ BalanceStatus.Loading -> {
+ hideWarning()
+ if (wallet.currencyData.amount == null) {
+ view.tv_exchange_rate.text = view.context.getText(R.string.wallet_balance_loading)
+ }
+ }
+ BalanceStatus.TransactionInProgress ->
+ showWarning(view.context.getString(R.string.wallet_balance_tx_in_progress))
+ BalanceStatus.Unreachable ->
+ showWarning(view.context.getString(R.string.wallet_balance_blockchain_unreachable))
+
+ BalanceStatus.NoAccount ->
+ showWarning(view.context.getString(R.string.wallet_error_no_account))
+ else -> {
+ }
+ }
+ }
+
+ private fun showWarning(message: String) {
+ toggleWarning(true)
+ view.tv_status_error_message.text = message
+ }
+
+ private fun hideWarning() {
+ toggleWarning(false)
+ }
+
+ private fun toggleWarning(show: Boolean) {
+ view.tv_exchange_rate.show(!show)
+ view.tv_status_error_message.show(show)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
new file mode 100644
index 0000000000..a15a4e3e9a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
@@ -0,0 +1,133 @@
+package com.tangem.tap.features.wallet.ui.adapters
+
+import android.graphics.Rect
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration
+import com.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.getActivity
+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.*
+import timber.log.Timber
+
+class WarningMessagesAdapter : ListAdapter(DiffUtilCallback) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WarningMessageVH {
+ val inflater = LayoutInflater.from(parent.context)
+ val layout = inflater.inflate(R.layout.layout_warning, parent, false)
+ return WarningMessageVH(layout)
+ }
+
+ override fun onBindViewHolder(holder: WarningMessageVH, position: Int) {
+ holder.bind(currentList[position])
+ }
+
+ object DiffUtilCallback : DiffUtil.ItemCallback() {
+ override fun areContentsTheSame(oldItem: WarningMessage, newItem: WarningMessage) = oldItem == newItem
+
+ override fun areItemsTheSame(oldItem: WarningMessage, newItem: WarningMessage) = oldItem == newItem
+ }
+}
+
+class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
+
+ fun bind(warning: WarningMessage) {
+ setBgColor(warning.priority)
+ setText(warning)
+ setupControlButtons(warning)
+ }
+
+ private fun setText(warning: WarningMessage) {
+ fun getString(resId: Int?, default: String) = if (resId == null) default else view.getString(resId)
+
+ view.tv_title.text = getString(warning.titleResId, warning.title)
+ view.tv_message.text = getString(warning.messageResId, warning.message)
+ }
+
+ private fun setBgColor(priority: WarningMessage.Priority) {
+ val color = when (priority) {
+ WarningMessage.Priority.Info -> R.color.warning_info
+ WarningMessage.Priority.Warning -> R.color.warning_warning
+ WarningMessage.Priority.Critical -> R.color.warning_critical
+ }
+ view.card_view.setCardBackgroundColor(view.context.resources.getColor(color))
+ }
+
+ private fun 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 {
+ val activity = view.context.getActivity() ?: return@setOnClickListener
+
+ FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
+ store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
+ val reviewManager = ReviewManagerFactory.create(activity)
+ val task = reviewManager.requestReviewFlow()
+ task.addOnCompleteListener {
+ if (it.isSuccessful) {
+ val reviewFlow = reviewManager.launchReviewFlow(activity, it.result)
+ reviewFlow.addOnCompleteListener {
+ if (it.isSuccessful) {
+ // send review was succeed
+ } else {
+ // send fails
+ }
+ }
+ } else {
+ Timber.e(task.exception)
+ }
+ }.addOnFailureListener {
+ Timber.e(it)
+ }
+ store.dispatch(GlobalAction.HideWarningMessage(warning))
+ }
+ }
+ }
+ }
+}
+
+class SpacesItemDecoration(private val spacePx: Int) : ItemDecoration() {
+ override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
+ outRect.left = spacePx
+ outRect.right = spacePx
+
+ outRect.top = spacePx / 2
+ outRect.top = spacePx / 2
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/PayIdDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/PayIdDialog.kt
deleted file mode 100644
index dd1944ac74..0000000000
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/PayIdDialog.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.tangem.tap.features.wallet.ui.dialogs
-
-import android.app.Dialog
-import android.content.Context
-import android.view.View
-import com.tangem.tap.common.extensions.hide
-import com.tangem.tap.common.extensions.show
-import com.tangem.tap.features.wallet.redux.WalletAction
-import com.tangem.tap.notificationsHandler
-import com.tangem.tap.store
-import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.dialog_create_payid.*
-
-class PayIdDialog(context: Context) : Dialog(context) {
-
- init {
- this.setContentView(R.layout.dialog_create_payid)
- }
-
- override fun show() {
- notificationsHandler?.replaceBaseLayout(this.cl_payid_dialog)
- this.setOnDismissListener {
- notificationsHandler?.returnBaseLayout()
- store.dispatch(WalletAction.CreatePayId.Cancel)
- }
- this.btn_create_payid?.setOnClickListener {
- if (this.et_payid.text.isNullOrBlank()) {
- store.dispatch(WalletAction.CreatePayId.EmptyField)
- } else {
- val payid = this.et_payid.text!!.toString() +
- this.context.getString(R.string.wallet_create_payid_domain)
- store.dispatch(WalletAction.CreatePayId.CompleteCreatingPayId(payid))
- }
- }
- super.show()
- }
-
- fun showProgress() {
- this.btn_create_payid?.visibility = View.INVISIBLE
- this.pb_create_payid?.show()
- }
-
- fun stopProgress() {
- this.btn_create_payid?.show()
- this.pb_create_payid?.hide()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt
new file mode 100644
index 0000000000..9a8ff9ace5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt
@@ -0,0 +1,30 @@
+package com.tangem.tap.features.wallet.ui.dialogs
+
+import android.content.Context
+import androidx.appcompat.app.AlertDialog
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.features.feedback.ScanFailsEmail
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+class ScanFailsDialog {
+
+ companion object {
+ fun create(context: Context): AlertDialog {
+ return AlertDialog.Builder(context).apply {
+ setTitle(context.getString(R.string.common_warning))
+ setMessage(R.string.alert_troubleshooting_scan_card_title)
+ setPositiveButton(R.string.alert_button_request_support) { _, _ ->
+ store.dispatch(GlobalAction.SendFeedback(ScanFailsEmail()))
+ }
+ setNegativeButton(R.string.common_cancel) { _, _ -> }
+ setOnDismissListener { store.dispatch(WalletAction.HideDialog) }
+ }.create()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt
deleted file mode 100644
index 6f70a4f60d..0000000000
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.tangem.tap.features.wallet.ui.dialogs
-
-import android.content.Context
-import androidx.appcompat.app.AlertDialog
-import com.tangem.tap.features.wallet.redux.WalletAction
-import com.tangem.tap.features.wallet.redux.WarningType
-import com.tangem.tap.store
-import com.tangem.wallet.R
-
-class WarningDialog(context: Context) : AlertDialog(context) {
-
- private val dialog: AlertDialog = Builder(context)
- .setTitle(context.getString(R.string.common_warning))
- .setPositiveButton(context.getString(R.string.common_ok)) { _, _ ->
- dismiss()
- }.setOnDismissListener {
- store.dispatch(WalletAction.SaveCardId)
- store.dispatch(WalletAction.HideDialog)
- }
- .create()
-
- fun show(warningType: WarningType) {
- val messageRes = when (warningType) {
- WarningType.CardSignedHashesBefore -> R.string.alert_card_signed_transactions
- WarningType.DevCard -> R.string.alert_developer_card
- }
- dialog.setMessage(dialog.context.getString(messageRes))
- dialog.show()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt
new file mode 100644
index 0000000000..51f47fc82d
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt
@@ -0,0 +1,149 @@
+package com.tangem.tap.features.wallet.ui.wallet
+
+import android.app.Dialog
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.tangem.tap.common.extensions.hide
+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.features.tokens.redux.TokensAction
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletDialog
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.ui.BalanceStatus
+import com.tangem.tap.features.wallet.ui.WalletFragment
+import com.tangem.tap.features.wallet.ui.adapters.WalletAdapter
+import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.card_balance.*
+import kotlinx.android.synthetic.main.fragment_wallet.*
+import kotlinx.android.synthetic.main.layout_balance_error.*
+import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
+
+
+class MultiWalletView : WalletView {
+
+ private var fragment: WalletFragment? = null
+ private var dialog: Dialog? = null
+
+ private lateinit var walletsAdapter: WalletAdapter
+
+
+ override fun changeWalletView(fragment: WalletFragment) {
+ setFragment(fragment)
+ onViewCreated()
+ showMultiWalletView()
+ setupButtons()
+ }
+
+
+ private fun showMultiWalletView() {
+ val fragment = fragment ?: return
+ fragment.tv_twin_card_number.hide()
+ fragment.iv_twin_card.hide()
+ fragment.rv_pending_transaction.hide()
+ fragment.l_card_balance.hide()
+ fragment.l_address.hide()
+ fragment.l_buttons_short.hide()
+ fragment.l_buttons_long.hide()
+ fragment.btn_scan_multiwallet?.show()
+ fragment.rv_multiwallet.show()
+ fragment.btn_add_token.show()
+ }
+
+ private fun setupButtons() {
+ val fragment = fragment ?: return
+ fragment.btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) }
+ }
+
+ override fun setFragment(fragment: WalletFragment) {
+ this.fragment = fragment
+ }
+
+ override fun removeFragment() {
+ this.fragment = null
+ }
+
+ override fun onViewCreated() {
+ setupWalletsRecyclerView()
+ }
+
+ private fun setupWalletsRecyclerView() {
+ val fragment = fragment ?: return
+ walletsAdapter = WalletAdapter()
+ walletsAdapter.setHasStableIds(true)
+ fragment.rv_multiwallet.layoutManager = LinearLayoutManager(fragment.requireContext())
+ fragment.rv_multiwallet.adapter = walletsAdapter
+ }
+
+ override fun onNewState(state: WalletState) {
+ val fragment = fragment ?: return
+
+ walletsAdapter.submitList(state.wallets, state.primaryBlockchain, state.primaryToken)
+
+ fragment.btn_add_token?.setOnClickListener {
+ store.dispatch(TokensAction.LoadCurrencies)
+ store.dispatch(TokensAction.SetAddedCurrencies(state.wallets))
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
+ }
+ handleErrorStates(state, fragment)
+ handleDialogs(state.walletDialog)
+ }
+
+ private fun handleErrorStates(state: WalletState, fragment: WalletFragment) {
+ when (state.primaryWallet?.currencyData?.status) {
+ BalanceStatus.EmptyCard -> {
+ showErrorState(
+ fragment,
+ fragment.getText(R.string.wallet_error_empty_card),
+ fragment.getString(R.string.wallet_error_empty_card_subtitle)
+ )
+ configureButtonsForEmptyWalletState(fragment)
+ }
+ BalanceStatus.UnknownBlockchain -> {
+ showErrorState(
+ fragment,
+ fragment.getText(R.string.wallet_error_unsupported_blockchain),
+ fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
+ )
+ }
+ }
+ }
+
+ private fun showErrorState(
+ fragment: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence
+ ) {
+ fragment.l_card_balance.show()
+ fragment.l_balance.hide()
+ fragment.l_balance_error.show()
+ fragment.rv_multiwallet.hide()
+ fragment.btn_add_token.hide()
+ fragment.tv_error_title.text = errorTitle
+ fragment.tv_error_descriptions.text = errorDescription
+ }
+
+ private fun configureButtonsForEmptyWalletState(fragment: WalletFragment) {
+ fragment.btn_scan_multiwallet.hide()
+ fragment.l_buttons_long.show()
+ fragment.btn_scan_long.setOnClickListener { store.dispatch(WalletAction.Scan) }
+ fragment.btn_confirm_long.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
+ fragment.btn_confirm_long.text = fragment.getText(R.string.wallet_button_create_wallet)
+ }
+
+ private fun handleDialogs(walletDialog: WalletDialog?) {
+ val fragment = fragment ?: return
+ val context = fragment.context ?: return
+ when (walletDialog) {
+ is WalletDialog.ScanFailsDialog -> {
+ if (dialog == null) dialog = ScanFailsDialog.create(context).apply {
+ this.show()
+ }
+ }
+ else -> {
+ dialog?.dismiss()
+ dialog = null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
new file mode 100644
index 0000000000..cc14fa932e
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
@@ -0,0 +1,243 @@
+package com.tangem.tap.features.wallet.ui.wallet
+
+import android.app.Dialog
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.tangem.tap.common.extensions.*
+import com.tangem.tap.domain.twins.TwinCardNumber
+import com.tangem.tap.features.wallet.models.PendingTransaction
+import com.tangem.tap.features.wallet.redux.*
+import com.tangem.tap.features.wallet.ui.BalanceWidget
+import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper
+import com.tangem.tap.features.wallet.ui.WalletFragment
+import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
+import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
+import com.tangem.tap.features.wallet.ui.dialogs.QrDialog
+import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.card_balance.*
+import kotlinx.android.synthetic.main.fragment_wallet.*
+import kotlinx.android.synthetic.main.layout_address.*
+import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
+import kotlinx.android.synthetic.main.layout_wallet_short_buttons.*
+
+class SingleWalletView : WalletView {
+
+ private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
+ private var fragment: WalletFragment? = null
+ private var dialog: Dialog? = null
+
+ override fun setFragment(fragment: WalletFragment) {
+ this.fragment = fragment
+ }
+
+ override fun removeFragment() {
+ fragment = null
+ }
+
+ override fun changeWalletView(fragment: WalletFragment) {
+ setFragment(fragment)
+ onViewCreated()
+ showSingleWalletView()
+ }
+
+ private fun showSingleWalletView() {
+ val fragment = fragment ?: return
+ fragment.rv_multiwallet.hide()
+ fragment.btn_add_token.hide()
+ fragment.btn_scan_multiwallet?.hide()
+ fragment.rv_pending_transaction.hide()
+ fragment.l_card_balance.show()
+ fragment.l_address.show()
+ }
+
+ override fun onViewCreated() {
+ setupTransactionsRecyclerView()
+ }
+
+
+ private fun setupTransactionsRecyclerView() {
+ val fragment = fragment ?: return
+ pendingTransactionAdapter = PendingTransactionsAdapter()
+ fragment.rv_pending_transaction.layoutManager = LinearLayoutManager(fragment.requireContext())
+ fragment.rv_pending_transaction.adapter = pendingTransactionAdapter
+ }
+
+ override fun onNewState(state: WalletState) {
+ setupTwinCards(state.twinCardsState)
+ state.primaryWallet ?: return
+ setupButtons(state.primaryWallet, state.twinCardsState != null)
+ setupAddressCard(state.primaryWallet)
+ showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
+ setupBalance(state, state.primaryWallet)
+ handleDialogs(state.walletDialog)
+ }
+
+ private fun showPendingTransactionsIfPresent(pendingTransactions: List) {
+ pendingTransactionAdapter.submitList(pendingTransactions)
+ fragment?.rv_pending_transaction?.show(pendingTransactions.isNotEmpty())
+ }
+
+
+ private fun setupBalance(state: WalletState, primaryWallet: WalletData) {
+ fragment?.apply {
+ this.l_balance.show()
+ BalanceWidget(this, primaryWallet.currencyData, state.twinCardsState != null).setup()
+ }
+ }
+
+ private fun setupTwinCards(twinCardsState: TwinCardsState?) {
+ fragment?.apply {
+ twinCardsState?.cardNumber?.let { cardNumber ->
+ this.tv_twin_card_number.show()
+ this.iv_twin_card.show()
+ val number = when (cardNumber) {
+ TwinCardNumber.First -> "1"
+ TwinCardNumber.Second -> "2"
+ }
+ this.tv_twin_card_number.text =
+ this.getString(R.string.wallet_twins_chip_format, number)
+ }
+ if (twinCardsState?.cardNumber == null) {
+ this.tv_twin_card_number.hide()
+ this.iv_twin_card.hide()
+ }
+ }
+
+ }
+
+ private fun setupButtons(state: WalletData, isTwinsWallet: Boolean) {
+ val fragment = fragment ?: return
+
+ setupButtonsType(state, fragment)
+
+ val btnConfirm = if (state.topUpState.allowed) {
+ fragment.btn_confirm_short
+ } else {
+ fragment.btn_confirm_long
+ }
+ val btnScan = if (state.topUpState.allowed) {
+ fragment.btn_scan_short
+ } else {
+ fragment.btn_scan_long
+ }
+
+ setupConfirmButton(state, btnConfirm, fragment, isTwinsWallet)
+
+ btnScan.setOnClickListener {
+ store.dispatch(WalletAction.Scan)
+ }
+
+ fragment.btn_copy.setOnClickListener {
+ state.walletAddresses?.selectedAddress?.address?.let { addressString ->
+ store.dispatch(WalletAction.CopyAddress(addressString, fragment.requireContext()))
+ }
+ }
+ fragment.btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
+
+ fragment.btn_top_up.setOnClickListener {
+ store.dispatch(
+ WalletAction.TopUpAction.TopUp(fragment.requireContext(), R.color.backgroundLightGray)
+ )
+ }
+ }
+
+ private fun setupButtonsType(state: WalletData, fragment: WalletFragment) {
+ if (state.topUpState.allowed) {
+ fragment.l_buttons_long.hide()
+ fragment.l_buttons_short.show()
+ } else {
+ fragment.l_buttons_long.show()
+ fragment.l_buttons_short.hide()
+ }
+ }
+
+ private fun setupConfirmButton(
+ state: WalletData, btnConfirm: Button, fragment: WalletFragment, isTwinsWallet: Boolean
+ ) {
+ val buttonTitle = when (state.mainButton) {
+ is WalletMainButton.SendButton -> R.string.wallet_button_send
+ is WalletMainButton.CreateWalletButton -> {
+ if (!isTwinsWallet) {
+ R.string.wallet_button_create_wallet
+ } else {
+ R.string.wallet_button_create_twin_wallet
+ }
+ }
+ }
+ btnConfirm.text = fragment.getString(buttonTitle)
+ btnConfirm.isEnabled = state.mainButton.enabled
+ btnConfirm.setOnClickListener {
+ when (state.mainButton) {
+ is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send())
+ is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet)
+ }
+ }
+ }
+
+
+ private fun setupAddressCard(state: WalletData) {
+ val fragment = fragment ?: return
+ if (state.walletAddresses != null) {
+ fragment.l_address?.show()
+ if (state.shouldShowMultipleAddress()) {
+ (fragment.l_address as? ViewGroup)?.beginDelayedTransition()
+ fragment.chip_group_address_type.show()
+ fragment.chip_group_address_type.fitChipsByGroupWidth()
+
+ val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
+ if (checkedId != View.NO_ID) fragment.chip_group_address_type.check(checkedId)
+
+ fragment.chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
+ if (checkedId == -1) return@setOnCheckedChangeListener
+ val type = MultipleAddressUiHelper.idToType(checkedId, state.currencyData.currencySymbol)
+ type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
+ }
+ } else {
+ fragment.chip_group_address_type.hide()
+ }
+ fragment.tv_address.text = state.walletAddresses.selectedAddress.address
+ fragment.tv_explore?.setOnClickListener {
+ store.dispatch(WalletAction.ExploreAddress(
+ state.walletAddresses.selectedAddress.exploreUrl,
+ fragment.requireContext()))
+ }
+ } else {
+ fragment.l_address?.hide()
+ }
+ }
+
+ private fun handleDialogs(walletDialog: WalletDialog?) {
+ val fragment = fragment ?: return
+ val context = fragment.context ?: return
+ when (walletDialog) {
+ is WalletDialog.QrDialog -> {
+ if (walletDialog.qrCode != null && walletDialog.shareUrl != null) {
+ if (dialog == null) dialog = QrDialog(context).apply {
+ this.showQr(
+ walletDialog.qrCode, walletDialog.shareUrl, walletDialog.currencyName
+ )
+ }
+ }
+ }
+ is WalletDialog.SelectAmountToSendDialog -> {
+ if (dialog == null) dialog = AmountToSendDialog(context).apply {
+ this.show(walletDialog.amounts)
+ }
+ }
+ is WalletDialog.ScanFailsDialog -> {
+ if (dialog == null) dialog = ScanFailsDialog.create(context).apply {
+ this.show()
+ }
+ }
+ null -> {
+ dialog?.dismiss()
+ dialog = null
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt
new file mode 100644
index 0000000000..ab49865c02
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt
@@ -0,0 +1,18 @@
+package com.tangem.tap.features.wallet.ui.wallet
+
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.ui.WalletFragment
+
+interface WalletView {
+
+ fun setFragment(fragment: WalletFragment)
+
+ fun removeFragment()
+
+ fun changeWalletView(fragment: WalletFragment)
+
+ fun onViewCreated()
+
+ fun onNewState(state: WalletState)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
index 08346630f3..9d45dd8c36 100644
--- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
+++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
@@ -11,16 +11,18 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.network.coinmarketcap.FiatCurrency
+import java.util.*
class PreferencesStorage(applicationContext: Application) {
- private val preferences: SharedPreferences by lazy {
- applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
- }
+ private val preferences: SharedPreferences = applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
+
+ val appRatingLaunchObserver: AppRatingLaunchObserver
init {
incrementLaunchCounter()
+ appRatingLaunchObserver = AppRatingLaunchObserver(preferences, getCountOfLaunches())
}
private val fiatCurrenciesAdapter: JsonAdapter> by lazy {
@@ -97,4 +99,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)
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_binance.png b/app/src/main/res/drawable-hdpi/ic_binance.png
new file mode 100644
index 0000000000..edce12fa14
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_btc.png b/app/src/main/res/drawable-hdpi/ic_btc.png
new file mode 100644
index 0000000000..be16f5a51b
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_btc_cash.png b/app/src/main/res/drawable-hdpi/ic_btc_cash.png
new file mode 100644
index 0000000000..ec0864fdb8
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_cardano.png b/app/src/main/res/drawable-hdpi/ic_cardano.png
new file mode 100644
index 0000000000..8a4ac01ad4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_eth.png b/app/src/main/res/drawable-hdpi/ic_eth.png
new file mode 100644
index 0000000000..9db27f814e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_ltc.png b/app/src/main/res/drawable-hdpi/ic_ltc.png
new file mode 100644
index 0000000000..6c0fb4ad3c
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_rsk.png b/app/src/main/res/drawable-hdpi/ic_rsk.png
new file mode 100644
index 0000000000..be49cda24f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_stellar.png b/app/src/main/res/drawable-hdpi/ic_stellar.png
new file mode 100644
index 0000000000..7bb500ef1d
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_tezos.png b/app/src/main/res/drawable-hdpi/ic_tezos.png
new file mode 100644
index 0000000000..938fc9d3d4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_xlm.png b/app/src/main/res/drawable-hdpi/ic_xlm.png
new file mode 100644
index 0000000000..6155f291ea
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_xrp.png b/app/src/main/res/drawable-hdpi/ic_xrp.png
new file mode 100644
index 0000000000..058650f44b
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_binance.png b/app/src/main/res/drawable-ldpi/ic_binance.png
new file mode 100644
index 0000000000..18aa82ea4b
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_btc.png b/app/src/main/res/drawable-ldpi/ic_btc.png
new file mode 100644
index 0000000000..982bb50ad1
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_btc_cash.png b/app/src/main/res/drawable-ldpi/ic_btc_cash.png
new file mode 100644
index 0000000000..3789ae5071
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_cardano.png b/app/src/main/res/drawable-ldpi/ic_cardano.png
new file mode 100644
index 0000000000..dd8332ef62
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_eth.png b/app/src/main/res/drawable-ldpi/ic_eth.png
new file mode 100644
index 0000000000..b8cd7b26ab
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_ltc.png b/app/src/main/res/drawable-ldpi/ic_ltc.png
new file mode 100644
index 0000000000..a9dc421e25
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_rsk.png b/app/src/main/res/drawable-ldpi/ic_rsk.png
new file mode 100644
index 0000000000..416da64bb7
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_stellar.png b/app/src/main/res/drawable-ldpi/ic_stellar.png
new file mode 100644
index 0000000000..0de7200a90
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_tezos.png b/app/src/main/res/drawable-ldpi/ic_tezos.png
new file mode 100644
index 0000000000..502e81b732
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_xlm.png b/app/src/main/res/drawable-ldpi/ic_xlm.png
new file mode 100644
index 0000000000..3262fe8e13
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_xrp.png b/app/src/main/res/drawable-ldpi/ic_xrp.png
new file mode 100644
index 0000000000..70d484bc18
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_binance.png b/app/src/main/res/drawable-mdpi/ic_binance.png
new file mode 100644
index 0000000000..9254cb4a01
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_btc.png b/app/src/main/res/drawable-mdpi/ic_btc.png
new file mode 100644
index 0000000000..f02a7a13a1
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_btc_cash.png b/app/src/main/res/drawable-mdpi/ic_btc_cash.png
new file mode 100644
index 0000000000..e0dea3ea53
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_cardano.png b/app/src/main/res/drawable-mdpi/ic_cardano.png
new file mode 100644
index 0000000000..7ff6fc7ec0
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_eth.png b/app/src/main/res/drawable-mdpi/ic_eth.png
new file mode 100644
index 0000000000..e01e76371f
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_ltc.png b/app/src/main/res/drawable-mdpi/ic_ltc.png
new file mode 100644
index 0000000000..13f1de9253
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_rsk.png b/app/src/main/res/drawable-mdpi/ic_rsk.png
new file mode 100644
index 0000000000..af269e0bda
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_stellar.png b/app/src/main/res/drawable-mdpi/ic_stellar.png
new file mode 100644
index 0000000000..207f7c21a8
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_tezos.png b/app/src/main/res/drawable-mdpi/ic_tezos.png
new file mode 100644
index 0000000000..71d699414b
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_xlm.png b/app/src/main/res/drawable-mdpi/ic_xlm.png
new file mode 100644
index 0000000000..1a35164ed1
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_xrp.png b/app/src/main/res/drawable-mdpi/ic_xrp.png
new file mode 100644
index 0000000000..ec5e3bb680
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_binance.png b/app/src/main/res/drawable-xhdpi/ic_binance.png
new file mode 100644
index 0000000000..4a1619f750
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_btc.png b/app/src/main/res/drawable-xhdpi/ic_btc.png
new file mode 100644
index 0000000000..e58ee8b721
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_btc_cash.png b/app/src/main/res/drawable-xhdpi/ic_btc_cash.png
new file mode 100644
index 0000000000..4709218591
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_cardano.png b/app/src/main/res/drawable-xhdpi/ic_cardano.png
new file mode 100644
index 0000000000..5e9c1be96a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_eth.png b/app/src/main/res/drawable-xhdpi/ic_eth.png
new file mode 100644
index 0000000000..5e5807bf8b
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_ltc.png b/app/src/main/res/drawable-xhdpi/ic_ltc.png
new file mode 100644
index 0000000000..1135186fa5
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_rsk.png b/app/src/main/res/drawable-xhdpi/ic_rsk.png
new file mode 100644
index 0000000000..2016162c6d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stellar.png b/app/src/main/res/drawable-xhdpi/ic_stellar.png
new file mode 100644
index 0000000000..d6073f7fc7
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_tezos.png b/app/src/main/res/drawable-xhdpi/ic_tezos.png
new file mode 100644
index 0000000000..811d221bdb
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_xlm.png b/app/src/main/res/drawable-xhdpi/ic_xlm.png
new file mode 100644
index 0000000000..7ebe6a2fe8
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_xrp.png b/app/src/main/res/drawable-xhdpi/ic_xrp.png
new file mode 100644
index 0000000000..aa8adabc2c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_binance.png b/app/src/main/res/drawable-xxhdpi/ic_binance.png
new file mode 100644
index 0000000000..dbe9c0805f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_btc.png b/app/src/main/res/drawable-xxhdpi/ic_btc.png
new file mode 100644
index 0000000000..87f552a566
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_btc_cash.png b/app/src/main/res/drawable-xxhdpi/ic_btc_cash.png
new file mode 100644
index 0000000000..11d710501e
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_cardano.png b/app/src/main/res/drawable-xxhdpi/ic_cardano.png
new file mode 100644
index 0000000000..74faea419a
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_eth.png b/app/src/main/res/drawable-xxhdpi/ic_eth.png
new file mode 100644
index 0000000000..1e7a0e7952
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_ltc.png b/app/src/main/res/drawable-xxhdpi/ic_ltc.png
new file mode 100644
index 0000000000..0cf80c3570
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_rsk.png b/app/src/main/res/drawable-xxhdpi/ic_rsk.png
new file mode 100644
index 0000000000..3a660c50bc
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_stellar.png b/app/src/main/res/drawable-xxhdpi/ic_stellar.png
new file mode 100644
index 0000000000..8824973627
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_tezos.png b/app/src/main/res/drawable-xxhdpi/ic_tezos.png
new file mode 100644
index 0000000000..16dccf95c8
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_xlm.png b/app/src/main/res/drawable-xxhdpi/ic_xlm.png
new file mode 100644
index 0000000000..0dc1b2ba5f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_xrp.png b/app/src/main/res/drawable-xxhdpi/ic_xrp.png
new file mode 100644
index 0000000000..881d6b7b4b
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_binance.png b/app/src/main/res/drawable-xxxhdpi/ic_binance.png
new file mode 100644
index 0000000000..f18f7ff9d7
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_btc.png b/app/src/main/res/drawable-xxxhdpi/ic_btc.png
new file mode 100644
index 0000000000..165164f069
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_btc_cash.png b/app/src/main/res/drawable-xxxhdpi/ic_btc_cash.png
new file mode 100644
index 0000000000..babd59d8b1
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_cardano.png b/app/src/main/res/drawable-xxxhdpi/ic_cardano.png
new file mode 100644
index 0000000000..1f8b6888a5
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_eth.png b/app/src/main/res/drawable-xxxhdpi/ic_eth.png
new file mode 100644
index 0000000000..74ead90795
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_ltc.png b/app/src/main/res/drawable-xxxhdpi/ic_ltc.png
new file mode 100644
index 0000000000..d159e6728f
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_rsk.png b/app/src/main/res/drawable-xxxhdpi/ic_rsk.png
new file mode 100644
index 0000000000..b442ac65a4
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stellar.png b/app/src/main/res/drawable-xxxhdpi/ic_stellar.png
new file mode 100644
index 0000000000..a19f0db146
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_tezos.png b/app/src/main/res/drawable-xxxhdpi/ic_tezos.png
new file mode 100644
index 0000000000..d91ceab8fc
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_xlm.png b/app/src/main/res/drawable-xxxhdpi/ic_xlm.png
new file mode 100644
index 0000000000..6500485826
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_xrp.png b/app/src/main/res/drawable-xxxhdpi/ic_xrp.png
new file mode 100644
index 0000000000..22400ff291
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml
new file mode 100644
index 0000000000..9b975701fe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_copy_chip.xml b/app/src/main/res/drawable/ic_copy_chip.xml
new file mode 100644
index 0000000000..83325e0952
--- /dev/null
+++ b/app/src/main/res/drawable/ic_copy_chip.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_inactive.xml b/app/src/main/res/drawable/ic_inactive.xml
new file mode 100644
index 0000000000..eb19f0ce6d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_inactive.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_no_tokens.xml b/app/src/main/res/drawable/ic_no_tokens.xml
new file mode 100644
index 0000000000..f1149e0fad
--- /dev/null
+++ b/app/src/main/res/drawable/ic_no_tokens.xml
@@ -0,0 +1,468 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 0000000000..d698df7efe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_share_chip.xml b/app/src/main/res/drawable/ic_share_chip.xml
new file mode 100644
index 0000000000..e026b36c2e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_share_chip.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/shape_circle.xml b/app/src/main/res/drawable/shape_circle.xml
new file mode 100644
index 0000000000..f857819f4d
--- /dev/null
+++ b/app/src/main/res/drawable/shape_circle.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_tokens.xml b/app/src/main/res/layout/fragment_add_tokens.xml
new file mode 100644
index 0000000000..3ed5cbd260
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_tokens.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml
index 2533020f4a..b45e3e92ce 100644
--- a/app/src/main/res/layout/fragment_details.xml
+++ b/app/src/main/res/layout/fragment_details.xml
@@ -116,15 +116,15 @@
android:id="@+id/tv_disclaimer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:drawablePadding="15dp"
android:paddingTop="7dp"
android:paddingBottom="10dp"
- android:drawablePadding="15dp"
+ android:text="@string/disclaimer_title"
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_signed_hashes_title"
- android:text="@string/disclaimer_title" />
+ app:layout_constraintTop_toBottomOf="@id/tv_signed_hashes_title" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/tv_send_feedback" />
+ app:layout_constraintTop_toBottomOf="@id/tv_security_title" />
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml
index 86ec08548b..78131a2e15 100644
--- a/app/src/main/res/layout/fragment_send.xml
+++ b/app/src/main/res/layout/fragment_send.xml
@@ -74,6 +74,13 @@
android:layout_marginBottom="24dp"
tools:visibility="gone" />
+
+
+ android:clipToPadding="false"
+ android:paddingBottom="33dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/guideline"
+ app:layout_constraintTop_toTopOf="parent">
+ app:layout_constraintTop_toBottomOf="@id/iv_card" />
+ app:layout_constraintBottom_toBottomOf="@id/iv_twin_card"
+ app:layout_constraintEnd_toEndOf="@id/iv_twin_card"
+ app:layout_constraintStart_toStartOf="@id/iv_twin_card"
+ app:layout_constraintTop_toTopOf="@id/iv_twin_card"
+ tools:text="Card 2 of 2" />
+ app:barrierDirection="bottom"
+ app:constraint_referenced_ids="iv_card,iv_twin_card" />
+
+
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" />
@@ -117,22 +132,69 @@
layout="@layout/layout_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/l_card_balance" />
+
+
+
+
+
+
+
+
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent" />
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+
diff --git a/app/src/main/res/layout/fragment_wallet_details.xml b/app/src/main/res/layout/fragment_wallet_details.xml
new file mode 100644
index 0000000000..a848f0df9d
--- /dev/null
+++ b/app/src/main/res/layout/fragment_wallet_details.xml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_currency_subtitle.xml b/app/src/main/res/layout/item_currency_subtitle.xml
new file mode 100644
index 0000000000..c4ae27800d
--- /dev/null
+++ b/app/src/main/res/layout/item_currency_subtitle.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_currency_wallet.xml b/app/src/main/res/layout/item_currency_wallet.xml
new file mode 100644
index 0000000000..09b7dd4cc9
--- /dev/null
+++ b/app/src/main/res/layout/item_currency_wallet.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_popular_token.xml b/app/src/main/res/layout/item_popular_token.xml
new file mode 100644
index 0000000000..8d22ab57b4
--- /dev/null
+++ b/app/src/main/res/layout/item_popular_token.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_balance_wallet_details.xml b/app/src/main/res/layout/layout_balance_wallet_details.xml
new file mode 100644
index 0000000000..56bf9b2d69
--- /dev/null
+++ b/app/src/main/res/layout/layout_balance_wallet_details.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_search_view.xml b/app/src/main/res/layout/layout_search_view.xml
new file mode 100644
index 0000000000..65bbfa77d1
--- /dev/null
+++ b/app/src/main/res/layout/layout_search_view.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_wallet_details.xml b/app/src/main/res/layout/layout_wallet_details.xml
new file mode 100644
index 0000000000..d276934193
--- /dev/null
+++ b/app/src/main/res/layout/layout_wallet_details.xml
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_wallet_long_buttons.xml b/app/src/main/res/layout/layout_wallet_long_buttons.xml
index d32b3ac917..d63cb4681a 100644
--- a/app/src/main/res/layout/layout_wallet_long_buttons.xml
+++ b/app/src/main/res/layout/layout_wallet_long_buttons.xml
@@ -1,8 +1,8 @@
+ android:layout_height="match_parent">
+
+ app:layout_constraintVertical_bias="1" />
+ app:layout_constraintVertical_bias="1" />
+ app:layout_constraintVertical_bias="1" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_warning.xml b/app/src/main/res/layout/layout_warning.xml
new file mode 100644
index 0000000000..a9a76d4637
--- /dev/null
+++ b/app/src/main/res/layout/layout_warning.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/popular_tokens.xml b/app/src/main/res/menu/popular_tokens.xml
new file mode 100644
index 0000000000..80d52565c9
--- /dev/null
+++ b/app/src/main/res/menu/popular_tokens.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/wallet_details.xml b/app/src/main/res/menu/wallet_details.xml
new file mode 100644
index 0000000000..9622311e82
--- /dev/null
+++ b/app/src/main/res/menu/wallet_details.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 4ae7d12378..0000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
old mode 100644
new mode 100755
index f26b7cd005..e307de6027
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
deleted file mode 100644
index cd4844d78b..0000000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
deleted file mode 100644
index ad4b07ff80..0000000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index bb2457e1ff..0000000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
old mode 100644
new mode 100755
index d7b8e985cc..cfab76de31
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
deleted file mode 100644
index 115308a4f9..0000000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
deleted file mode 100644
index 7fccb8a69b..0000000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index 2a7e2a1395..0000000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
old mode 100644
new mode 100755
index 4f205d64ab..bddcb01270
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
deleted file mode 100644
index a6129e9f51..0000000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 3c3b30ca7e..0000000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index dc8fb9c530..0000000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
old mode 100644
new mode 100755
index ffebc72291..ac5cb152bb
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
deleted file mode 100644
index c018380e65..0000000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 97a83eb92a..0000000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 9d876c01e3..0000000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
old mode 100644
new mode 100755
index 613405d26a..533177434c
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
index dd3b3a6d67..d32c73f660 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
index fbe488e03a..9b72259299 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 405be1cf56..0000000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index df6ab33618..97c8b2d0af 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -12,7 +12,7 @@
Sie haben keinen Zugang zur Kamera erteilt, bitte passen Sie Ihre Datenschutzeinstellungen an
Einstellungen
Willkommen bei Tangem. Haben Sie eine dieser Karten?
- Willkommen bei Tangem Tap. Scannen Sie Ihre Karten um zu starten.
+ Willkommen bei Tangem. Scannen Sie Ihre Karten um zu starten.
Ja! Bitte scannen
Einkaufen
Halten Sie die \ncard hinter dem \nphone bereit und scannen Sie sie.
@@ -21,7 +21,7 @@
Scannen
Absenden
ein Wallet erstellen
- Tangem Tap
+ Tangem
eine Walletoption wählen
Token
Tokens
@@ -95,9 +95,10 @@
Zugangscode
Sie müssen den richtigen Zugangscode eingeben, bevor Sie die Karte scannen.
Sie können NFC-Probleme mit einigen iPhone 7/7+ während der Extraktion haben
- Warnung: Diese Karte wurde früher bereits aufgeladen und Transaktionen wurden damit signiert. Ziehen Sie eine sofortige Auszahlung aller Beträge in Betracht, wenn Sie diese Karte von einer nicht vertrauenswürdigen Quelle erhalten haben.
+ 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.
+ Warnung
- Diese Karte ist für die Zusammenarbeit mit Tangem Tap nicht geeignet
+ Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet
Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel
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.
Tippen um zu signieren
@@ -111,7 +112,7 @@
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:
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index ffbe678287..29e248d8d7 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -13,7 +13,7 @@
Vous n\'avez pas octroyé l’accès à votre caméra, veuillez modifier vos paramètres de confidentialité
Paramètres
Bienvenue à Tangem. Avez-vous une de nos cartes
- Encore une fois, bienvenue dans Tangem Tap. \nScannez votre carte pour commencer.
+ Encore une fois, bienvenue dans Tangem. \nScannez votre carte pour commencer.
Oui! Scannez-la.
Magasin
Préparez-vous à appuyer votre carte au dos de votre téléphone.
@@ -22,7 +22,7 @@
Scanner
Envoyer
Créer un portefeuille
- Tangem Tap
+ Tangem
Choisir une variante de portefeuille
Jeton d\'authentification
Jetons d\'authentification
@@ -96,8 +96,9 @@
Code d’accès
Vous devrez entrer le mot de passe correct avant de scanner la carte
Vous pouvez rencontrer des problèmes NFC avec certains iPhone 7/7 + lors de l’extraction
- Attention: cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable.
- Cette carte n\'est pas conçue pour fonctionner avec Tangem Tap
+ 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.
+ Attention
+ Cette carte n\'est pas conçue pour fonctionner avec Tangem
La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement
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.
Touchez pour signer
@@ -112,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 :
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 7c683849f7..a7011d4bcc 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -11,7 +11,7 @@
Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy
Impostazioni
Benvenuti in Tangem. Hai una delle nostre schede?>
- Benevenuti di nuovo su Tangem Tap. Scansiona la tua carta per iniziare.
+ Benevenuti di nuovo su Tangem. Scansiona la tua carta per iniziare.
Si! Scansiona carta.
Shop
Preparati ad avvicinare la tua carta sul retro del telefono.
@@ -20,7 +20,7 @@
Scansiona
Invia
Crea portafoglio
- Tangem Tap
+ Tangem
Scegli l\'opzione del portafoglio
Token
Token
@@ -93,8 +93,9 @@
Codice di accesso
Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto
Potresti riscontrare problemi con l\'NFC su alcuni iPhone 7/7 + durante la rimozione
- Attenzione: questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile.
- Questa carta non è progettata per funzionare con Tangem Tap
+ 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.
+ Attenzione
+ Questa carta non è progettata per funzionare con Tangem
La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento
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.
Avvicina per firmare
@@ -109,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
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 2b8a0270b6..fc5a1fd7b2 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -43,5 +43,8 @@
#CBE4FF
#E0E6FA
+ #1C1C1E
+ #FFB71B
+ #CA0F03
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 690be865b5..041c1aa018 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,6 +1,6 @@
- Tangem Tap
+ Tangem
No
Save changes
@@ -16,7 +16,7 @@
Settings
Welcome to Tangem.\nDo you have one of our cards?
- Welcome back to Tangem Tap. \nScan your card to start.
+ Welcome back to Tangem. \nScan your card to start.
Shop
Get ready to tap your \ncard to the back of the \nphone.
Tap in
@@ -26,7 +26,7 @@
Scan
Send
Create wallet
- Tangem Tap
+ Tangem
Choice wallet option
Token
@@ -107,8 +107,9 @@
You will have to submit the correct access code before scanning the card
You may experience NFC problems with some iPhone 7/7+ during the extraction
- Warning: This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source.
- This card it is not designed to work with Tangem Tap
+ 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.
+ Warning
+ This card it is not designed to work with Tangem
The card you scanned is a development card. Don’t accept it as a payment
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.
@@ -123,7 +124,7 @@
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
diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml
index 8418b367b6..3e6b22d9c1 100644
--- a/app/src/main/res/values/strings_untranslated.xml
+++ b/app/src/main/res/values/strings_untranslated.xml
@@ -31,13 +31,75 @@ this wallet.
The twin address was successfully created
+ //Common
Start
Back
+ Add
+ Remove
+ Search
+
Memo
Destination tag
Invalid destination tag. It won\'t be added to the transaction
Invalid Memo ID. It won\'t be added to the transaction
+ App
+ Send feedback
+ Sent successfully
+ Thank you for your feedback
+ Your suggestions were sent
+ Thank your for your feedback. We will response as soon as possible
+ Failed to send email
+ Reason: %s
+ Can’t send a transaction
+ Reason: %s. Do you want to send feedback?
+ Are you having difficulty scanning your card?
+ Please try to tap the card exactly as shown in the animation or request support.
+ Try again
+ Request support
+ Send feedback
+ Really cool!
+ Can be better
+ One question
+ How do you like Tangem?
+
+ All fields are required
+
+ //Manage tokens
+ No tokens yet
+ Added tokens
+ Remove %s token
+ Are you sure you want to remove this token?
+ Add Tokens
+ Add Token
+ Manage Tokens
+ Add Custom
+ Custom tokens
+ Popular tokens
+ Add Custom Token
+ Please, fill all fields
+ Entered number is not a valid decimals number
+ Name
+ Token symbol
+ Contract address
+ Decimals
+ ex. USD Coin
+ ex. USDC
+ Added
+
+ //Details
+ Blockchain
+ Manage tokens
+
+ Blockchains
+ Ethereum Tokens
+ Copy
+ Share
+ + Add tokens
+
+
+
+
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 0000000000..1a33b59980
--- /dev/null
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/dependencies.gradle b/dependencies.gradle
index 6aa50c71a1..f74991aa26 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,4 +1,4 @@
ext.versions = [
- kotlin : '1.4.30',
- build_gradle: '4.1.2',
+ kotlin : '1.4.31',
+ build_gradle: '4.1.3',
]