diff --git a/app/build.gradle b/app/build.gradle
index 70f6e3df99..a670cb5c27 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,22 +1,22 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
android {
- compileSdkVersion 30
+ compileSdkVersion 31
defaultConfig {
applicationId "com.tangem.wallet"
versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 1
versionName project.hasProperty('versionName') ? project.property('versionName') : "1.0.0.-SNAPSHOT"
minSdkVersion 21
- targetSdkVersion 30
+ targetSdkVersion 31
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
- androidExtensions {
- experimental = true
+ buildFeatures {
+ compose true
+ viewBinding true
}
buildTypes {
release {
@@ -47,12 +47,14 @@ android {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
-
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled false
}
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.1.0'
+ }
packagingOptions {
exclude 'lib/x86_64/darwin/libscrypt.dylib'
exclude 'lib/x86_64/freebsd/libscrypt.so'
@@ -67,19 +69,19 @@ repositories {
dependencies {
implementation fileTree(include: ['*.aar'], dir: 'libs')
- implementation 'androidx.core:core-ktx:1.6.0'
- implementation 'androidx.appcompat:appcompat:1.3.1'
- implementation 'androidx.fragment:fragment-ktx:1.3.6'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
- implementation 'com.google.android.material:material:1.4.0'
- implementation "androidx.core:core-ktx:1.6.0"
- implementation 'com.google.android.play:core:1.10.2'
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'androidx.fragment:fragment-ktx:1.4.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
+ implementation 'com.google.android.material:material:1.5.0'
+ implementation "androidx.core:core-ktx:1.7.0"
+ implementation 'com.google.android.play:core:1.10.3'
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'com.tangem:blockchain:develop-64'
- implementation 'com.tangem.tangem-sdk-kotlin:core:develop-131'
- implementation 'com.tangem.tangem-sdk-kotlin:android:develop-131'
+ implementation 'com.tangem.tangem-sdk-kotlin:core:develop-139'
+ implementation 'com.tangem.tangem-sdk-kotlin:android:develop-139'
// WebView
implementation "androidx.browser:browser:1.3.0"
@@ -129,6 +131,24 @@ dependencies {
// animation
implementation "com.airbnb.android:lottie:3.4.0"
+ // Shopify
+ implementation('com.shopify.mobilebuysdk:buy3:12.0.0') {
+ exclude group: "com.shopify.graphql.support"
+ exclude module: 'joda-time'
+ }
+
+ // Google Pay
+ implementation 'com.google.android.gms:play-services-wallet:19.1.0'
+
+ //Compose
+ implementation 'androidx.activity:activity-compose:1.4.0'
+ implementation 'androidx.compose.material:material:1.1.0'
+ implementation 'androidx.compose.animation:animation:1.1.0'
+ implementation 'androidx.compose.ui:ui-tooling:1.1.0'
+ implementation "com.google.accompanist:accompanist-appcompat-theme:0.23.0"
+
+ implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6'
+
testImplementation 'junit:junit:4.13.2'
testImplementation "com.google.truth:truth:1.1.3"
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
diff --git a/app/src/debug/res/values-ru/strings.xml b/app/src/debug/res/values-ru/strings.xml
new file mode 100644
index 0000000000..1abc31ee12
--- /dev/null
+++ b/app/src/debug/res/values-ru/strings.xml
@@ -0,0 +1,6 @@
+
+
+
+ DebugTangem
+
+
\ No newline at end of file
diff --git a/app/src/debug_beta/res/values-ru/strings.xml b/app/src/debug_beta/res/values-ru/strings.xml
new file mode 100644
index 0000000000..3e2d147b31
--- /dev/null
+++ b/app/src/debug_beta/res/values-ru/strings.xml
@@ -0,0 +1,6 @@
+
+
+
+ BetaTangem
+
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f358215715..4dfca87b23 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -39,8 +39,12 @@
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:fullBackupContent">
+
{
+ store.dispatch(
+ ShopAction.BuyWithGooglePay.HandleGooglePayResponse(resultCode, data)
+ )
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt
index 1d84bd399c..74e957f6c3 100644
--- a/app/src/main/java/com/tangem/tap/TapApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TapApplication.kt
@@ -12,6 +12,7 @@ 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.common.shop.TangemShopService
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
import com.tangem.tap.domain.configurable.config.FeaturesRemoteLoader
@@ -37,6 +38,7 @@ val store = Store(
lateinit var preferencesStorage: PreferencesStorage
lateinit var currenciesRepository: CurrenciesRepository
lateinit var walletConnectRepository: WalletConnectRepository
+lateinit var shopService: TangemShopService
class TapApplication : Application() {
override fun onCreate() {
@@ -72,7 +74,10 @@ class TapApplication : Application() {
val localLoader = FeaturesLocalLoader(this, moshi)
val remoteLoader = FeaturesRemoteLoader(moshi)
val configManager = ConfigManager(localLoader, remoteLoader)
- configManager.load { store.dispatch(GlobalAction.SetConfigManager(configManager)) }
+ configManager.load {
+ store.dispatch(GlobalAction.SetConfigManager(configManager))
+ shopService = TangemShopService(this, configManager.config.shopify!!)
+ }
val warningsManager = WarningMessagesManager(RemoteWarningLoader(moshi))
warningsManager.load { store.dispatch(GlobalAction.SetWarningManager(warningsManager)) }
}
diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
index 7f3bf094a7..56ce4dd062 100644
--- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
@@ -15,6 +15,7 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInPro
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
+import com.tangem.tap.features.wallet.ui.dialogs.SimpleOkDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
@@ -48,6 +49,8 @@ class DialogManager : StoreSubscriber {
if (dialog != null) return
dialog = when (state.dialog) {
+ is AppDialog.SimpleOkDialog -> SimpleOkDialog.create(state.dialog, context)
+ is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context)
is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context)
diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
index 7058973e1a..1e41053369 100644
--- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
@@ -5,7 +5,6 @@ import android.nfc.NfcAdapter
import android.nfc.Tag
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
-import com.tangem.tap.domain.topup.TradeCryptoHelper
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.home.redux.HomeAction
@@ -15,6 +14,11 @@ import timber.log.Timber
class IntentHandler {
+ private val TRANSACTION_ID_PARAM = "transactionId"
+ private val CURRENCY_CODE_PARAM = "baseCurrencyCode"
+ private val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
+ private val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
+
fun handleIntent(intent: Intent?) {
handleBackgroundScan(intent)
handleWalletConnectLink(intent)
@@ -23,8 +27,8 @@ class IntentHandler {
private fun handleBackgroundScan(intent: Intent?) {
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action ||
- NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action
- )
+ NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action
+ )
) {
val tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
if (tag != null) {
@@ -45,13 +49,13 @@ class IntentHandler {
private fun handleSellCurrencyCallback(intent: Intent?) {
try {
val transactionID =
- intent?.data?.getQueryParameter(TradeCryptoHelper.TRANSACTION_ID_PARAM) ?: return
+ intent?.data?.getQueryParameter(TRANSACTION_ID_PARAM) ?: return
val currency =
- intent.data?.getQueryParameter(TradeCryptoHelper.CURRENCY_CODE_PARAM) ?: return
+ intent.data?.getQueryParameter(CURRENCY_CODE_PARAM) ?: return
val amount =
- intent.data?.getQueryParameter(TradeCryptoHelper.CURRENCY_AMOUNT_PARAM) ?: return
+ intent.data?.getQueryParameter(CURRENCY_AMOUNT_PARAM) ?: return
val destinationAddress =
- intent.data?.getQueryParameter(TradeCryptoHelper.DEPOSIT_WALLET_ADDRESS_PARAM)
+ intent.data?.getQueryParameter(DEPOSIT_WALLET_ADDRESS_PARAM)
?: return
Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
@@ -63,7 +67,7 @@ class IntentHandler {
transactionId = transactionID
))
} catch (exception: Exception) {
- Timber.d("Not Moonpay URL")
+ Timber.d("Not MoonPay URL")
}
}
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 caa5f0117b..424f06d9ce 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
@@ -1,5 +1,6 @@
package com.tangem.tap.common.analytics
+import com.shopify.buy3.Storefront
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.extensions.filterNotNull
@@ -13,6 +14,11 @@ abstract class AnalyticsHandler {
params: Map = emptyMap()
)
+ abstract fun triggerEvent(
+ event: String,
+ params: Map
+ )
+
abstract fun logCardSdkError(
error: TangemSdkError,
actionToLog: Analytics.ActionToLog,
@@ -71,6 +77,13 @@ abstract class AnalyticsHandler {
)
}
}
-
}
+
+ fun logShopifyOrder(order: Storefront.Order) {
+ triggerEvent(getOrderEvent(), getOrderParams(order))
+ }
+
+ protected abstract fun getOrderEvent(): String
+
+ protected abstract fun getOrderParams(order: Storefront.Order): Map
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/AppsFlyerAnalyticsHandler.kt
index 9692defe86..64beb11b4a 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/AppsFlyerAnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AppsFlyerAnalyticsHandler.kt
@@ -1,7 +1,10 @@
package com.tangem.tap.common.analytics
import android.app.Application
+import com.appsflyer.AFInAppEventParameterName
+import com.appsflyer.AFInAppEventType
import com.appsflyer.AppsFlyerLib
+import com.shopify.buy3.Storefront
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
@@ -30,4 +33,30 @@ class AppsFlyerAnalyticsHandler(val context: Application): AnalyticsHandler() {
//TODO
}
+ override fun getOrderEvent(): String = AFInAppEventType.PURCHASE
+
+ override fun getOrderParams(order: Storefront.Order): Map {
+
+ val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
+
+ val discountCode =
+ (order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
+
+ val discountParams = if (discountCode != null ){
+ mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode)
+ } else {
+ mapOf()
+ }
+
+ return mapOf(
+ AFInAppEventParameterName.CONTENT_ID to sku,
+ AFInAppEventParameterName.REVENUE to order.totalPriceV2.amount,
+ AFInAppEventParameterName.CURRENCY to order.currencyCode.name
+ ) + discountParams
+ }
+
+ override fun triggerEvent(event: String, params: Map) {
+ AppsFlyerLib.getInstance().logEvent(context, event, params)
+ }
+
}
\ 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 106c07aae9..4e0371206a 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
@@ -2,9 +2,11 @@ package com.tangem.tap.common.analytics
import android.os.Bundle
import androidx.core.os.bundleOf
+import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
+import com.shopify.buy3.Storefront
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
@@ -57,4 +59,30 @@ object FirebaseAnalyticsHandler : AnalyticsHandler() {
params.forEach { (key, value) -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) }
FirebaseCrashlytics.getInstance().recordException(error)
}
+
+ override fun getOrderEvent(): String = FirebaseAnalytics.Event.PURCHASE
+
+ override fun getOrderParams(order: Storefront.Order): Map {
+
+ val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
+
+ val discountCode =
+ (order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
+
+ val discountParams = if (discountCode != null ){
+ mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode)
+ } else {
+ mapOf()
+ }
+
+ return mapOf(
+ FirebaseAnalytics.Param.ITEM_ID to sku,
+ FirebaseAnalytics.Param.VALUE to order.totalPriceV2.amount,
+ FirebaseAnalytics.Param.CURRENCY to order.currencyCode.name
+ ) + discountParams
+ }
+
+ override fun triggerEvent(event: String, params: Map) {
+ Firebase.analytics.logEvent(event, params.toBundle())
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt
index da77c37e05..b4824b60a3 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.common.analytics
import android.app.Application
+import com.shopify.buy3.Storefront
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
@@ -15,6 +16,10 @@ class GlobalAnalyticsHandler(val analyticsHandlers: List) :
analyticsHandlers.forEach { it.triggerEvent(event, card, blockchain) }
}
+ override fun triggerEvent(event: String, params: Map) {
+ analyticsHandlers.forEach { it.triggerEvent(event, params) }
+ }
+
override fun logCardSdkError(
error: TangemSdkError,
actionToLog: Analytics.ActionToLog,
@@ -28,6 +33,14 @@ class GlobalAnalyticsHandler(val analyticsHandlers: List) :
analyticsHandlers.forEach { it.logError(error, params) }
}
+ override fun getOrderEvent(): String {
+ return ""
+ }
+
+ override fun getOrderParams(order: Storefront.Order): Map {
+ return emptyMap()
+ }
+
companion object {
fun createDefaultAnalyticHandlers(context: Application): GlobalAnalyticsHandler {
return GlobalAnalyticsHandler(
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 30d8e1b18a..5fdd3a9279 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
@@ -19,6 +19,7 @@ import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCar
import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
import com.tangem.tap.features.send.ui.SendFragment
+import com.tangem.tap.features.shop.ui.ShopFragment
import com.tangem.tap.features.tokens.ui.AddTokensFragment
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
@@ -69,6 +70,7 @@ fun FragmentActivity.addOnBackPressedDispatcher(
private fun fragmentFactory(screen: AppScreen): Fragment {
return when (screen) {
AppScreen.Home -> HomeFragment()
+ AppScreen.Shop -> ShopFragment()
AppScreen.OnboardingNote -> OnboardingNoteFragment()
AppScreen.OnboardingWallet -> OnboardingWalletFragment()
AppScreen.OnboardingTwins -> TwinsCardsFragment()
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt
index f34b5e9ecf..81e215bcf9 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt
@@ -1,12 +1,12 @@
package com.tangem.tap.common.extensions
-import android.graphics.PorterDuff
+import android.graphics.*
import android.net.Uri
-import android.widget.ImageView
import android.widget.TextView
-import androidx.core.content.ContextCompat
+import androidx.constraintlayout.utils.widget.ImageFilterView
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
+import com.squareup.picasso.Transformation
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.IconsUtil
import com.tangem.blockchain.common.Token
@@ -14,7 +14,7 @@ import com.tangem.tap.domain.extensions.getCustomIconUrl
import com.tangem.wallet.R
fun Picasso.loadCurrenciesIcon(
- imageView: ImageView,
+ imageView: ImageFilterView,
textView: TextView,
token: Token? = null,
blockchain: Blockchain,
@@ -42,6 +42,7 @@ fun Picasso.loadCurrenciesIcon(
setTokenImage(imageView, textView, token)
}
this.load(url)
+ .transform(RoundedCornersTransform())
.noPlaceholder()
?.into(imageView,
object : Callback {
@@ -54,7 +55,7 @@ fun Picasso.loadCurrenciesIcon(
imageView.colorFilter = null
textView.text = null
}
- if (blockchain.isTestnet()) imageView.tint(R.color.tint)
+ if (blockchain.isTestnet()) imageView.saturation = 0f
}
})
}
@@ -68,42 +69,64 @@ private const val QCX = "QCX"
private const val VOYR = "VOYRME"
private fun setOfflineCurrencyImage(
- imageView: ImageView,
+ imageView: ImageFilterView,
textView: TextView,
token: Token?,
blockchain: Blockchain,
) {
- if (token != null) {
- setTokenImage(imageView, textView, token)
- } else {
- setBlockchainImage(imageView, textView, blockchain)
+ when (token) {
+ null -> setBlockchainImage(imageView, textView, blockchain)
+ else -> setTokenImage(imageView, textView, token)
}
- if (blockchain.isTestnet()) imageView.tint(R.color.tint)
-}
-
-fun ImageView.tint(colorRes: Int) {
-// val color = ContextCompat.getColor(context, colorRes);
-// ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color));
- this.setColorFilter(ContextCompat.getColor(context, colorRes), PorterDuff.Mode.DARKEN);
-
}
private fun setBlockchainImage(
- imageView: ImageView,
+ imageView: ImageFilterView,
textView: TextView,
blockchain: Blockchain,
) {
imageView.setImageResource(blockchain.getIconRes())
imageView.colorFilter = null
+ if (blockchain.isTestnet()) imageView.saturation = 0f
textView.text = null
}
private fun setTokenImage(
- imageView: ImageView,
+ imageView: ImageFilterView,
textView: TextView,
token: Token,
) {
imageView.setImageResource(R.drawable.shape_circle)
- imageView.setColorFilter(token.getColor())
+ if (token.blockchain.isTestnet()) {
+ imageView.saturation = 0f
+ } else {
+ imageView.setColorFilter(token.getColor())
+ }
textView.text = token.symbol.take(1)
}
+
+private class RoundedCornersTransform : Transformation {
+
+ override fun transform(source: Bitmap): Bitmap {
+ val size = source.width.coerceAtMost(source.height)
+ val x = (source.width - size) / 2
+ val y = (source.height - size) / 2
+ val squaredBitmap = Bitmap.createBitmap(source, x, y, size, size)
+ if (squaredBitmap != source) source.recycle()
+
+ val paint = Paint().apply {
+ shader = BitmapShader(squaredBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+ isAntiAlias = true
+ }
+
+ val rectF = RectF(0f, 0f, source.width.toFloat(), source.height.toFloat())
+ val radius = size / 8f
+ val bitmap = Bitmap.createBitmap(size, size, source.config)
+ Canvas(bitmap).drawRoundRect(rectF, radius, radius, paint)
+ squaredBitmap.recycle()
+
+ return bitmap
+ }
+
+ override fun key(): String = "rounded_corners"
+}
\ No newline at end of file
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
index f0f4ef1d00..7d54a7bf92 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt
@@ -3,6 +3,7 @@ package com.tangem.tap.common.extensions
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
+import android.net.Uri
import android.text.Spannable
import android.text.style.ForegroundColorSpan
import androidx.core.content.ContextCompat
@@ -21,24 +22,24 @@ fun String?.ellipsizeBeforeSpace(allowedSize: Int): String {
val startIndex = endIndex - sizeDifference
val newString = this.removeRange(startIndex, endIndex)
return newString.substring(0 until startIndex) + "..." +
- newString.substring(startIndex until newString.length)
+ newString.substring(startIndex until newString.length)
}
fun String.colorSegment(
- context: Context,
- color: Int,
- startIndex: Int = 0,
- endIndex: Int = this.length
+ 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
- )
- }
+ .also { spannable ->
+ spannable.setSpan(
+ ForegroundColorSpan(ContextCompat.getColor(context, color)),
+ startIndex,
+ endIndex,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ }
}
fun String.toQrCode(): Bitmap {
@@ -59,4 +60,6 @@ fun String.toQrCode(): Bitmap {
}
}
return bmp
-}
\ No newline at end of file
+}
+
+fun String.urlEncode(): String = Uri.encode(this)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt b/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt
index 132b8cc44c..3f8fa992ff 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt
@@ -3,6 +3,20 @@ package com.tangem.tap.common.extensions
/**
[REDACTED_AUTHOR]
*/
-fun StringBuilder.appendIf(value: String, predicate: (String) -> Boolean) {
+fun StringBuilder.appendIf(value: String, predicate: (String) -> Boolean): StringBuilder {
if (predicate(value)) this.append(value)
+ return this
+}
+
+fun StringBuilder.appendIfNotNull(value: String?, prefix: String? = null, postfix: String? = null): StringBuilder {
+ if (value == null) return this
+
+ prefix?.let { append(it) }
+ append(value)
+ postfix?.let { append(it) }
+ return this
+}
+
+fun String.appendIfNotNull(value: String?, prefix: String? = null, postfix: String? = null): String {
+ return StringBuilder(this).apply { appendIfNotNull(value, prefix, postfix) }.toString()
}
\ 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 e468ec2203..d241f8768c 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
@@ -37,10 +37,19 @@ fun Context.getColorCompat(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(this, colorRes)
}
+@ColorInt
+fun View.getColor(@ColorRes colorRes: Int): Int {
+ return ContextCompat.getColor(context, colorRes)
+}
+
fun View.getString(@StringRes id: Int): String {
return context.getString(id)
}
+fun View.getString(@StringRes id: Int, vararg formatArgs: String): String {
+ return context.getString(id, *formatArgs)
+}
+
fun View.getResourceName(): String {
return try {
resources.getResourceEntryName(id)
@@ -68,9 +77,16 @@ fun View.hide(invokeBeforeStateChanged: (() -> Unit)? = null) {
this.visibility = View.GONE
}
-fun View.makeInvisible() {
- if (this.visibility == View.INVISIBLE) return
- this.visibility = View.INVISIBLE
+fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> Unit)? = null) {
+ if (invisible) {
+ if (this.visibility == View.INVISIBLE) return
+
+ invokeBeforeStateChanged?.invoke()
+ this.visibility = View.INVISIBLE
+ } else {
+ this.show(invokeBeforeStateChanged)
+ }
+
}
fun Context.dpToPixels(dp: Int): Int =
@@ -158,4 +174,8 @@ fun Context.safeStartActivity(
} finally {
finally?.invoke()
}
+}
+
+fun View.getString(resId: Int, vararg formatArgs: Any?): String {
+ return context.getString(resId, formatArgs)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt b/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt
index 81be964cb1..50c90e1133 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/ViewGroup.kt
@@ -29,6 +29,10 @@ fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition())
TransitionManager.beginDelayedTransition(this, transition)
}
+fun View.beginDelayedTransition(transition: Transition = AutoTransition()) {
+ (this as? ViewGroup)?.beginDelayedTransition(transition)
+}
+
fun ChipGroup.fitChipsByGroupWidth() {
val layoutStateHandler = GlobalLayoutStateHandler(this)
layoutStateHandler.onStateChanged = stateHandler@{
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
index 3d33ceeaa8..b76e63b0e2 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
@@ -7,19 +7,29 @@ import com.tangem.common.services.Result
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.getFirstToken
-import com.tangem.tap.domain.topup.TradeCryptoHelper
+import com.tangem.tap.features.demo.isDemoWallet
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
import com.tangem.tap.network.NetworkConnectivity
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.store
+import kotlinx.coroutines.delay
+import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
suspend fun WalletManager.safeUpdate(): Result = try {
- update()
- Result.Success(wallet)
+ if (isDemoWallet()) {
+ delay(500)
+ Result.Success(wallet)
+ } else {
+ update()
+ Result.Success(wallet)
+ }
} catch (exception: Exception) {
+ Timber.e(exception)
+
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
Result.Failure(TapError.NoInternetConnection)
} else {
@@ -36,17 +46,17 @@ suspend fun WalletManager.safeUpdate(): Result = try {
}
fun WalletManager?.getToUpUrl(): String? {
+ val globalState = store.state.globalState
+ val currencyExchangeManager = globalState.currencyExchangeManager ?: return null
val wallet = this?.wallet ?: return null
- val config = store.state.globalState.configManager?.config ?: return null
- val defaultAddress = wallet.address
- return TradeCryptoHelper.getUrl(
- TradeCryptoHelper.Action.Buy,
- wallet.blockchain,
- wallet.blockchain.currency,
- defaultAddress,
- config.moonPayApiKey,
- config.moonPayApiSecretKey
+ val defaultAddress = wallet.address
+ return currencyExchangeManager.getUrl(
+ action = CurrencyExchangeManager.Action.Buy,
+ blockchain = wallet.blockchain,
+ cryptoCurrencyName = wallet.blockchain.currency,
+ fiatCurrency = globalState.appCurrency,
+ walletAddress = defaultAddress,
)
}
diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt
index 36e92a0ebe..1999c35855 100644
--- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt
+++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt
@@ -5,15 +5,18 @@ import android.view.View
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
+import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidgetState
import com.tangem.tap.domain.twins.TwinsCardWidget
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.test_leapfrog_fragment.*
+import com.tangem.wallet.databinding.TestLeapfrogFragmentBinding
class TestLeapfrogFragment : Fragment(R.layout.test_leapfrog_fragment) {
private lateinit var twinsCardWidget: TwinsCardWidget
+ private val binding: TestLeapfrogFragmentBinding by viewBinding(TestLeapfrogFragmentBinding::bind)
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -28,30 +31,29 @@ class TestLeapfrogFragment : Fragment(R.layout.test_leapfrog_fragment) {
val leapfrog = LeapfrogWidget(leapfrogContainer)
twinsCardWidget = TwinsCardWidget(leapfrog) { 200f }
- btn_twin_welcome.setOnClickListener {
+ binding.btnTwinWelcome.setOnClickListener {
twinsCardWidget.toWelcome()
}
- btn_twin_to_leapfrog.setOnClickListener {
+ binding.btnTwinToLeapfrog.setOnClickListener {
twinsCardWidget.toLeapfrog()
}
- btn_twin_activate.setOnClickListener {
+ binding.btnTwinActivate.setOnClickListener {
twinsCardWidget.toActivate()
}
-
- btn_lp_init.setOnClickListener {
+ binding.btnLpInit.setOnClickListener {
leapfrog.initViews()
}
- btn_lp_unfold.setOnClickListener {
+ binding.btnLpUnfold.setOnClickListener {
leapfrog.unfold()
}
- btn_lp_fold.setOnClickListener {
+ binding.btnLpFold.setOnClickListener {
leapfrog.fold()
}
- btn_lp_leap.setOnClickListener {
+ binding.btnLpLeap.setOnClickListener {
leapfrog.leap()
}
- btn_lp_leap_back.setOnClickListener {
+ binding.btnLpLeapBack.setOnClickListener {
leapfrog.leapBack()
}
}
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 d797ff9010..cbfa2565fb 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
@@ -11,6 +11,7 @@ import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOt
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
+import com.tangem.tap.features.shop.redux.ShopReducer
import com.tangem.tap.features.tokens.redux.TokensReducer
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
import org.rekotlin.Action
@@ -32,7 +33,8 @@ fun appReducer(action: Action, state: AppState?): AppState {
detailsState = DetailsReducer.reduce(action, state),
disclaimerState = DisclaimerReducer.reduce(action, state),
tokensState = TokensReducer.reduce(action, state),
- walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState)
+ walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
+ shopState = ShopReducer.reduce(action, state.shopState),
)
}
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 d3c6f3656b..719386cad9 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
@@ -23,6 +23,8 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWallet
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
import com.tangem.tap.features.send.redux.middlewares.SendMiddleware
import com.tangem.tap.features.send.redux.states.SendState
+import com.tangem.tap.features.shop.redux.ShopMiddleware
+import com.tangem.tap.features.shop.redux.ShopState
import com.tangem.tap.features.tokens.redux.TokensMiddleware
import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.wallet.redux.WalletState
@@ -44,6 +46,7 @@ data class AppState(
val disclaimerState: DisclaimerState = DisclaimerState(),
val tokensState: TokensState = TokensState(),
val walletConnectState: WalletConnectState = WalletConnectState(),
+ val shopState: ShopState = ShopState(),
) : StateType {
companion object {
@@ -62,7 +65,8 @@ data class AppState(
DisclaimerMiddleware().disclaimerMiddleware,
TokensMiddleware().tokensMiddleware,
WalletConnectMiddleware().walletConnectMiddleware,
- BackupMiddleware().backupMiddleware
+ BackupMiddleware().backupMiddleware,
+ ShopMiddleware().shopMiddleware,
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
index bcde7dae3b..6419e09ac2 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.common.redux
+import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.Currency
@@ -9,6 +10,8 @@ import com.tangem.tap.features.wallet.redux.Currency
interface StateDialog
sealed class AppDialog : StateDialog {
+ data class SimpleOkDialog(val header: String, val message: String, val onOk: VoidCallback? = null) : AppDialog()
+ data class SimpleOkDialogRes(val headerId: Int, val messageId: Int, val onOk: VoidCallback? = null) : AppDialog()
object ScanFailsDialog : AppDialog()
data class AddressInfoDialog(
val currency: Currency,
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 04b8f27743..58684ac334 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
@@ -14,7 +14,7 @@ import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.feedback.EmailData
import com.tangem.tap.features.feedback.FeedbackManager
-import com.tangem.tap.network.moonpay.MoonpayStatus
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import org.rekotlin.Action
sealed class GlobalAction : Action {
@@ -76,7 +76,7 @@ sealed class GlobalAction : Action {
data class SendFeedback(val emailData: EmailData) : GlobalAction()
data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction()
- object GetMoonPayStatus : GlobalAction() {
- data class Success(val moonPayStatus: MoonpayStatus) : GlobalAction()
+ object InitCurrencyExchangeManager : GlobalAction() {
+ data class Success(val exchangeManager: CurrencyExchangeManager) : 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 97fc6ac162..7675e42b03 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
@@ -2,7 +2,7 @@ package com.tangem.tap.common.redux.global
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
-import com.tangem.common.services.Result
+import com.tangem.common.extensions.ifNotNull
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
@@ -12,10 +12,11 @@ import com.tangem.tap.common.redux.AppState
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.network.moonpay.MoonpayService
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
+import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
+import com.tangem.tap.network.exchangeServices.onramper.OnramperService
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
-import timber.log.Timber
class GlobalMiddleware {
companion object {
@@ -69,19 +70,21 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState
}
is GlobalAction.UpdateFeedbackInfo -> {
store.state.globalState.feedbackManager?.infoHolder
- ?.setWalletsInfo(action.walletManagers)
+ ?.setWalletsInfo(action.walletManagers)
}
- is GlobalAction.GetMoonPayStatus -> {
- val apiKey = appState()?.globalState?.configManager?.config?.moonPayApiKey
- if (apiKey != null) {
+ is GlobalAction.InitCurrencyExchangeManager -> {
+ val config = appState()?.globalState?.configManager?.config
+ ifNotNull(
+ config?.onramperApiKey,
+ config?.moonPayApiKey,
+ config?.moonPayApiSecretKey,
+ ) { onramperKey, moonPayKey, moonPaySecretKey ->
scope.launch {
- val result = MoonpayService().getMoonpayStatus(apiKey)
- when (result) {
- is Result.Success -> {
- store.dispatchOnMain(GlobalAction.GetMoonPayStatus.Success(result.data))
- }
- is Result.Failure -> Timber.e(result.error)
- }
+ val onramper = OnramperService(onramperKey)
+ val moonPay = MoonPayService(moonPayKey, moonPaySecretKey)
+ val exchangeManager = CurrencyExchangeManager(onramper, moonPay)
+ exchangeManager.getStatus()
+ store.dispatchOnMain(GlobalAction.InitCurrencyExchangeManager.Success(exchangeManager))
}
}
}
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 94621117bc..d84c43379d 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
@@ -2,7 +2,6 @@ package com.tangem.tap.common.redux.global
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.onboarding.OnboardingManager
-import com.tangem.tap.network.moonpay.MoonpayStatus
import com.tangem.tap.preferencesStorage
import org.rekotlin.Action
@@ -19,7 +18,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.Onboarding.Start -> {
val onboardingManager = if (action.scanResponse != null) {
val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage
- OnboardingManager(action.scanResponse, usedCardsPrefStorage)
+ OnboardingManager(action.scanResponse, usedCardsPrefStorage)
} else {
null
}
@@ -67,19 +66,8 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.HideDialog -> {
globalState.copy(dialog = null)
}
- is GlobalAction.GetMoonPayStatus.Success -> {
- val fiatExchangeIsEnabled = globalState.configManager?.config?.isTopUpEnabled ?: false
- val moonpayStatus = if (fiatExchangeIsEnabled) {
- action.moonPayStatus
- } else {
- MoonpayStatus(
- isBuyAllowed = false,
- isSellAllowed = false,
- availableToBuy = emptyList(),
- availableToSell = emptyList()
- )
- }
- globalState.copy(moonpayStatus = moonpayStatus)
+ is GlobalAction.InitCurrencyExchangeManager.Success -> {
+ globalState.copy(currencyExchangeManager = action.exchangeManager)
}
is GlobalAction.SetIfCardVerifiedOnline ->
globalState.copy(cardVerifiedOnline = action.verified)
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 c607125cb8..ace0a06ae7 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
@@ -11,7 +11,7 @@ import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.feedback.FeedbackManager
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
-import com.tangem.tap.network.moonpay.MoonpayStatus
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import org.rekotlin.StateType
data class GlobalState(
@@ -27,7 +27,7 @@ data class GlobalState(
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
val scanCardFailsCounter: Int = 0,
val dialog: StateDialog? = null,
- val moonpayStatus: MoonpayStatus? = null,
+ val currencyExchangeManager: CurrencyExchangeManager? = null,
val resources: AndroidResources = AndroidResources(),
val analyticsHandlers: AnalyticsHandler? = null,
) : StateType
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 61e3d263da..6f68439f61 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
@@ -11,6 +11,7 @@ data class NavigationState(
enum class AppScreen {
Home,
+ Shop,
Disclaimer,
OnboardingNote, OnboardingWallet, OnboardingTwins, OnboardingOther,
Wallet, WalletDetails,
diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt
new file mode 100644
index 0000000000..3da94dc568
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt
@@ -0,0 +1,230 @@
+package com.tangem.tap.common.shop
+
+import android.app.Application
+import android.content.Intent
+import com.google.android.gms.wallet.PaymentData
+import com.shopify.buy3.Storefront
+import com.tangem.tap.common.analytics.AnalyticsHandler
+import com.tangem.tap.common.shop.data.ProductType
+import com.tangem.tap.common.shop.data.TangemProduct
+import com.tangem.tap.common.shop.data.TotalSum
+import com.tangem.tap.common.shop.shopify.ShopifyService
+import com.tangem.tap.common.shop.shopify.ShopifyShop
+import com.tangem.tap.common.shop.shopify.data.CheckoutItem
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import java.math.BigDecimal
+import java.util.*
+
+class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
+
+ private val shopifyService = ShopifyService(application, shopifyShop)
+
+ private lateinit var product: Storefront.Product
+
+ private val checkouts = mutableMapOf()
+ private val variants = mutableMapOf()
+
+ private lateinit var googlePayService: GooglePayService
+
+ suspend fun getProducts(): Result> {
+ val result = shopifyService.getProducts()
+
+ result.onSuccess {
+ product = it.first {
+ val variantsSku = it.variants.edges.map { it.node.sku }
+ variantsSku.contains(ProductType.WALLET_2_CARDS.sku) && variantsSku.contains(
+ ProductType.WALLET_3_CARDS.sku
+ )
+ }
+ product.variants.edges.map { it.node }
+ .forEach { variant ->
+ if (variant.sku == ProductType.WALLET_2_CARDS.sku) {
+ variants[ProductType.WALLET_2_CARDS] = variant
+ } else if (variant.sku == ProductType.WALLET_3_CARDS.sku) {
+ variants[ProductType.WALLET_3_CARDS] = variant
+ }
+ }
+
+
+ val twoCardsProduct = TangemProduct(
+ type = ProductType.WALLET_2_CARDS,
+ totalSum = TotalSum(
+ finalValue = variants[ProductType.WALLET_2_CARDS]?.priceV2?.format(),
+ beforeDiscount = variants[ProductType.WALLET_2_CARDS]?.compareAtPriceV2?.format()
+ )
+ )
+ val threeCardsProduct = TangemProduct(
+ type = ProductType.WALLET_3_CARDS,
+ totalSum = TotalSum(
+ finalValue = variants[ProductType.WALLET_3_CARDS]?.priceV2?.format(),
+ beforeDiscount = variants[ProductType.WALLET_3_CARDS]?.compareAtPriceV2?.format()
+ )
+ )
+ createCheckouts()
+ return Result.success(listOf(twoCardsProduct, threeCardsProduct))
+ }
+ return Result.failure(result.exceptionOrNull()!!)
+ }
+
+ private suspend fun createCheckouts() {
+ variants.keys.map { coroutineScope { async { createCheckout(it) } } }.awaitAll()
+ }
+
+ private suspend fun createCheckout(productType: ProductType) {
+ val checkoutItem = CheckoutItem(variants[productType]!!.id, 1)
+ val result = shopifyService.createCheckout(listOf(checkoutItem))
+ result.onSuccess { checkout ->
+ checkouts[productType] = checkout
+ }
+ }
+
+ suspend fun checkIfGooglePayAvailable(googlePayService: GooglePayService): Result {
+ this.googlePayService = googlePayService
+ return googlePayService.checkIfGooglePayAvailable()
+ }
+
+ fun buyWithGooglePay(productType: ProductType) {
+ val totalPrice = checkouts[productType]!!.totalPriceV2.amount
+ googlePayService.payWithGooglePay(
+ totalPriceCents = totalPrice, currencyCode = checkouts[productType]!!.currencyCode.name,
+ merchantID = shopifyService.shop.merchantID
+ )
+ }
+
+// fun subscribeToGooglePayResult(
+// productType: ProductType,
+// resultCallback: (Result) -> Unit
+// ) {
+// googlePayService.responseCallback = { result ->
+// result.onFailure { }
+// result.onSuccess {
+// completeTokenizedPayment(it, productType)
+// }
+// }
+// }
+
+ suspend fun handleGooglePayResult(
+ resultCode: Int,
+ data: Intent?,
+ productType: ProductType
+ ): Result {
+ val result = googlePayService.handleResponseFromGooglePay(resultCode, data)
+ result.onSuccess {
+ val finalizePaymentResult = completeTokenizedPayment(it, productType)
+ finalizePaymentResult.onSuccess {
+ return Result.success(Unit)
+ }
+ return Result.failure(finalizePaymentResult.exceptionOrNull()!!)
+ }
+ return Result.failure(result.exceptionOrNull()!!)
+ }
+
+ private suspend fun completeTokenizedPayment(
+ paymentData: PaymentData,
+ productType: ProductType
+ ): Result {
+ val checkout = checkouts[productType]!!
+ val googlePayResponse =
+ googlePayService.parsePaymentData(paymentData)
+ ?: return Result.failure(Exception("cannot parse GPay result"))
+
+ val amount =
+ Storefront.MoneyInput(checkout.totalPriceV2.amount, checkout.totalPriceV2.currencyCode)
+ val idempotencyKey = UUID.randomUUID().toString()
+ val addressGPay = googlePayResponse.billingAddress
+
+ val address = Storefront.MailingAddressInput().apply {
+ lastName = addressGPay.name
+ address1 = addressGPay.address1
+ address2 = addressGPay.address2 + addressGPay.address3
+ province = addressGPay.administrativeArea
+ zip = addressGPay.postalCode
+ phone = addressGPay.phoneNumber
+ }
+
+
+ val payment = Storefront.TokenizedPaymentInputV3(
+ amount,
+ idempotencyKey,
+ address,
+ paymentData.toJson(),
+ Storefront.PaymentTokenType.GOOGLE_PAY
+ )
+ .setTest(true)
+
+ return shopifyService.completeWithTokenizedPayment(
+ payment = payment,
+ checkoutID = checkout.id
+ )
+ }
+
+ suspend fun applyPromoCode(promoCode: String): Result> {
+ val products = variants.keys
+ .map { coroutineScope { async { applyPromoCode(promoCode, it) } } }
+ .awaitAll()
+ .map { result -> result.getOrElse { return Result.failure(it) } }
+
+ return Result.success(products)
+ }
+
+ suspend fun applyPromoCode(promoCode: String, productType: ProductType): Result {
+ val checkout = checkouts[productType] ?: return Result.failure(Exception("No checkout"))
+
+ val result = if (promoCode.isBlank()) {
+ shopifyService.removeDiscount(checkout.id)
+ } else {
+ shopifyService.applyDiscount(promoCode, checkout.id)
+ }
+
+ result.onSuccess {
+ checkouts[productType] = it
+ return Result.success(
+ TangemProduct(
+ productType,
+ TotalSum(
+ finalValue = it.totalPriceV2.format(),
+ beforeDiscount = variants[productType]!!.compareAtPriceV2.format()
+ ),
+ appliedDiscount = it.getAppliedDiscount()
+ )
+
+ )
+ }
+ return Result.failure(result.exceptionOrNull()!!)
+ }
+
+ fun getCheckoutUrl(productType: ProductType): String {
+ return checkouts[productType]!!.webUrl
+ }
+
+ suspend fun waitForCheckout(
+ productType: ProductType,
+ analyticsHandler: AnalyticsHandler?
+ ) {
+ val result = shopifyService.checkout(true, checkouts[productType]!!.id)
+ result.onSuccess {
+ if (it.order != null) {
+ analyticsHandler?.logShopifyOrder(order = it.order)
+ }
+ }
+ }
+
+ companion object {
+ const val TANGEM_WALLET_2_CARDS_SKU = "TG115x2"
+ const val TANGEM_WALLET_3_CARDS_SKU = "TG115x3"
+ }
+}
+
+private fun Storefront.MoneyV2.format(): String {
+ val currencySymbol = Currency.getInstance(currencyCode.name).symbol
+ val amountFormatted = BigDecimal(amount).setScale(2)
+ return currencySymbol + amountFormatted
+}
+
+private fun Storefront.Checkout.getAppliedDiscount(): String? {
+ val discountApplication =
+ discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication
+ return discountApplication?.code
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/data/ProductType.kt b/app/src/main/java/com/tangem/tap/common/shop/data/ProductType.kt
new file mode 100644
index 0000000000..89116f8096
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/data/ProductType.kt
@@ -0,0 +1,8 @@
+package com.tangem.tap.common.shop.data
+
+import com.tangem.tap.common.shop.TangemShopService
+
+enum class ProductType(val sku: String) {
+ WALLET_2_CARDS(TangemShopService.TANGEM_WALLET_2_CARDS_SKU),
+ WALLET_3_CARDS(TangemShopService.TANGEM_WALLET_3_CARDS_SKU)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/data/TangemProduct.kt b/app/src/main/java/com/tangem/tap/common/shop/data/TangemProduct.kt
new file mode 100644
index 0000000000..022af2b4ea
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/data/TangemProduct.kt
@@ -0,0 +1,7 @@
+package com.tangem.tap.common.shop.data
+
+data class TangemProduct(
+ val type: ProductType,
+ val totalSum: TotalSum? = null,
+ val appliedDiscount: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/data/TotalSum.kt b/app/src/main/java/com/tangem/tap/common/shop/data/TotalSum.kt
new file mode 100644
index 0000000000..a9e920745f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/data/TotalSum.kt
@@ -0,0 +1,6 @@
+package com.tangem.tap.common.shop.data
+
+data class TotalSum(
+ val finalValue: String? = null,
+ val beforeDiscount: String? = null,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt
new file mode 100644
index 0000000000..13c3913ebe
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt
@@ -0,0 +1,148 @@
+package com.tangem.tap.common.shop
+
+import android.app.Activity
+import android.app.Activity.RESULT_CANCELED
+import android.app.Activity.RESULT_OK
+import android.content.Intent
+import android.util.Log
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.wallet.*
+import com.tangem.common.core.TangemSdkError
+import com.tangem.tap.common.shop.googlepay.GooglePayUtil
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.json.JSONException
+import org.json.JSONObject
+import timber.log.Timber
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+class GooglePayService(private val paymentsClient: PaymentsClient, private val activity: Activity) {
+
+// var responseCallback: ((Result) -> Unit)? = null
+
+ suspend fun checkIfGooglePayAvailable(): Result {
+
+ val isReadyToPayJson = GooglePayUtil.isReadyToPayRequest() ?: return Result.success(false)
+ val request = IsReadyToPayRequest.fromJson(isReadyToPayJson.toString())
+
+ val task = paymentsClient.isReadyToPay(request)
+ return withContext(Dispatchers.IO) {
+ suspendCoroutine { continuation ->
+ task.addOnCompleteListener { completedTask ->
+ try {
+ val result = completedTask.getResult(ApiException::class.java)
+ continuation.resume(Result.success(true))
+ } catch (exception: ApiException) {
+ // Process error
+ Timber.w("isReadyToPay failed: $exception")
+ continuation.resume(Result.failure(exception))
+ }
+ }
+ }
+ }
+ }
+
+ fun payWithGooglePay(totalPriceCents: String, currencyCode: String, merchantID: String) {
+ val paymentDataRequestJson = GooglePayUtil.getPaymentDataRequest(
+ totalPriceCents,
+ currencyCode = currencyCode,
+ countryCode = "RU",
+ merchantID = merchantID
+ )
+ if (paymentDataRequestJson == null) {
+ Timber.e("RequestPayment: can't fetch payment data request")
+ return
+ }
+ val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())
+
+ AutoResolveHelper.resolveTask(
+ paymentsClient.loadPaymentData(request), activity, LOAD_PAYMENT_DATA_REQUEST_CODE
+ )
+ }
+
+ fun handleResponseFromGooglePay(resultCode: Int, data: Intent?): Result {
+ val result = when (resultCode) {
+ RESULT_OK -> {
+ val paymentData = data?.let { intent -> PaymentData.getFromIntent(intent) }
+ if (paymentData == null) {
+ Result.failure(Exception("No payment data"))
+ } else {
+ Result.success(paymentData)
+ }
+ }
+ RESULT_CANCELED -> {
+ Result.failure(TangemSdkError.UserCancelled())
+ }
+ AutoResolveHelper.RESULT_ERROR -> {
+ val statusCode = AutoResolveHelper.getStatusFromIntent(data)?.statusCode
+ if (statusCode == null) {
+ Result.failure(Exception("Unknown Status"))
+ } else {
+ Result.failure(Exception("$statusCode"))
+ }
+
+ }
+ else -> Result.failure(Exception("Unknown Status"))
+ }
+// responseCallback?.invoke(result)
+ return result
+ }
+
+ fun parsePaymentData(paymentData: PaymentData): GooglePayResponse? {
+ val paymentInformation = paymentData.toJson()
+
+ try {
+ // Token will be null if PaymentDataRequest was not constructed using fromJson(String).
+ val paymentMethodData =
+ JSONObject(paymentInformation).getJSONObject("paymentMethodData")
+ val addressJson = paymentMethodData.getJSONObject("info")
+ .getJSONObject("billingAddress")
+
+ val address = Address(
+ name = addressJson.getString("name"),
+ postalCode = addressJson.getString("postalCode"),
+ countryCode = addressJson.getString("countryCode"),
+ phoneNumber = addressJson.getString("phoneNumber"),
+ address1 = addressJson.getString("address1"),
+ address2 = addressJson.getString("address2"),
+ address3 = addressJson.getString("address3"),
+ locality = addressJson.getString("locality"),
+ administrativeArea = addressJson.getString("administrativeArea"),
+ sortingCode = addressJson.getString("sortingCode"),
+ )
+
+ val token = paymentMethodData
+ .getJSONObject("tokenizationData")
+ .getString("token")
+
+ return GooglePayResponse(address, token)
+
+ } catch (e: JSONException) {
+ Log.e("handlePaymentSuccess", "Error: " + e.toString())
+ }
+ return null
+ }
+
+ companion object {
+ const val LOAD_PAYMENT_DATA_REQUEST_CODE = 315
+ }
+}
+
+data class GooglePayResponse(
+ val billingAddress: Address,
+ val token: String,
+)
+
+data class Address(
+ val name: String,
+ val postalCode: String,
+ val countryCode: String,
+ val phoneNumber: String,
+ val address1: String,
+ val address2: String,
+ val address3: String,
+ val locality: String,
+ val administrativeArea: String,
+ val sortingCode: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt
new file mode 100644
index 0000000000..610ab10c9d
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt
@@ -0,0 +1,137 @@
+package com.tangem.tap.common.shop.googlepay
+
+import android.app.Activity
+import com.google.android.gms.wallet.PaymentsClient
+import com.google.android.gms.wallet.Wallet
+import com.google.android.gms.wallet.WalletConstants
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+
+object GooglePayUtil {
+ private val baseRequest = JSONObject().apply {
+ put("apiVersion", 2)
+ put("apiVersionMinor", 0)
+ }
+
+ private fun gatewayTokenizationSpecification(merchantID: String): JSONObject {
+ return JSONObject().apply {
+ put("type", "PAYMENT_GATEWAY")
+ put(
+ "parameters", JSONObject(
+ mapOf(
+ "gateway" to "shopify",
+ "gatewayMerchantId" to merchantID
+ )
+ )
+ )
+ }
+ }
+
+ private val allowedCardNetworks = JSONArray(
+ listOf(
+ "AMEX",
+ "DISCOVER",
+ "INTERAC",
+ "JCB",
+ "MASTERCARD",
+ "VISA"
+ )
+ )
+
+ private val allowedCardAuthMethods = JSONArray(
+ listOf(
+ "PAN_ONLY",
+ "CRYPTOGRAM_3DS"
+ )
+ )
+
+ private fun baseCardPaymentMethod(): JSONObject {
+ return JSONObject().apply {
+
+ val parameters = JSONObject().apply {
+ put("allowedAuthMethods", allowedCardAuthMethods)
+ put("allowedCardNetworks", allowedCardNetworks)
+ put("billingAddressRequired", true)
+ put("billingAddressParameters", JSONObject().apply {
+ put("format", "FULL")
+ })
+ }
+
+ put("type", "CARD")
+ put("parameters", parameters)
+ }
+ }
+
+ private fun cardPaymentMethod(merchantID: String): JSONObject {
+ val cardPaymentMethod = baseCardPaymentMethod()
+ cardPaymentMethod.put("tokenizationSpecification", gatewayTokenizationSpecification(merchantID))
+
+ return cardPaymentMethod
+ }
+
+ fun createPaymentsClient(activity: Activity): PaymentsClient {
+ val walletOptions = Wallet.WalletOptions.Builder()
+ .setEnvironment(PAYMENTS_ENVIRONMENT)
+ .build()
+
+ return Wallet.getPaymentsClient(activity, walletOptions)
+ }
+
+ fun isReadyToPayRequest(): JSONObject? {
+ return try {
+ baseRequest.apply {
+ put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod()))
+ }
+
+ } catch (e: JSONException) {
+ null
+ }
+ }
+
+ private fun getTransactionInfo(
+ price: String,
+ countryCode: String,
+ currencyCode: String
+ ): JSONObject {
+ return JSONObject().apply {
+ put("totalPrice", price)
+ put("totalPriceStatus", "FINAL")
+ put("countryCode", countryCode)
+ put("currencyCode", currencyCode)
+ }
+ }
+
+ private val merchantInfo: JSONObject =
+ JSONObject().put("merchantName", "Example Merchant")
+
+
+ fun getPaymentDataRequest(
+ price: String,
+ countryCode: String,
+ currencyCode: String,
+ merchantID: String
+ ): JSONObject? {
+ try {
+ return baseRequest.apply {
+ put("allowedPaymentMethods", JSONArray().put(cardPaymentMethod(merchantID)))
+ put("transactionInfo", getTransactionInfo(price, countryCode, currencyCode))
+ put("merchantInfo", merchantInfo)
+
+ // An optional shipping address requirement is a top-level property of the
+ // PaymentDataRequest JSON object.
+ val shippingAddressParameters = JSONObject().apply {
+ put("phoneNumberRequired", false)
+// put("allowedCountryCodes", JSONArray(listOf("US", "GB")))
+ }
+ put("shippingAddressParameters", shippingAddressParameters)
+ put("shippingAddressRequired", true)
+ }
+ } catch (e: JSONException) {
+ return null
+ }
+ }
+}
+
+
+const val PAYMENTS_ENVIRONMENT = WalletConstants.ENVIRONMENT_TEST
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt
new file mode 100644
index 0000000000..a33fb82b84
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt
@@ -0,0 +1,383 @@
+package com.tangem.tap.common.shop.shopify
+
+import android.app.Application
+import com.shopify.buy3.GraphCallResult
+import com.shopify.buy3.GraphClient
+import com.shopify.buy3.RetryHandler
+import com.shopify.buy3.Storefront.*
+import com.shopify.graphql.support.ID
+import com.shopify.graphql.support.Input
+import com.tangem.tap.common.shop.shopify.data.CheckoutItem
+import com.tangem.tap.common.shop.shopify.data.checkoutFieldsFragment
+import com.tangem.tap.common.shop.shopify.data.collectionFieldsFragment
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.util.concurrent.TimeUnit
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+
+class ShopifyService(private val application: Application, val shop: ShopifyShop) {
+ val client: GraphClient by lazy { initClient() }
+
+
+ suspend fun getShopName(): Result {
+ val query = query { rootQuery: QueryRootQuery ->
+ rootQuery
+ .shop { shopQuery: ShopQuery ->
+ shopQuery
+ .name()
+ }
+ }
+ return when (val result = queryAsync(query)) {
+ is GraphCallResult.Success -> {
+ val name = result.response.data!!.shop.name
+ Result.success(name)
+ }
+ is GraphCallResult.Failure -> {
+ Result.failure(result.error)
+ }
+ }
+ }
+
+ suspend fun getProducts(collectionTitleFilter: String? = null): Result> {
+ val filter = collectionTitleFilter?.let { "title:\"$it\"" }
+
+ val query = query { rootQuery: QueryRootQuery ->
+ rootQuery
+ .collections(
+ { arg -> arg.first(250).query(filter) },
+ ) { collectionConnectionQuery ->
+ collectionConnectionQuery.collectionFieldsFragment()
+ }
+ }
+ return when (val result = queryAsync(query)) {
+ is GraphCallResult.Success -> {
+ val products = result.response.data!!.collections.edges
+ .map { it.node.products }
+ .flatMap { it.edges }
+ .map { it.node }
+ Result.success(products)
+ }
+ is GraphCallResult.Failure -> {
+ Result.failure(result.error)
+ }
+ }
+ }
+
+ suspend fun checkout(pollUntilOrder: Boolean, checkoutID: ID): Result {
+
+ val query = query { rootQuery: QueryRootQuery ->
+ rootQuery
+ .node(checkoutID) { query ->
+ query.onCheckout { checkoutQuery ->
+ with(checkoutQuery) {
+ checkoutFieldsFragment()
+ }
+ }
+ }
+ }
+ val retryHandler = RetryHandler.build(
+ 1, TimeUnit.SECONDS
+ ) {
+ this.retryWhen { result ->
+ when (result) {
+ is GraphCallResult.Success -> {
+ val checkout = result.response.data?.node as? Checkout
+ checkout?.order == null
+ }
+ is GraphCallResult.Failure -> false
+ }
+ }
+ }
+
+ val result = if (pollUntilOrder) queryAsync(query, retryHandler) else queryAsync(query)
+ return when (result) {
+ is GraphCallResult.Success -> {
+ val checkout = result.response.data!!.node as? Checkout
+ if (checkout != null) {
+ Result.success(checkout)
+ } else {
+ Result.failure(ShopifyError.Unknown)
+ }
+
+ }
+ is GraphCallResult.Failure -> {
+ Result.failure(result.error)
+ }
+ }
+ }
+
+ suspend fun createCheckout(
+ checkoutItems: List,
+ checkoutID: ID? = null
+ ): Result {
+
+
+ val storefrontLineItems: MutableList = checkoutItems
+ .map { CheckoutLineItemInput(it.quantity, it.id) }.toMutableList()
+
+ val query = if (checkoutID != null) {
+ mutation { mutationQuery: MutationQuery ->
+ mutationQuery
+ .checkoutLineItemsReplace(
+ storefrontLineItems, checkoutID
+ ) { payloadQuery: CheckoutLineItemsReplacePayloadQuery ->
+ payloadQuery
+ .checkout { checkoutQuery: CheckoutQuery ->
+ checkoutQuery.checkoutFieldsFragment()
+ }
+ .userErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ userErrorQuery
+ .field()
+ .message()
+ }
+ }
+ }
+ } else {
+ val input = CheckoutCreateInput()
+ .setLineItemsInput(
+ Input.value(storefrontLineItems)
+ )
+ mutation { mutationQuery: MutationQuery ->
+ mutationQuery
+ .checkoutCreate(
+ input
+ ) { payloadQuery: CheckoutCreatePayloadQuery ->
+ payloadQuery
+ .checkout { checkoutQuery: CheckoutQuery ->
+ checkoutQuery.checkoutFieldsFragment()
+ }
+ .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ userErrorQuery
+ .field()
+ .message()
+ }
+ }
+ }
+ }
+ return runCheckoutMutation(query)
+ }
+
+ suspend fun applyDiscount(discountCode: String, checkoutID: ID): Result {
+ val query = mutation { mutationQuery: MutationQuery ->
+ mutationQuery
+ .checkoutDiscountCodeApplyV2(
+ discountCode, checkoutID
+ ) { payloadQuery: CheckoutDiscountCodeApplyV2PayloadQuery ->
+ payloadQuery
+ .checkout { checkoutQuery: CheckoutQuery ->
+ checkoutQuery.checkoutFieldsFragment()
+ }
+ .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ userErrorQuery
+ .field()
+ .message()
+ }
+ }
+ }
+ return runCheckoutMutation(query)
+ }
+
+ suspend fun removeDiscount(checkoutID: ID): Result {
+ val query = mutation { mutationQuery: MutationQuery ->
+ mutationQuery
+ .checkoutDiscountCodeRemove(
+ checkoutID
+ ) { payloadQuery: CheckoutDiscountCodeRemovePayloadQuery ->
+ payloadQuery
+ .checkout { checkoutQuery: CheckoutQuery ->
+ checkoutQuery.checkoutFieldsFragment()
+ }
+ .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ userErrorQuery
+ .field()
+ .message()
+ }
+ }
+ }
+ return runCheckoutMutation(query)
+ }
+
+ suspend fun updateAddress(
+ address: MailingAddress,
+ checkoutID: ID,
+ waitForShippingRates: Boolean
+ ): Result {
+ val input = MailingAddressInput()
+ .setAddress1(address.address1)
+ .setAddress2(address.address2)
+ .setCity(address.city)
+ .setCountry(address.country)
+ .setFirstName(address.firstName)
+ .setLastName(address.lastName)
+ .setPhone(address.phone)
+ .setProvince(address.province)
+ .setZip(address.zip)
+
+ val query = mutation { mutationQuery: MutationQuery ->
+ mutationQuery
+ .checkoutShippingAddressUpdateV2(
+ input, checkoutID
+ ) { shippingAddressUpdatePayloadQuery: CheckoutShippingAddressUpdateV2PayloadQuery ->
+ shippingAddressUpdatePayloadQuery
+ .checkout { checkoutQuery: CheckoutQuery ->
+ checkoutQuery.checkoutFieldsFragment()
+ }
+ .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
+ userErrorQuery
+ .field()
+ .message()
+ }
+ }
+ }
+
+ return runCheckoutMutation(query)
+ }
+
+ suspend fun updateEmail(email: String?, checkoutID: ID): Result {
+ val query = mutation { mutationQuery: MutationQuery ->
+ mutationQuery
+ .checkoutEmailUpdateV2(
+ checkoutID, email
+ ) { emailUpdatePayloadQuery: CheckoutEmailUpdateV2PayloadQuery ->
+ emailUpdatePayloadQuery
+ .checkout { checkoutQuery: CheckoutQuery ->
+ checkoutQuery.checkoutFieldsFragment()
+ }
+ .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ userErrorQuery
+ .field()
+ .message()
+ }
+ }
+ }
+ return runCheckoutMutation(query)
+ }
+
+ suspend fun updateShippingRate(handle: String?, checkoutID: ID): Result {
+ val query = mutation { mutationQuery: MutationQuery ->
+ mutationQuery
+ .checkoutShippingLineUpdate(
+ checkoutID, handle
+ ) { shippingLineUpdatePayloadQuery: CheckoutShippingLineUpdatePayloadQuery ->
+ shippingLineUpdatePayloadQuery
+ .checkout { checkoutQuery: CheckoutQuery ->
+ checkoutQuery.checkoutFieldsFragment()
+ }
+ .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ userErrorQuery
+ .field()
+ .message()
+ }
+ }
+ }
+ return runCheckoutMutation(query)
+ }
+
+ suspend fun completeWithTokenizedPayment(
+ payment: TokenizedPaymentInputV3,
+ checkoutID: ID
+ ): Result {
+
+ val query = mutation { mutationQuery: MutationQuery ->
+ mutationQuery
+ .checkoutCompleteWithTokenizedPaymentV3(
+ checkoutID, payment
+ ) { payloadQuery: CheckoutCompleteWithTokenizedPaymentV3PayloadQuery ->
+ payloadQuery
+ .payment { paymentQuery: PaymentQuery ->
+ paymentQuery
+ .ready()
+ .errorMessage()
+ }
+ .checkout { checkoutQuery: CheckoutQuery ->
+ checkoutQuery
+ .ready()
+ }
+ .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ userErrorQuery
+ .field()
+ .message()
+ }
+ }
+ }
+ return runCheckoutMutation(query)
+ }
+
+ fun startGooglePaySession() {
+// PaySession()
+ }
+
+ private suspend fun runCheckoutMutation(mutation: MutationQuery): Result {
+ return when (val result = mutationQueryAsync(mutation)) {
+ is GraphCallResult.Success -> {
+ val checkout = result.response.data!!.checkoutCreate?.checkout
+ ?: result.response.data!!.checkoutDiscountCodeApplyV2?.checkout
+ ?: result.response.data!!.checkoutDiscountCodeRemove?.checkout
+ ?: result.response.data!!.checkoutShippingAddressUpdateV2?.checkout
+ ?: result.response.data!!.checkoutEmailUpdateV2?.checkout
+ ?: result.response.data!!.checkoutShippingLineUpdate?.checkout
+ ?: result.response.data!!.checkoutCompleteWithTokenizedPaymentV3.checkout
+
+ Result.success(checkout)
+ }
+ is GraphCallResult.Failure -> Result.failure(result.error)
+ }
+ }
+
+ private suspend fun queryAsync(
+ query: QueryRootQuery,
+ retryHandler: RetryHandler
+ ): GraphCallResult =
+ withContext(Dispatchers.IO) {
+ suspendCoroutine { continuation ->
+ client.queryGraph(query).enqueue(retryHandler = retryHandler) { result ->
+ continuation.resume(result)
+ }
+ }
+ }
+
+ private suspend fun queryAsync(
+ query: QueryRootQuery,
+ ): GraphCallResult =
+ withContext(Dispatchers.IO) {
+ suspendCoroutine { continuation ->
+ client.queryGraph(query).enqueue { result ->
+ continuation.resume(result)
+ }
+ }
+ }
+
+ private suspend fun mutationQueryAsync(query: MutationQuery): GraphCallResult =
+ withContext(Dispatchers.IO) {
+ suspendCoroutine { continuation ->
+ client.mutateGraph(query).enqueue { result ->
+ continuation.resume(result)
+ }
+ }
+ }
+
+
+ private fun initClient(): GraphClient {
+ return GraphClient.build(
+ application,
+ shop.domain,
+ shop.storefrontApiKey
+ ) {
+// httpCache(application.filesDir) {
+// cacheMaxSizeBytes = (1024 * 1024 * 10)
+// defaultCachePolicy =
+// HttpCachePolicy.Default.CACHE_FIRST.expireAfter(20, TimeUnit.MINUTES)
+// }
+ }
+
+ }
+}
+
+
+sealed class ShopifyError : Throwable() {
+ object Unknown : ShopifyError()
+ object GooglePayFailed : ShopifyError()
+ class UserError(val errorMessage: String)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyShop.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyShop.kt
new file mode 100644
index 0000000000..5fde9c60ff
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyShop.kt
@@ -0,0 +1,10 @@
+package com.tangem.tap.common.shop.shopify
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class ShopifyShop(
+ val domain: String,
+ val storefrontApiKey: String,
+ val merchantID: String,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt
new file mode 100644
index 0000000000..849c1f33c3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt
@@ -0,0 +1,134 @@
+package com.tangem.tap.common.shop.shopify.data
+
+import com.shopify.buy3.Storefront
+
+fun Storefront.CheckoutQuery.checkoutFieldsFragment() {
+ // id()
+ ready()
+ webUrl()
+ currencyCode()
+ lineItemsSubtotalPrice { it.amount() }
+ totalPriceV2 {
+ it.currencyCode()
+ it.amount() }
+ lineItems({ arg -> arg.first(250) }) {
+ it.edges {
+ it.node {
+// it.id()
+ it.title()
+ it.quantity()
+ it.variant() {
+ it.priceV2() { it.amount() }
+ }
+ }
+ }
+ }
+ shippingLine {
+ it.handle()
+ it.title()
+ it.priceV2 { it.amount() }
+ }
+ availableShippingRates {
+ it.ready()
+ it.shippingRates {
+ it.handle()
+ it.title()
+ it.priceV2 { it.amount() }
+ }
+ }
+ shippingAddress {
+ it.address1()
+ it.address2()
+ it.city()
+// .company()
+ it.country()
+// .countryCodeV2()
+ it.firstName()
+// .formatted()
+// .formattedArea()
+// .id()
+ it.lastName()
+// .latitude()
+// .longitude()
+// .name()
+ it.phone()
+ it.province()
+// .provinceCode()
+ it.zip()
+ }
+ discountApplications({ arg -> arg.first(250) }) {
+ it.edges {
+ it.node {
+ it.onDiscountCodeApplication {
+ it.code()
+// .applicable()
+// .allocationMethod()
+// .targetSelection()
+// .targetType()
+ it.value {
+ it.onMoneyV2 {
+ it.amount()
+ }
+ it.onPricingPercentageValue {
+ it.percentage()
+ }
+ }
+ }
+ }
+ }
+ }
+ order {
+ it.cancelReason()
+ it.canceledAt()
+ it.currencyCode()
+// .currentSubtotalPrice()
+// .currentTotalDuties()
+// .currentTotalPrice()
+// .currentTotalTax()
+ it.customerLocale()
+ it.customerUrl()
+// .discountApplications()
+ it.edited()
+ it.email()
+ it.financialStatus()
+ it.fulfillmentStatus()
+// .id()
+// .lineItems()
+// .metafield()
+// .metafields()
+ it.name()
+ it.orderNumber()
+// .originalTotalDuties()
+// .originalTotalPrice()
+ it.phone()
+ it.processedAt()
+ it.shippingAddress {
+ it.address1()
+ it.address2()
+ it.city()
+ it.company()
+ it.country()
+ it.countryCodeV2()
+ it.firstName()
+ it.formatted()
+ it.formattedArea()
+// .id()
+ it.lastName()
+ it.latitude()
+ it.longitude()
+ it.name()
+ it.phone()
+ it.province()
+ it.provinceCode()
+ it.zip()
+ }
+// .shippingDiscountAllocations()
+ it.statusUrl()
+// .subtotalPriceV2()
+// .successfulFulfillments()
+// .totalPriceV2()
+// .totalRefundedV2()
+// .totalShippingPriceV2()
+// .totalTaxV2()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/CheckoutItem.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/CheckoutItem.kt
new file mode 100644
index 0000000000..5dc508df40
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/CheckoutItem.kt
@@ -0,0 +1,8 @@
+package com.tangem.tap.common.shop.shopify.data
+
+import com.shopify.graphql.support.ID
+
+data class CheckoutItem(
+ val id: ID,
+ val quantity: Int
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt
new file mode 100644
index 0000000000..572a7b68c7
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt
@@ -0,0 +1,50 @@
+package com.tangem.tap.common.shop.shopify.data
+
+import com.shopify.buy3.Storefront
+
+fun Storefront.CollectionConnectionQuery.collectionFieldsFragment() {
+ edges { collectionEdgeQuery ->
+ collectionEdgeQuery
+ .node { collectionQuery ->
+ collectionQuery
+ .title()
+ .products({ arg -> arg.first(250) }
+ ) { productConnectionQuery ->
+ productConnectionQuery
+ .edges { productEdgeQuery ->
+ productEdgeQuery
+ .node { productQuery ->
+ productQuery.title()
+ .productType()
+ .description()
+ .variants({ arg -> arg.first(10) }) { variantConnectionQuery ->
+ variantConnectionQuery.edges { variantQuery ->
+ variantQuery.node {
+ it.title()
+ it.sku()
+ it.currentlyNotInStock()
+ it.priceV2 {
+ it.amount()
+ it.currencyCode()
+ }
+ it.compareAtPriceV2 {
+ it.amount()
+ it.currencyCode()
+ }
+ it.compareAtPriceV2 {
+ it.amount()
+ }
+ it.product {
+ it.title()
+ .productType()
+ .description()
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
index 5712a5df13..bb104a4c80 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
@@ -153,7 +153,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse) {
tangemSdk.config.cardIdDisplayFormat = if (scanResponse.isTangemTwins()) {
- CardIdDisplayFormat.LastLunh(4)
+ CardIdDisplayFormat.LastLuhn(4)
} else {
CardIdDisplayFormat.Full
}
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 cfd628a093..55fc7f2154 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
@@ -1,24 +1,33 @@
package com.tangem.tap.domain
+import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.*
import com.tangem.common.services.Result
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
+import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.safeUpdate
+import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.configurable.config.ConfigManager
-import com.tangem.tap.domain.extensions.*
+import com.tangem.tap.domain.extensions.makePrimaryWalletManager
+import com.tangem.tap.domain.extensions.makeWalletManagersForApp
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.CardCurrencies
+import com.tangem.tap.features.demo.isDemoCard
+import com.tangem.tap.features.wallet.models.PendingTransactionType
+import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
+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
@@ -30,7 +39,7 @@ class TapWalletManager {
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
}
val walletManagerFactory: WalletManagerFactory
- by lazy { WalletManagerFactory(blockchainSdkConfig) }
+ by lazy { WalletManagerFactory(blockchainSdkConfig) }
suspend fun loadWalletData(walletManager: WalletManager) {
handleUpdateWalletResult(walletManager.safeUpdate(), walletManager)
@@ -51,8 +60,8 @@ class TapWalletManager {
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
val currencies = wallet.getTokens()
- .map { Currency.Token(it) }
- .plus(Currency.Blockchain(wallet.blockchain))
+ .map { Currency.Token(it) }
+ .plus(Currency.Blockchain(wallet.blockchain))
loadFiatRate(fiatCurrency, currencies)
}
@@ -89,7 +98,7 @@ class TapWalletManager {
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
configManager?.turnOff(ConfigManager.isTopUpEnabled)
} else if (blockchain == Blockchain.Bitcoin
- || data.walletData?.blockchain == Blockchain.Bitcoin.id) {
+ || data.walletData?.blockchain == Blockchain.Bitcoin.id) {
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
} else {
@@ -129,8 +138,7 @@ class TapWalletManager {
loadMultiWalletData(data, blockchain, null)
}
}
- val moonPayStatus = store.state.globalState.moonpayStatus
- store.dispatch(WalletAction.LoadWallet(moonPayStatus))
+ store.dispatch(WalletAction.LoadWallet())
store.dispatch(WalletAction.LoadFiatRate())
}
}
@@ -156,7 +164,7 @@ class TapWalletManager {
CardCurrencies(blockchains = blockchains, tokens = emptyList())
))
val walletManagers =
- walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList())
+ walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList())
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains.toList()))
}
@@ -165,12 +173,12 @@ class TapWalletManager {
} else {
val blockchains = savedCurrencies.blockchains.toList()
val walletManagers = if (
- primaryTokens.isNotEmpty() &&
- primaryWalletManager != null && primaryBlockchain != null
+ primaryTokens.isNotEmpty() &&
+ primaryWalletManager != null && primaryBlockchain != null
) {
val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain }
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchainsWithoutPrimary)
- .plus(primaryWalletManager)
+ .plus(primaryWalletManager)
} else {
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains)
}
@@ -192,8 +200,7 @@ class TapWalletManager {
return@withContext
}
- val moonPayStatus = store.state.globalState.moonpayStatus
- store.dispatch(WalletAction.LoadWallet(moonPayStatus))
+ store.dispatch(WalletAction.LoadWallet())
store.dispatch(WalletAction.LoadFiatRate())
}
}
@@ -207,6 +214,9 @@ class TapWalletManager {
data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
WalletAction.LoadData.Failure(TapError.UnknownBlockchain)
}
+ data.isDemoCard() -> {
+ return null
+ }
data.card.wallets.isEmpty() -> {
WalletAction.EmptyWallet
}
@@ -217,7 +227,10 @@ class TapWalletManager {
private suspend fun handleUpdateWalletResult(result: Result, walletManager: WalletManager) {
withContext(Dispatchers.Main) {
when (result) {
- is Result.Success -> store.dispatch(WalletAction.LoadWallet.Success(result.data))
+ is Result.Success -> {
+ checkForRentWarning(walletManager)
+ store.dispatch(WalletAction.LoadWallet.Success(result.data))
+ }
is Result.Failure -> {
val error = (result.error as? TapError) ?: TapError.UnknownError
when (error) {
@@ -226,11 +239,11 @@ class TapWalletManager {
}
is TapError.WalletManagerUpdate.NoAccountError -> {
store.dispatch(WalletAction.LoadWallet
- .NoAccount(walletManager.wallet, error.customMessage))
+ .NoAccount(walletManager.wallet, error.customMessage))
}
is TapError.WalletManagerUpdate.InternalError -> {
store.dispatch(WalletAction.LoadWallet
- .Failure(walletManager.wallet, error.customMessage))
+ .Failure(walletManager.wallet, error.customMessage))
}
else -> {
store.dispatchDebugErrorNotification(error)
@@ -241,6 +254,38 @@ class TapWalletManager {
}
}
+ private fun checkForRentWarning(walletManager: WalletManager) {
+ val rentProvider = walletManager as? RentProvider ?: return
+
+ scope.launch {
+ when (val result = rentProvider.minimalBalanceForRentExemption()) {
+ is com.tangem.blockchain.extensions.Result.Success -> {
+ fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean = balance < rentExempt
+
+ val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
+ val outgoingTxs = walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing)
+ val rentExempt = result.data
+ val show = if (outgoingTxs.isEmpty()) {
+ isNeedToShowWarning(balance, rentExempt)
+ } else {
+ val outgoingAmount = outgoingTxs.sumOf { it.amount ?: BigDecimal.ZERO }
+ val rest = balance.minus(outgoingAmount)
+ isNeedToShowWarning(rest, rentExempt)
+ }
+ if (!show) return@launch
+
+ val currency = walletManager.wallet.blockchain.currency
+ store.dispatchOnMain(WalletAction.SetWalletRent(
+ blockchain = walletManager.wallet.blockchain,
+ minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
+ rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
+ ))
+ }
+ is com.tangem.blockchain.extensions.Result.Failure -> {}
+ }
+ }
+ }
+
private suspend fun handleFiatRatesResult(results: List?>>) {
withContext(Dispatchers.Main) {
results.map {
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 ffc8df824c..c0f9c1e239 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
@@ -63,6 +63,8 @@ object TapWorkarounds {
"AB04" to Blockchain.Dogecoin,
"AB05" to Blockchain.BSC,
"AB06" to Blockchain.XRP,
+ "AB07" to Blockchain.Bitcoin,
+ "AB08" to Blockchain.Ethereum,
)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
index 412537befb..cb587f277b 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.domain.configurable.config
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.common.extensions.VoidCallback
+import com.tangem.tap.common.shop.shopify.ShopifyShop
import com.tangem.tap.domain.configurable.Loader
/**
@@ -10,13 +11,15 @@ import com.tangem.tap.domain.configurable.Loader
data class Config(
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
+ val onramperApiKey: String = "pk_test_Ix2aCF3ej_5tcDKkBR7MChIvf5Nb0oPORPQ3Oal5G8I0",
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
val appsFlyerDevKey: String = "",
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
val isSendingToPayIdEnabled: Boolean = true,
val isTopUpEnabled: Boolean = false,
@Deprecated("Not relevant since version 3.23")
- val isCreatingTwinCardsAllowed: Boolean = false
+ val isCreatingTwinCardsAllowed: Boolean = false,
+ val shopify: ShopifyShop? = null
)
class ConfigManager(
@@ -52,10 +55,10 @@ class ConfigManager(
fun resetToDefault(name: String) {
when (name) {
isSendingToPayIdEnabled -> config =
- config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
+ config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
isCreatingTwinCardsAllowed -> config =
- config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
+ config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
}
}
@@ -79,6 +82,7 @@ class ConfigManager(
config = config.copy(
coinMarketCapKey = values.coinMarketCapKey,
moonPayApiKey = values.moonPayApiKey,
+ onramperApiKey = values.onramperApiKey,
moonPayApiSecretKey = values.moonPayApiSecretKey,
blockchainSdkConfig = BlockchainSdkConfig(
blockchairApiKey = values.blockchairApiKey,
@@ -87,10 +91,12 @@ class ConfigManager(
infuraProjectId = values.infuraProjectId
),
appsFlyerDevKey = values.appsFlyerDevKey,
+ shopify = values.shopifyShop,
)
defaultConfig = defaultConfig.copy(
coinMarketCapKey = values.coinMarketCapKey,
moonPayApiKey = values.moonPayApiKey,
+ onramperApiKey = values.onramperApiKey,
moonPayApiSecretKey = values.moonPayApiSecretKey,
blockchainSdkConfig = BlockchainSdkConfig(
blockchairApiKey = values.blockchairApiKey,
@@ -99,6 +105,7 @@ class ConfigManager(
infuraProjectId = values.infuraProjectId
),
appsFlyerDevKey = values.appsFlyerDevKey,
+ shopify = values.shopifyShop,
)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
index edbbf6345c..41d1ea2022 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
@@ -1,5 +1,7 @@
package com.tangem.tap.domain.configurable.config
+import com.tangem.tap.common.shop.shopify.ShopifyShop
+
/**
[REDACTED_AUTHOR]
*/
@@ -14,12 +16,14 @@ class FeatureModel(
class ConfigValueModel(
val coinMarketCapKey: String,
val moonPayApiKey: String,
+ val onramperApiKey: String,
val moonPayApiSecretKey: String,
val blockchairApiKey: String?,
val blockchairAuthorizationToken: String?,
val blockcypherTokens: Set?,
val infuraProjectId: String?,
val appsFlyerDevKey: String,
+ val shopifyShop: ShopifyShop?
)
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
index 3cdf14dfea..163ac1264a 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
@@ -10,7 +10,7 @@ import com.tangem.wallet.R
[REDACTED_AUTHOR]
*/
class WarningMessagesManager(
- private val warningLoader: RemoteWarningLoader,
+ private val warningLoader: RemoteWarningLoader,
) {
private val warningsList: MutableList = mutableListOf()
@@ -33,15 +33,15 @@ class WarningMessagesManager(
fun getWarnings(location: WarningMessage.Location, forBlockchains: List = emptyList()): List {
return warningsList
- .filter { !it.isHidden && it.location.contains(location) }
- .filter {
- val list = it.blockchainList
- when {
- list == null -> true
- list.containsAny(forBlockchains) -> true
- else -> false
- }
+ .filter { !it.isHidden && it.location.contains(location) }
+ .filter {
+ val list = it.blockchainList
+ when {
+ list == null -> true
+ list.containsAny(forBlockchains) -> true
+ else -> false
}
+ }
}
fun hideWarning(warning: WarningMessage): Boolean {
@@ -49,7 +49,7 @@ class WarningMessagesManager(
return when {
foundWarning == null -> false
foundWarning.type == WarningMessage.Type.Temporary
- || foundWarning.type == WarningMessage.Type.AppRating -> {
+ || foundWarning.type == WarningMessage.Type.AppRating -> {
if (foundWarning.isHidden) {
false
} else {
@@ -80,32 +80,32 @@ class WarningMessagesManager(
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
+ "",
+ "",
+ 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
+ "",
+ "",
+ 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 signedHashesMultiWalletWarning(): WarningMessage = WarningMessage(
title = "",
- message = "",
+ message = "",
type = WarningMessage.Type.Temporary,
priority = WarningMessage.Priority.Info,
location = listOf(WarningMessage.Location.MainScreen),
@@ -117,15 +117,15 @@ class WarningMessagesManager(
)
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
+ "",
+ "",
+ 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 {
@@ -133,15 +133,15 @@ class WarningMessagesManager(
}
fun onlineVerificationFailed(): WarningMessage = WarningMessage(
- "",
- "",
- type = WarningMessage.Type.Permanent,
- priority = WarningMessage.Priority.Critical,
- listOf(WarningMessage.Location.MainScreen),
- null,
- R.string.warning_failed_to_verify_card_title,
- R.string.warning_failed_to_verify_card_message,
- WarningMessage.Origin.Local
+ "",
+ "",
+ type = WarningMessage.Type.Permanent,
+ priority = WarningMessage.Priority.Critical,
+ listOf(WarningMessage.Location.MainScreen),
+ null,
+ R.string.warning_failed_to_verify_card_title,
+ R.string.warning_failed_to_verify_card_message,
+ WarningMessage.Origin.Local
)
fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage = WarningMessage(
@@ -162,13 +162,25 @@ class WarningMessagesManager(
"",
type = WarningMessage.Type.TestCard,
priority = WarningMessage.Priority.Critical,
- listOf(WarningMessage.Location.MainScreen),
+ listOf(WarningMessage.Location.MainScreen, WarningMessage.Location.SendScreen),
null,
R.string.alert_title,
R.string.warning_testnet_card_message,
WarningMessage.Origin.Local
)
+ fun demoCardWarning(): WarningMessage = WarningMessage(
+ "",
+ "",
+ type = WarningMessage.Type.Permanent,
+ priority = WarningMessage.Priority.Critical,
+ listOf(WarningMessage.Location.MainScreen),
+ null,
+ R.string.alert_title,
+ R.string.alert_demo_message,
+ WarningMessage.Origin.Local
+ )
+
const val REMAINING_SIGNATURES_WARNING = 10
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt b/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt
similarity index 55%
rename from app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt
rename to app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt
index bd4b7af891..1d39e53861 100644
--- a/app/src/main/java/com/tangem/tap/domain/extensions/MoonpayStatus.kt
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt
@@ -2,13 +2,22 @@ package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.features.wallet.redux.Currency
-import com.tangem.tap.network.moonpay.MoonpayStatus
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeStatus
import com.tangem.tap.store
/**
[REDACTED_AUTHOR]
*/
-fun MoonpayStatus.buyIsAllowed(currency: Currency): Boolean {
+fun CurrencyExchangeManager.buyIsAllowed(currency: Currency): Boolean {
+ return this.status?.buyIsAllowed(currency) ?: false
+}
+
+fun CurrencyExchangeManager.sellIsAllowed(currency: Currency): Boolean {
+ return this.status?.sellIsAllowed(currency) ?: false
+}
+
+fun CurrencyExchangeStatus.buyIsAllowed(currency: Currency): Boolean {
if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false
if (!isBuyAllowed) return false
@@ -25,16 +34,17 @@ fun MoonpayStatus.buyIsAllowed(currency: Currency): Boolean {
}
}
-fun MoonpayStatus.sellIsAllowed(currency: Currency): Boolean {
+fun CurrencyExchangeStatus.sellIsAllowed(currency: Currency): Boolean {
if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false
if (!isSellAllowed) return false
return when (currency) {
is Currency.Blockchain -> {
- if (currency.blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC) {
- false
- } else {
- availableToSell.contains(currency.currencySymbol)
+ val blockchain = currency.blockchain
+ when {
+ blockchain.isTestnet() -> false
+ blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC -> false
+ else -> availableToSell.contains(currency.currencySymbol)
}
}
is Currency.Token -> false
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt
index e75da7ca72..1d46b341ec 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt
@@ -5,8 +5,10 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemSdkError
+import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
+import com.tangem.common.hdWallet.DerivationPath
import com.tangem.operations.CommandResponse
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
@@ -16,10 +18,12 @@ import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
+import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
+import com.tangem.tap.features.demo.DemoHelper
data class CreateProductWalletTaskResponse(
@@ -96,25 +100,27 @@ private class CreateWalletTangemNote : ProductCommandProcessor {
+ private lateinit var card: Card
private var primaryCard: PrimaryCard? = null
- private var createWalletResponse: CreateWalletResponse? = null
override fun proceed(
card: Card,
session: CardSession,
callback: (result: CompletionResult) -> Unit,
) {
+ this.card = card
val curves = card.getCurvesForNonCreatedWallets()
+
CreateWalletsTask(curves).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
- createWalletResponse = result.data.createWalletResponses[0]
+ val createWalletResponses = result.data.createWalletResponses
when {
card.settings.isBackupAllowed -> {
- linkPrimaryCard(session, callback)
+ linkPrimaryCard(createWalletResponses, session, callback)
}
card.settings.isHDWalletAllowed -> {
- deriveKeys(session, callback)
+ deriveKeys(createWalletResponses, session, callback)
}
else -> {
callback(
@@ -124,7 +130,6 @@ private class CreateWalletTangemWallet : ProductCommandProcessor callback(CompletionResult.Failure(result.error))
}
@@ -132,6 +137,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor,
session: CardSession,
callback: (result: CompletionResult) -> Unit,
) {
@@ -139,7 +145,20 @@ private class CreateWalletTangemWallet : ProductCommandProcessor {
primaryCard = result.data
- deriveKeys(session, callback)
+ when {
+ card.settings.isHDWalletAllowed -> {
+ deriveKeys(createWalletResponse, session, callback)
+ }
+ else -> {
+ callback(
+ CompletionResult.Success(
+ CreateProductWalletTaskResponse(
+ card = session.environment.card!!, primaryCard = primaryCard
+ )
+ )
+ )
+ }
+ }
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
@@ -149,28 +168,26 @@ private class CreateWalletTangemWallet : ProductCommandProcessor,
session: CardSession,
callback: (result: CompletionResult) -> Unit,
) {
- val derivationPaths = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
- .mapNotNull { it.derivationPath() }
- val response = createWalletResponse.guard {
+ val map = mutableMapOf>()
+ createWalletResponse.forEach { response ->
+ val blockchainsForCurve = getBlockchains(response.cardId).filter {
+ it.getSupportedCurves().contains(response.wallet.curve)
+ }
+ val derivationPaths = blockchainsForCurve.mapNotNull { it.derivationPath() }
+ if (derivationPaths.isNotEmpty()) {
+ map[response.wallet.publicKey.toMapKey()] = derivationPaths
+ }
+ }
+ if (map.isEmpty()) {
callback(CompletionResult.Failure(TangemSdkError.UnknownError()))
return
}
- if (derivationPaths.isNullOrEmpty()) {
- callback(
- CompletionResult.Success(
- CreateProductWalletTaskResponse(
- card = session.environment.card!!, primaryCard = primaryCard
- )
- )
- )
- return
- }
-
- DeriveMultipleWalletPublicKeysTask(mapOf(response.wallet.publicKey.toMapKey() to derivationPaths))
+ DeriveMultipleWalletPublicKeysTask(map)
.run(session) { result ->
when (result) {
is CompletionResult.Success -> {
@@ -188,4 +205,12 @@ private class CreateWalletTangemWallet : ProductCommandProcessor {
+ return when {
+ DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.demoBlockchains
+ card.isTestCard -> listOf(Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet)
+ else -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
index 271b9bf761..31931af76f 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
@@ -1,6 +1,5 @@
package com.tangem.tap.domain.tasks.product
-import com.tangem.*
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.common.CompletionResult
@@ -241,7 +240,7 @@ private class ScanWalletProcessor(
callback: (result: CompletionResult) -> Unit
) {
val derivations = collectDerivations(card)
- if (derivations.isEmpty()) {
+ if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
callback(
CompletionResult.Success(
ScanResponse(
diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt
index f56e6f7bc8..bb1d5c74bc 100644
--- a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt
@@ -13,9 +13,12 @@ import com.tangem.tap.common.extensions.appendIf
import com.tangem.tap.common.extensions.readJsonFileToString
import com.tangem.tap.domain.extensions.getCustomIconUrl
import com.tangem.tap.domain.extensions.setCustomIconUrl
+import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.network.createMoshi
+import timber.log.Timber
class CurrenciesRepository(val context: Application) {
+
private val moshi = createMoshi()
private val blockchainsAdapter: JsonAdapter> = moshi.adapter(
Types.newParameterizedType(List::class.java, Blockchain::class.java)
@@ -28,10 +31,13 @@ class CurrenciesRepository(val context: Application) {
)
fun loadCardCurrencies(cardId: String): CardCurrencies? {
- val blockchains = loadSavedBlockchains(cardId)
+ val blockchains = loadSavedBlockchains(cardId).toMutableSet()
+ if (DemoHelper.isDemoCardId(cardId)) {
+ blockchains.addAll(DemoHelper.config.demoBlockchains)
+ }
if (blockchains.isEmpty()) return null
- return CardCurrencies(loadSavedTokens(cardId), blockchains)
+ return CardCurrencies(loadSavedTokens(cardId), blockchains.toList())
}
fun saveCardCurrencies(cardId: String, currencies: CardCurrencies) {
@@ -117,16 +123,21 @@ class CurrenciesRepository(val context: Application) {
getSupportedTokens()
}
- val blockchainTokens = supportedTokens.map {
- fromJsonToTokensDao(loadTokensJson(it), it)
+ val blockchainTokens = supportedTokens.mapNotNull { blockchain ->
+ loadTokensJson(blockchain)?.let { fromJsonToTokensDao(it, blockchain) }
}
val tokens = blockchainTokens.flatten().map { it.toToken() }
return tokens
}
- private fun loadTokensJson(blockchain: Blockchain): String {
+ private fun loadTokensJson(blockchain: Blockchain): String? {
val fileName = getFileName(blockchain)
- return context.assets.readJsonFileToString(fileName)
+ return try {
+ context.assets.readJsonFileToString(fileName)
+ } catch (ex: Exception) {
+ Timber.e(ex, "Tokens with the file name %s not found", fileName)
+ null
+ }
}
private fun getFileName(blockchain: Blockchain): String {
@@ -143,12 +154,12 @@ class CurrenciesRepository(val context: Application) {
private fun getSupportedTokens(): List {
return listOf(
- Blockchain.Avalanche,
- Blockchain.Binance,
- Blockchain.BSC,
Blockchain.Ethereum,
+ Blockchain.BSC,
+ Blockchain.Binance,
Blockchain.Polygon,
- Blockchain.Solana,
+ Blockchain.Avalanche,
+ Blockchain.Fantom,
)
}
@@ -164,11 +175,11 @@ class CurrenciesRepository(val context: Application) {
return excludeUnsupportedBlockchains(blockchains)
}
- //TODO: move to the App settings
+ // Use this list to temporarily exclude a blockchain from the list of tokens.
private fun excludeUnsupportedBlockchains(blockchains: List): List {
return blockchains.toMutableList().apply {
removeAll(listOf(
- Blockchain.Fantom, Blockchain.FantomTestnet
+// Any blockchain
))
}
}
@@ -183,6 +194,13 @@ class CurrenciesRepository(val context: Application) {
}
}
+fun Blockchain.getTokensName(): String {
+ return when (this) {
+ Blockchain.Fantom -> "Fantom Opera"
+ else -> this.fullName
+ }
+}
+
@JsonClass(generateAdapter = true)
data class TokenDao(
val name: String,
diff --git a/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt b/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt
deleted file mode 100644
index 9dd6e91960..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.tangem.tap.domain.topup
-
-import com.tangem.Message
-import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
-import com.tangem.blockchain.common.Amount
-import com.tangem.blockchain.common.AmountType
-import com.tangem.blockchain.common.Token
-import com.tangem.blockchain.extensions.Result
-import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.domain.TangemSigner
-import com.tangem.tap.store
-import com.tangem.tap.tangemSdk
-import java.math.BigDecimal
-
-class TopUpManager {
- suspend fun topUpTestErc20Tokens(walletManager: EthereumWalletManager, token: Token) {
- walletManager.update()
-
- val amountToSend = Amount(walletManager.wallet.blockchain)
- val destinationAddress = token.contractAddress
-
- val feeResult = walletManager.getFee(amountToSend,
- destinationAddress) as? Result.Success ?: return
- val fee = feeResult.data[0]
-
- if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) {
- return
- }
-
- val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress)
-
- val signer = TangemSigner(
- tangemSdk = tangemSdk, Message()
- ) { signResponse ->
- store.dispatch(
- GlobalAction.UpdateWalletSignedHashes(
- walletSignedHashes = signResponse.totalSignedHashes,
- walletPublicKey = walletManager.wallet.publicKey.seedKey,
- remainingSignatures = signResponse.remainingSignatures
- )
- )
- }
- walletManager.send(transaction, signer)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt b/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt
deleted file mode 100644
index ec92f9f0fe..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/topup/TradeCryptoHelper.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-package com.tangem.tap.domain.topup
-
-import android.net.Uri
-import android.util.Base64
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.tap.common.redux.global.CryptoCurrencyName
-import javax.crypto.Mac
-import javax.crypto.spec.SecretKeySpec
-
-class TradeCryptoHelper {
-
- companion object {
-
- const val TRANSACTION_ID_PARAM = "transactionId"
- const val CURRENCY_CODE_PARAM = "baseCurrencyCode"
- const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
- const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
-
- private const val REDIRECT_URL_BUY = "tangem://success.tangem.com"
- private const val REDIRECT_URL_SELL_REQUEST = "tangem://sell-request.tangem.com"
-
- private const val BASE_URL_BUY =
- "https://buy.moonpay.io"
-// "https://buy-staging.moonpay.io" // TESTNET
- private const val BASE_URL_SELL =
- "https://sell.moonpay.com"
-// "https://sell-staging.moonpay.com" // TESTNET
-
- private const val API_KEY_PATH = "?apiKey="
- private const val CURRENCY_PATH = "¤cyCode="
- private const val WALLET_ADDRESS_PATH = "&walletAddress="
-
- private const val REFUND_WALLET_ADDRESS_PATH = "&refundWalletAddress="
- private const val BASE_CURRENCY_PATH = "&baseCurrencyCode="
-
- private const val REDIRECT_URL_PATH = "&redirectURL="
- private const val SIGNATURE_PATH = "&signature="
-
- private const val TRANSACTION_RECEIPT_PATH = "transaction_receipt?transactionId="
-
- private const val BASE_CURRENCY_USD = "USD"
-
- fun getUrl(
- action: Action,
- blockchain: Blockchain?,
- cryptoCurrencyName: CryptoCurrencyName,
- walletAddress: String,
- apiKey: String,
- secretKey: String,
- ): String {
- val originalQuery: String
- val baseUrl: String
- when (action) {
- Action.Buy -> {
- if (blockchain?.isTestnet() == true) {
- return blockchain.getTestnetTopUpUrl() ?: ""
- }
- baseUrl = BASE_URL_BUY
- originalQuery = API_KEY_PATH + apiKey.urlEncode() +
- CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
- BASE_CURRENCY_PATH + BASE_CURRENCY_USD.urlEncode() +
- WALLET_ADDRESS_PATH + walletAddress.urlEncode() +
- REDIRECT_URL_PATH + REDIRECT_URL_BUY.urlEncode()
- }
- Action.Sell -> {
- baseUrl = BASE_URL_SELL
- originalQuery = API_KEY_PATH + apiKey.urlEncode() +
- BASE_CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
- REFUND_WALLET_ADDRESS_PATH + walletAddress.urlEncode() +
- REDIRECT_URL_PATH + REDIRECT_URL_SELL_REQUEST.urlEncode()
- }
- }
- val signature = createSignature(originalQuery, secretKey)
- return baseUrl + originalQuery + SIGNATURE_PATH + signature.urlEncode()
-
- }
-
- fun getSellCryptoReceiptUrl(transactionId: String): String {
- return BASE_URL_SELL + TRANSACTION_RECEIPT_PATH + transactionId
- }
-
- private fun String.urlEncode(): String {
- return Uri.encode(this)
- }
-
- private fun createSignature(data: String, key: String): String {
- val sha256Hmac = Mac.getInstance("HmacSHA256")
- val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
- sha256Hmac.init(secretKey)
- val sha256encoded = sha256Hmac.doFinal(data.toByteArray())
- return Base64.encodeToString(sha256encoded, Base64.NO_WRAP)
- }
- }
-
- enum class Action { Buy, Sell }
-}
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt
index becb529e80..3a163149e5 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt
@@ -16,6 +16,7 @@ import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
*/
class TwinsCardWidget(
val leapfrogWidget: LeapfrogWidget,
+ private val deviceScaleFactor: Float = 1f,
val getTopOfAnchorViewForActivateState: () -> Float
) {
@@ -87,20 +88,20 @@ class TwinsCardWidget(
return when (cardNumber) {
TwinCardNumber.First -> {
TwinsCardProperties(
- xTranslation = -120f,
- yTranslation = -220f,
+ xTranslation = -120f * deviceScaleFactor,
+ yTranslation = -220f * deviceScaleFactor,
rotation = -3f,
elevation = 1f,
- scale = 1f,
+ scale = deviceScaleFactor,
)
}
TwinCardNumber.Second -> {
TwinsCardProperties(
- xTranslation = 170f,
- yTranslation = 170f,
+ xTranslation = 170f * deviceScaleFactor,
+ yTranslation = 170f * deviceScaleFactor,
rotation = -3f,
elevation = 0f,
- scale = 1f,
+ scale = deviceScaleFactor,
)
}
}
@@ -123,7 +124,7 @@ class TwinsCardWidget(
xTranslation = twinProperties.xTranslation,
yTranslation = twinProperties.yTranslation - topOfAnchorView,
rotation = 0f,
- scale = twinProperties.scale - 0.5f,
+ scale = (twinProperties.scale - 0.5f) * deviceScaleFactor,
)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
index e56b4d9ce6..1d0dc4a9e0 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
@@ -15,6 +15,7 @@ import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.Analytics
+import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
@@ -35,18 +36,11 @@ class WalletConnectSdkHelper {
id: Long,
type: WcTransactionType,
): WcTransactionData? {
-
val walletManager = getWalletManager(session) ?: return null
- try {
- walletManager.update()
- } catch (exception: Exception) {
- Timber.e(exception)
- return null
- }
+ walletManager.safeUpdate()
val wallet = walletManager.wallet
- val balance =
- wallet.amounts[AmountType.Coin]?.value ?: return null
+ val balance = wallet.amounts[AmountType.Coin]?.value ?: return null
val gas = transaction.gas?.hexToBigDecimal()
?: transaction.gasLimit?.hexToBigDecimal()
diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
new file mode 100644
index 0000000000..dad1a88a3b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
@@ -0,0 +1,203 @@
+package com.tangem.tap.features.demo
+
+import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager
+import com.tangem.blockchain.common.*
+import com.tangem.blockchain.extensions.Result
+import com.tangem.blockchain.extensions.SimpleResult
+import com.tangem.common.CompletionResult
+import com.tangem.tap.common.extensions.dispatchNotification
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.domain.tasks.product.ScanResponse
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
+import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+import com.tangem.wallet.BuildConfig
+import com.tangem.wallet.R
+import org.rekotlin.Action
+import java.math.BigDecimal
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface DemoMiddleware {
+ fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean
+}
+
+object DemoHelper {
+ val config = DemoConfig()
+
+ private val demoMiddlewares = listOf(
+ DemoOnboardingNoteMiddleware(),
+ )
+
+ private val disabledActionFeatures = listOf(
+ WalletConnectAction.StartWalletConnect::class.java,
+ WalletAction.TradeCryptoAction.Buy::class.java,
+ WalletAction.TradeCryptoAction.Sell::class.java,
+ BackupAction.StartBackup::class.java,
+ WalletAction.ExploreAddress::class.java
+ )
+
+ fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
+
+ fun isDemoCardId(cardId: String): Boolean = config.isDemoCardId(cardId)
+
+ fun tryHandle(appState: () -> AppState?, action: Action): Boolean {
+ val scanResponse = getScanResponse(appState) ?: return false
+ if (!scanResponse.isDemoCard()) return false
+
+ demoMiddlewares.forEach {
+ if (it.tryHandle(config, scanResponse, action)) return true
+ }
+
+ disabledActionFeatures.firstOrNull { it == action::class.java }?.let {
+ store.dispatchNotification(R.string.alert_demo_feature_disabled)
+ return true
+ }
+
+ return false
+ }
+
+ fun injectDemoBalance(walletManager: WalletManager?) {
+ val manager = walletManager ?: return
+
+ val blockchain = walletManager.wallet.blockchain
+ val amount = config.getBalance(blockchain)
+ manager.wallet.setAmount(amount)
+ }
+
+ private fun getScanResponse(appState: () -> AppState?): ScanResponse? {
+ val state = appState() ?: return null
+
+ return state.globalState.onboardingState.onboardingManager?.scanResponse
+ ?: state.globalState.scanResponse
+ }
+}
+
+class DemoConfig {
+
+ val demoBlockchains = listOf(
+ Blockchain.Bitcoin,
+ Blockchain.Ethereum,
+ Blockchain.Dogecoin,
+ Blockchain.Solana,
+ )
+
+ val demoCardIds: List by lazy {
+ val demoIds = (releaseDemoCardIds + testDemoCardIds).toMutableList()
+ if (BuildConfig.DEBUG) demoIds.addAll(debugTestDemoCardIds)
+
+ return@lazy demoIds.distinct()
+ }
+
+ private val walletBalances: Map = mapOf(
+ Blockchain.Bitcoin to Amount(0.028.toBigDecimal(), Blockchain.Bitcoin),
+ Blockchain.Ethereum to Amount(0.2311.toBigDecimal(), Blockchain.Ethereum),
+ Blockchain.Dogecoin to Amount(1450.025.toBigDecimal(), Blockchain.Dogecoin),
+ Blockchain.Solana to Amount(13.246.toBigDecimal(), Blockchain.Solana),
+ )
+
+ fun isDemoCardId(cardId: String): Boolean = demoCardIds.contains(cardId)
+
+ fun getBalance(blockchain: Blockchain): Amount = walletBalances[blockchain]?.copy()
+ ?: Amount(BigDecimal.ZERO, blockchain).copy()
+
+ private val releaseDemoCardIds = mutableListOf(
+// Tangem Wallet:
+ "AC01000000041100",
+ "AC01000000042462",
+ "AC01000000041647",
+ "AC01000000041621",
+ "AC01000000041217",
+ "AC01000000041225",
+ "AC01000000041209",
+ "AC01000000041092",
+ "AC01000000041472",
+ "AC01000000041662",
+ "AC01000000045754",
+ "AC01000000045960",
+// Tangem Note BTC:
+ "AB0100000046530",
+ "AB0100000046720",
+ "AB0100000046746",
+ "AB0100000046498",
+ "AB0100000046753",
+ "AB0100000049608",
+ "AB0100000046761",
+ "AB0100000049574",
+ "AB0100000046605",
+ "AB0100000046571",
+ "AB0100000046704",
+ "AB0100000046647",
+// Tangem Note Ethereum:
+ "AB02000000051000",
+ "AB02000000050986",
+ "AB02000000051026",
+ "AB02000000051042",
+ "AB02000000051091",
+ "AB02000000051083",
+ "AB02000000050960",
+ "AB02000000051034",
+ "AB02000000050911",
+ "AB02000000051133",
+ "AB02000000051158",
+ "AB02000000051059",
+ )
+
+ private val testDemoCardIds = listOf(
+ "FB20000000000186", // Note ETH
+ "FB10000000000196", // Note BTC
+ "FB30000000000176", // Wallet
+ //TODO: delete bellow ids before 3.28 release
+ "AB01000000045060", // Note BTC
+ "AB02000000045028", // Note ETH
+ "AC79000000000004", // Wallet 4.46
+ )
+
+ private val debugTestDemoCardIds = listOf(
+ )
+}
+
+class DemoTransactionSender(
+ private val walletManager: WalletManager,
+ private val sender: TransactionSender = walletManager as TransactionSender
+) : TransactionSender {
+
+ override suspend fun getFee(amount: Amount, destination: String): Result> {
+ val blockchain = walletManager.wallet.blockchain
+ return when (walletManager) {
+ is BitcoinWalletManager -> Result.Success(listOf(
+ Amount(0.0001.toBigDecimal(), blockchain),
+ Amount(0.0003.toBigDecimal(), blockchain),
+ Amount(0.00055.toBigDecimal(), blockchain),
+ ))
+ else -> sender.getFee(amount, destination)
+ }
+ }
+
+
+ override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
+ val dataToSign = randomString(32).toByteArray()
+ val signerResponse = signer.sign(dataToSign, walletManager.wallet.cardId, walletManager.wallet.publicKey)
+ return when (signerResponse) {
+ is CompletionResult.Success -> SimpleResult.Failure(Exception(ID))
+ is CompletionResult.Failure -> SimpleResult.fromTangemSdkError(signerResponse.error)
+ }
+ }
+
+ private fun randomInt(from: Int, to: Int): Int = kotlin.random.Random.nextInt(from, to)
+
+ private fun randomString(length: Int): String {
+ val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9')
+ return (1..length)
+ .map { randomInt(0, charPool.size) }
+ .map(charPool::get)
+ .joinToString("")
+ }
+
+ companion object {
+ val ID = DemoTransactionSender::class.java.simpleName
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt
new file mode 100644
index 0000000000..7537d54bdd
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt
@@ -0,0 +1,58 @@
+package com.tangem.tap.features.demo
+
+import com.tangem.common.extensions.guard
+import com.tangem.tap.common.extensions.withMainContext
+import com.tangem.tap.domain.extensions.makePrimaryWalletManager
+import com.tangem.tap.domain.tasks.product.ScanResponse
+import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
+import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import kotlinx.coroutines.launch
+import org.rekotlin.Action
+
+/**
+[REDACTED_AUTHOR]
+ */
+internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
+
+ override fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean {
+ val globalState = store.state.globalState
+ val noteState = store.state.onboardingNoteState
+
+ when (action) {
+ is OnboardingNoteAction.Balance.Update -> {
+ val walletManager = if (noteState.walletManager != null) {
+ noteState.walletManager
+ } else {
+ val wmFactory = globalState.tapWalletManager.walletManagerFactory
+ val walletManager = wmFactory.makePrimaryWalletManager(scanResponse).guard {
+ return false
+ }
+ store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
+ walletManager
+ }
+ val balanceAmount = config.getBalance(walletManager.wallet.blockchain)
+ val loadedBalance = noteState.walletBalance.copy(
+ value = balanceAmount.value!!,
+ currency = Currency.Blockchain(walletManager.wallet.blockchain),
+ state = ProgressState.Done,
+ error = null,
+ criticalError = null
+ )
+ walletManager.wallet.setAmount(balanceAmount)
+
+ scope.launch {
+ withMainContext {
+ store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance))
+ store.dispatch(OnboardingNoteAction.Balance.SetCriticalError(loadedBalance.criticalError))
+ store.dispatch(OnboardingNoteAction.Balance.SetNonCriticalError(loadedBalance.error))
+ }
+ }
+ return true
+ }
+ }
+ return false
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt b/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt
new file mode 100644
index 0000000000..f32c079efc
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt
@@ -0,0 +1,12 @@
+package com.tangem.tap.features.demo
+
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.tap.domain.tasks.product.ScanResponse
+
+/**
+[REDACTED_AUTHOR]
+ */
+fun ScanResponse.isDemoCard(): Boolean = DemoHelper.isDemoCardId(card.cardId)
+fun WalletManager.isDemoWallet(): Boolean = DemoHelper.isDemoCardId(wallet.cardId)
+fun Wallet.isDemoWallet(): Boolean = DemoHelper.isDemoCardId(cardId)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
index a8517bd1bc..6db0bf1a97 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
@@ -17,9 +17,11 @@ import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.walletconnect.BnbHelper
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
+import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
+import org.rekotlin.Action
import org.rekotlin.Middleware
class WalletConnectMiddleware {
@@ -28,138 +30,131 @@ class WalletConnectMiddleware {
val walletConnectMiddleware: Middleware = { dispatch, state ->
{ next ->
{ action ->
- when (action) {
+ handle(state, action)
+ next(action)
+ }
+ }
+ }
- is WalletConnectAction.RestoreSessions -> {
- walletConnectManager.restoreSessions()
- }
+ private fun handle(state: () -> AppState?, action: Action) {
+ if (DemoHelper.tryHandle(state, action)) return
- is WalletConnectAction.HandleDeepLink -> {
- if (!action.wcUri.isNullOrBlank()) {
- if (WalletConnectManager.isCorrectWcUri(action.wcUri)) {
- store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
- }
- }
- }
-
- is WalletConnectAction.StartWalletConnect -> {
- val uri = action.activity.getFromClipboard()?.toString()
- if (uri != null && WalletConnectManager.isCorrectWcUri(uri)) {
- store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
- } else {
- store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.QrScan))
- }
- }
-
- is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
- store.dispatchOnMain(
- GlobalAction.ShowDialog(
- WalletConnectDialog.ClipboardOrScanQr(
- action.wcUri
- )
- )
- )
- }
-
- is WalletConnectAction.OpeningSessionTimeout -> {
- store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
- }
-
- is WalletConnectAction.FailureEstablishingSession -> {
- if (action.session != null) {
- walletConnectManager.disconnect(action.session)
- }
- }
-
- is WalletConnectAction.UnsupportedCard -> {
- store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard))
- }
-
- is WalletConnectAction.OpenSession -> {
- walletConnectManager.connect(
- wcUri = action.wcUri,
- )
- }
-
- is WalletConnectAction.RefuseOpeningSession -> {
- store.dispatch(
- GlobalAction.ShowDialog(
- WalletConnectDialog.OpeningSessionRejected
- )
- )
- }
-
- is WalletConnectAction.ScanCard -> {
- scanCard(action.session, action.chainId)
- }
-
- is WalletConnectAction.ApproveSession -> {
- walletConnectManager.approve(action.session)
- }
-
- is WalletConnectAction.DisconnectSession -> {
- walletConnectManager.disconnect(action.session)
- }
-
- is WalletConnectAction.HandleTransactionRequest -> {
- walletConnectManager.handleTransactionRequest(
- transaction = action.transaction,
- session = action.session,
- id = action.id,
- type = action.type
- )
- }
- is WalletConnectAction.HandlePersonalSignRequest -> {
- walletConnectManager.handlePersonalSignRequest(
- message = action.message,
- session = action.session,
- id = action.id
- )
- }
- is WalletConnectAction.RejectRequest -> {
- walletConnectManager.rejectRequest(action.session, action.id)
- }
- is WalletConnectAction.SendTransaction -> {
- walletConnectManager.completeTransaction(action.session)
- }
- is WalletConnectAction.SignMessage -> {
- walletConnectManager.sendSignedMessage(action.session)
- }
- is WalletConnectAction.BinanceTransaction.Trade -> {
- val messageData = BnbHelper.createMessageData(action.order)
- store.dispatchOnMain(
- GlobalAction.ShowDialog(
- WalletConnectDialog.BnbTransactionDialog(
- data = messageData,
- session = action.sessionData.session,
- sessionId = action.id,
- cardId = action.sessionData.wallet.cardId,
- dAppName = action.sessionData.peerMeta.name
- )
- )
- )
- }
- is WalletConnectAction.BinanceTransaction.Transfer -> {
- val messageData = BnbHelper.createMessageData(action.order)
- store.dispatchOnMain(
- GlobalAction.ShowDialog(
- WalletConnectDialog.BnbTransactionDialog(
- data = messageData,
- session = action.sessionData.session,
- sessionId = action.id,
- cardId = action.sessionData.wallet.cardId,
- dAppName = action.sessionData.peerMeta.name
- )
- )
- )
- }
- is WalletConnectAction.BinanceTransaction.Sign -> {
- walletConnectManager.signBnb(
- action.id, action.data, action.sessionData
- )
+ when (action) {
+ is WalletConnectAction.RestoreSessions -> {
+ walletConnectManager.restoreSessions()
+ }
+ is WalletConnectAction.HandleDeepLink -> {
+ if (!action.wcUri.isNullOrBlank()) {
+ if (WalletConnectManager.isCorrectWcUri(action.wcUri)) {
+ store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
}
}
- next(action)
+ }
+ is WalletConnectAction.StartWalletConnect -> {
+ val uri = action.activity.getFromClipboard()?.toString()
+ if (uri != null && WalletConnectManager.isCorrectWcUri(uri)) {
+ store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
+ } else {
+ store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.QrScan))
+ }
+ }
+ is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
+ store.dispatchOnMain(
+ GlobalAction.ShowDialog(
+ WalletConnectDialog.ClipboardOrScanQr(
+ action.wcUri
+ )
+ )
+ )
+ }
+ is WalletConnectAction.OpeningSessionTimeout -> {
+ store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
+ }
+ is WalletConnectAction.FailureEstablishingSession -> {
+ if (action.session != null) {
+ walletConnectManager.disconnect(action.session)
+ }
+ }
+ is WalletConnectAction.UnsupportedCard -> {
+ store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard))
+ }
+ is WalletConnectAction.OpenSession -> {
+ walletConnectManager.connect(
+ wcUri = action.wcUri,
+ )
+ }
+ is WalletConnectAction.RefuseOpeningSession -> {
+ store.dispatch(
+ GlobalAction.ShowDialog(
+ WalletConnectDialog.OpeningSessionRejected
+ )
+ )
+ }
+ is WalletConnectAction.ScanCard -> {
+ scanCard(action.session, action.chainId)
+ }
+ is WalletConnectAction.ApproveSession -> {
+ walletConnectManager.approve(action.session)
+ }
+ is WalletConnectAction.DisconnectSession -> {
+ walletConnectManager.disconnect(action.session)
+ }
+ is WalletConnectAction.HandleTransactionRequest -> {
+ walletConnectManager.handleTransactionRequest(
+ transaction = action.transaction,
+ session = action.session,
+ id = action.id,
+ type = action.type
+ )
+ }
+ is WalletConnectAction.HandlePersonalSignRequest -> {
+ walletConnectManager.handlePersonalSignRequest(
+ message = action.message,
+ session = action.session,
+ id = action.id
+ )
+ }
+ is WalletConnectAction.RejectRequest -> {
+ walletConnectManager.rejectRequest(action.session, action.id)
+ }
+ is WalletConnectAction.SendTransaction -> {
+ walletConnectManager.completeTransaction(action.session)
+ }
+ is WalletConnectAction.SignMessage -> {
+ walletConnectManager.sendSignedMessage(action.session)
+ }
+ is WalletConnectAction.BinanceTransaction.Trade -> {
+ val messageData = BnbHelper.createMessageData(action.order)
+ store.dispatchOnMain(
+ GlobalAction.ShowDialog(
+ WalletConnectDialog.BnbTransactionDialog(
+ data = messageData,
+ session = action.sessionData.session,
+ sessionId = action.id,
+ cardId = action.sessionData.wallet.cardId,
+ dAppName = action.sessionData.peerMeta.name
+ )
+ )
+ )
+ }
+ is WalletConnectAction.BinanceTransaction.Transfer -> {
+ val messageData = BnbHelper.createMessageData(action.order)
+ store.dispatchOnMain(
+ GlobalAction.ShowDialog(
+ WalletConnectDialog.BnbTransactionDialog(
+ data = messageData,
+ session = action.sessionData.session,
+ sessionId = action.id,
+ cardId = action.sessionData.wallet.cardId,
+ dAppName = action.sessionData.peerMeta.name
+ )
+ )
+ )
+ }
+ is WalletConnectAction.BinanceTransaction.Sign -> {
+ walletConnectManager.signBnb(
+ action.id, action.data, action.sessionData
+ )
}
}
}
@@ -190,9 +185,9 @@ class WalletConnectMiddleware {
}
val walletManager = getWalletManager(scanResponse, blockchain).guard {
- store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
- return
- }
+ store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
+ return
+ }
val wallet = walletManager.wallet
val derivedKey =
@@ -227,7 +222,7 @@ class WalletConnectMiddleware {
): WalletManager? {
val card = scanResponse.card
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
- val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
+ val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
Blockchain.EthereumTestnet
} else {
blockchain
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt
index 8e6dc116d8..acbe36e4ae 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt
@@ -5,6 +5,7 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
+import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.getDrawable
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.details.redux.ConfirmScreenState
@@ -12,12 +13,14 @@ import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_details_confirm.*
+import com.tangem.wallet.databinding.FragmentDetailsConfirmBinding
import org.rekotlin.StoreSubscriber
class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm),
StoreSubscriber {
+ private val binding: FragmentDetailsConfirmBinding by viewBinding(FragmentDetailsConfirmBinding::bind)
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
@@ -47,38 +50,39 @@ class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- toolbar.setNavigationOnClickListener {
+ binding.toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo())
}
}
override fun newState(state: DetailsState) {
- if (activity == null) return
+ if (activity == null || view == null) return
+ setState(state)
+ }
+ private fun setState(state: DetailsState) = with(binding) {
when (state.confirmScreenState) {
ConfirmScreenState.EraseWallet -> {
toolbar.title = getString(R.string.details_row_title_reset_factory_settings)
- tv_warning_description.text = getString(R.string.details_row_title_reset_factory_settings_warning)
- btn_confirm.text = getString(R.string.details_row_title_reset_factory_settings)
- btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
- null, null, getDrawable(R.drawable.ic_send), null
+ tvWarningDescription.text = getString(R.string.details_row_title_reset_factory_settings_warning)
+ btnConfirm.text = getString(R.string.details_row_title_reset_factory_settings)
+ btnConfirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ null, null, getDrawable(R.drawable.ic_send), null
)
- btn_confirm.setOnClickListener { store.dispatch(DetailsAction.ResetToFactory.Confirm) }
+ btnConfirm.setOnClickListener { store.dispatch(DetailsAction.ResetToFactory.Confirm) }
}
ConfirmScreenState.LongTap, ConfirmScreenState.AccessCode,
ConfirmScreenState.PassCode -> {
- toolbar.title = getString(R.string.details_row_title_manage_security)
- tv_warning_description.text = getString(R.string.details_security_management_warning)
- btn_confirm.text = getString(R.string.common_save_changes)
- btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
- null, null, getDrawable(R.drawable.ic_save), null
+ toolbar.title = getString(R.string.details_manage_security_title)
+ tvWarningDescription.text = getString(R.string.details_security_management_warning)
+ btnConfirm.text = getString(R.string.common_save_changes)
+ btnConfirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ null, null, getDrawable(R.drawable.ic_save), null
)
- btn_confirm.setOnClickListener { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) }
+ btnConfirm.setOnClickListener { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) }
}
}
-
-
}
}
\ No newline at end of file
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 acb76789ba..5df5d5969b 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
@@ -6,6 +6,7 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
+import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
@@ -18,13 +19,13 @@ import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.feedback.FeedbackEmail
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_details.*
-import kotlinx.android.synthetic.main.fragment_wallet.toolbar
+import com.tangem.wallet.databinding.FragmentDetailsBinding
import org.rekotlin.StoreSubscriber
class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber {
private var currencySelectionDialog = CurrencySelectionDialog()
+ private val binding: FragmentDetailsBinding by viewBinding(FragmentDetailsBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -55,15 +56,18 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber R.string.details_manage_security_access_code
null -> null
}
- currentSecurity?.let { tv_security.text = getString(it) }
+ currentSecurity?.let { tvSecurity.text = getString(it) }
if (state.appCurrencyState.showAppCurrencyDialog &&
!state.appCurrencyState.fiatCurrencies.isNullOrEmpty()) {
@@ -147,7 +151,6 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber {
+ private val binding: FragmentDetailsSecurityBinding by viewBinding(
+ FragmentDetailsSecurityBinding::bind
+ )
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
@@ -49,14 +53,14 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- toolbar.setNavigationOnClickListener {
+ binding.toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo())
}
setOnClickListeners()
}
private fun setOnClickListeners() {
- btn_save_changes.setOnClickListener {
+ binding.btnSaveChanges.setOnClickListener {
store.state.detailsState.securityScreenState?.selectedOption?.let {
store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection(it))
}
@@ -64,7 +68,8 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
}
override fun newState(state: DetailsState) {
- if (activity == null) return
+ if (activity == null || view == null) return
+
selectSecurityOption(state.securityScreenState?.selectedOption)
for (option in SecurityOption.values()) {
setupOption(
@@ -73,18 +78,18 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
?: EnumSet.noneOf(SecurityOption::class.java)
)
}
- tv_access_code_unavailable_disclaimer.show(
+ binding.tvAccessCodeUnavailableDisclaimer.show(
state.scanResponse?.card?.backupStatus == Card.BackupStatus.NoBackup
)
}
- private fun selectSecurityOption(securityOption: SecurityOption?) {
- radiobutton_long_tap.isChecked =
- securityOption == SecurityOption.LongTap && radiobutton_long_tap.isEnabled
- radiobutton_passcode.isChecked =
- securityOption == SecurityOption.PassCode && radiobutton_passcode.isEnabled
- radiobutton_access_code.isChecked =
- securityOption == SecurityOption.AccessCode && radiobutton_access_code.isEnabled
+ private fun selectSecurityOption(securityOption: SecurityOption?) = with(binding) {
+ radiobuttonLongTap.isChecked =
+ securityOption == SecurityOption.LongTap && radiobuttonLongTap.isEnabled
+ radiobuttonPasscode.isChecked =
+ securityOption == SecurityOption.PassCode && radiobuttonPasscode.isEnabled
+ radiobuttonAccessCode.isChecked =
+ securityOption == SecurityOption.AccessCode && radiobuttonAccessCode.isEnabled
}
private fun setupOption(option: SecurityOption, allowedOptions: EnumSet) {
@@ -95,46 +100,46 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
}
}
- private fun enableLongTap(enable: Boolean) {
- group_long_tap.show(enable)
+ private fun enableLongTap(enable: Boolean) = with(binding) {
+ groupLongTap.show(enable)
val alpha = if (enable) 1f else 0.5f
- tv_long_tap_description.alpha = alpha
- tv_long_tap_title.alpha = alpha
- radiobutton_long_tap.alpha = alpha
- radiobutton_long_tap.isEnabled = enable
- v_long_tap.isClickable = enable
+ tvLongTapDescription.alpha = alpha
+ tvLongTapTitle.alpha = alpha
+ radiobuttonLongTap.alpha = alpha
+ radiobuttonLongTap.isEnabled = enable
+ vLongTap.isClickable = enable
if (enable) {
- v_long_tap.setOnClickListener {
+ vLongTap.setOnClickListener {
store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.LongTap))
}
}
}
- private fun enablePasscode(enable: Boolean) {
- group_passcode.show(enable)
+ private fun enablePasscode(enable: Boolean) = with(binding) {
+ groupPasscode.show(enable)
val alpha = if (enable) 1f else 0.5f
- tv_passcode_description.alpha = alpha
- tv_passcode_title.alpha = alpha
- radiobutton_passcode.alpha = alpha
- radiobutton_passcode.isEnabled = enable
- v_passcode.isClickable = enable
+ tvPasscodeDescription.alpha = alpha
+ tvPasscodeTitle.alpha = alpha
+ radiobuttonPasscode.alpha = alpha
+ radiobuttonPasscode.isEnabled = enable
+ vPasscode.isClickable = enable
if (enable) {
- v_passcode.setOnClickListener {
+ vPasscode.setOnClickListener {
store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.PassCode))
}
}
}
- private fun enableAccessCode(enable: Boolean) {
- group_access_code.show(enable)
+ private fun enableAccessCode(enable: Boolean) = with(binding) {
+ groupAccessCode.show(enable)
val alpha = if (enable) 1f else 0.5f
- tv_access_code_description.alpha = alpha
- tv_access_code_title.alpha = alpha
- radiobutton_access_code.alpha = alpha
- radiobutton_access_code.isEnabled = enable
- v_access_code.isClickable = enable
+ tvAccessCodeDescription.alpha = alpha
+ tvAccessCodeTitle.alpha = alpha
+ radiobuttonAccessCode.alpha = alpha
+ radiobuttonAccessCode.isEnabled = enable
+ vAccessCode.isClickable = enable
if (enable) {
- v_access_code.setOnClickListener {
+ vAccessCode.setOnClickListener {
store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.AccessCode))
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/SessionsAdapter.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/SessionsAdapter.kt
index c1028c874d..95c28cf26a 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/SessionsAdapter.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/SessionsAdapter.kt
@@ -3,14 +3,15 @@ package com.tangem.tap.features.details.ui.walletconnect
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.chip.Chip
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.item_wallet_connect_session.view.*
class WalletConnectSessionsAdapter
: ListAdapter(
@@ -41,15 +42,14 @@ class WalletConnectSessionsAdapter
RecyclerView.ViewHolder(view) {
fun bind(session: WalletConnectSession) {
- view.tv_card_id.text = view.context.getString(
+ view.findViewById(R.id.tv_card_id).text = view.context.getString(
R.string.wallet_connect_card_number, session.wallet.cardId
)
- view.tv_d_app_name.text = session.peerMeta.name
+ view.findViewById(R.id.tv_d_app_name).text = session.peerMeta.name
- view.btn_disconnect.setOnClickListener {
+ view.findViewById(R.id.btn_disconnect).setOnClickListener {
store.dispatch(WalletConnectAction.DisconnectSession(session.session))
}
-
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectSessionsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectSessionsFragment.kt
index 1a55a976c7..b019dc95e7 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectSessionsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectSessionsFragment.kt
@@ -1,19 +1,20 @@
package com.tangem.tap.features.details.ui.walletconnect
-import android.app.Dialog
import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
+import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_wallet_connect_sessions.*
+import com.tangem.wallet.databinding.FragmentWalletConnectSessionsBinding
import org.rekotlin.StoreSubscriber
@@ -21,7 +22,9 @@ class WalletConnectSessionsFragment : Fragment(R.layout.fragment_wallet_connect_
StoreSubscriber {
private lateinit var walletConnectSessionsAdapter: WalletConnectSessionsAdapter
- private var dialog: Dialog? = null
+ private val binding: FragmentWalletConnectSessionsBinding by viewBinding(
+ FragmentWalletConnectSessionsBinding::bind
+ )
override fun onCreate(savedInstanceState: Bundle?) {
@@ -53,7 +56,7 @@ class WalletConnectSessionsFragment : Fragment(R.layout.fragment_wallet_connect_
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- toolbar.setNavigationOnClickListener {
+ binding.toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo())
}
setOnClickListeners()
@@ -61,27 +64,31 @@ class WalletConnectSessionsFragment : Fragment(R.layout.fragment_wallet_connect_
}
private fun setOnClickListeners() {
- fab_open_session.setOnClickListener {
+ binding.fabOpenSession.setOnClickListener {
store.dispatch(WalletConnectAction.StartWalletConnect(activity = requireActivity()))
}
}
- private fun setupTransactionsRecyclerView() {
+ private fun setupTransactionsRecyclerView() = with(binding) {
walletConnectSessionsAdapter = WalletConnectSessionsAdapter()
- rv_wallet_connect_sessions.layoutManager = LinearLayoutManager(requireContext())
- rv_wallet_connect_sessions.adapter = walletConnectSessionsAdapter
+ rvWalletConnectSessions.layoutManager = LinearLayoutManager(requireContext())
+ rvWalletConnectSessions.adapter = walletConnectSessionsAdapter
walletConnectSessionsAdapter.submitList(store.state.walletConnectState.sessions)
}
override fun newState(state: WalletConnectState) {
- if (activity == null) return
+ if (activity == null || view == null) return
- fab_open_session.show(!state.loading)
- pb_wallet_connect.show(state.loading)
+ binding.fabOpenSession.show(!state.loading)
+ binding.pbWalletConnect.show(state.loading)
- walletConnectSessionsAdapter.submitList(state.sessions)
- rv_wallet_connect_sessions.show(state.sessions.isNotEmpty())
- tv_no_sessions.show(state.sessions.isEmpty())
- tv_no_sessions_title.show(state.sessions.isEmpty())
+ showUpdatedList(state.sessions)
+ }
+
+ private fun showUpdatedList(sessions: List) = with(binding) {
+ walletConnectSessionsAdapter.submitList(sessions)
+ rvWalletConnectSessions.show(sessions.isNotEmpty())
+ tvNoSessions.show(sessions.isEmpty())
+ tvNoSessionsTitle.show(sessions.isEmpty())
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt
index 82f131a3a2..4538be7224 100644
--- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt
@@ -5,18 +5,21 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
+import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_disclaimer.*
+import com.tangem.wallet.databinding.FragmentDisclaimerBinding
import org.rekotlin.StoreSubscriber
class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer),
StoreSubscriber {
+ private val binding: FragmentDisclaimerBinding by viewBinding(FragmentDisclaimerBinding::bind)
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
@@ -45,22 +48,22 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- toolbar.setNavigationOnClickListener {
+ binding.toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo())
}
setOnClickListeners()
}
private fun setOnClickListeners() {
- btn_accept.setOnClickListener { store.dispatch(DisclaimerAction.AcceptDisclaimer) }
+ binding.btnAccept.setOnClickListener { store.dispatch(DisclaimerAction.AcceptDisclaimer) }
}
override fun newState(state: DisclaimerState) {
- if (activity == null) return
+ if (activity == null || view == null) return
if (state.accepted) {
- half_transparent_overlay.hide()
- btn_accept.hide()
+ binding.halfTransparentOverlay.hide()
+ binding.btnAccept.hide()
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt
index 6da60e3904..34f6cc4807 100644
--- a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt
+++ b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt
@@ -11,6 +11,9 @@ import com.tangem.common.card.Card
import com.tangem.tap.common.extensions.sendEmail
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.domain.TapWorkarounds
+import com.tangem.tap.features.feedback.EmailData.Companion.appendBlankLine
+import com.tangem.tap.features.feedback.EmailData.Companion.appendDelimiter
+import com.tangem.wallet.R
import timber.log.Timber
import java.io.File
import java.io.FileWriter
@@ -40,7 +43,8 @@ class FeedbackManager(
val fileLog = if (emailData is ScanFailsEmail) createLogFile() else null
activity.sendEmail(
email = getSupportEmail(),
- subject = emailData.subject, message = emailData.joinTogether(infoHolder, emailData !is ScanFailsEmail),
+ subject = activity.getString(emailData.subjectResId),
+ message = emailData.joinTogether(activity, infoHolder),
file = fileLog,
onFail = onFail
)
@@ -142,7 +146,7 @@ class AdditionalEmailInfo {
cardFirmwareVersion = card.firmwareVersion.stringValue
cardIssuer = card.issuer.name
signedHashesCount = card.wallets
- .joinToString(";") { "${it.curve?.curve} - ${it.totalSignedHashes}" }
+ .joinToString(";") { "${it.curve?.curve} - ${it.totalSignedHashes}" }
}
fun setWalletsInfo(walletManagers: List) {
@@ -201,43 +205,40 @@ class AdditionalEmailInfo {
}
interface EmailData {
- val subject: String
- val mainMessage: String
+ val subjectResId: Int
+ val mainMessageResId: Int
+
+ fun getDataCollectionMessageResId(): Int = R.string.feedback_data_collection_message
fun prepare(infoHolder: AdditionalEmailInfo) {}
- fun appendDelimiter(builder: StringBuilder) {
- builder.append("----------\n")
- }
-
- fun appendBlankLine(builder: StringBuilder) {
- builder.append("\n")
- }
-
fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String
- fun joinTogether(infoHolder: AdditionalEmailInfo, allowToErasePhoneInfo: Boolean): String {
- val allowToErasePhoneInfoDisclaimer = if (allowToErasePhoneInfo) {
- "Following information is optional. You can erase it if you don’t want to share it.\n\n"
- } else {
- ""
- }
- return "$mainMessage\n\n\n\n" +
- allowToErasePhoneInfoDisclaimer +
- createOptionalMessage(infoHolder)
+ fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String {
+ return StringBuilder().apply {
+ append(context.getString(mainMessageResId))
+ append("\n\n\n\n")
+ append(context.getString(getDataCollectionMessageResId()))
+ append(createOptionalMessage(infoHolder))
+ }.toString()
+ }
+
+ companion object {
+ internal fun StringBuilder.appendDelimiter() = append("----------\n")
+ internal fun StringBuilder.appendBlankLine() = append("\n")
}
}
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 val subjectResId: Int = R.string.feedback_subject_rate_negative
+ override val mainMessageResId: Int = R.string.feedback_preface_rate_negative
override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
val walletInfo = infoHolder.walletsInfo[0]
return StringBuilder().apply {
appendKeyValue("Card ID", infoHolder.cardId)
appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
- appendBlankLine(this)
+ appendBlankLine()
appendKeyValue("Phone model", infoHolder.phoneModel)
appendKeyValue("OS version", infoHolder.osVersion)
appendKeyValue("App version", infoHolder.appVersion)
@@ -246,11 +247,21 @@ class RateCanBeBetterEmail : EmailData {
}
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 val subjectResId: Int = R.string.feedback_subject_scan_failed
+ override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
+
+ override fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String {
+ return StringBuilder().apply {
+ append(context.getString(mainMessageResId))
+ append("\n\n\n\n")
+ append(createOptionalMessage(infoHolder))
+ }.toString()
+ }
+
override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
return StringBuilder().apply {
- appendBlankLine(this)
+ appendBlankLine()
appendKeyValue("Phone model", infoHolder.phoneModel)
appendKeyValue("OS version", infoHolder.osVersion)
appendKeyValue("App version", infoHolder.appVersion)
@@ -259,8 +270,8 @@ class ScanFailsEmail : EmailData {
}
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 val subjectResId: Int = R.string.feedback_subject_tx_failed
+ override val mainMessageResId: Int = R.string.feedback_preface_tx_failed
override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo()
@@ -268,17 +279,17 @@ class SendTransactionFailedEmail(private val error: String) : EmailData {
appendKeyValue("Card ID", infoHolder.cardId)
appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
- appendDelimiter(this)
+ appendDelimiter()
appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
appendKeyValue("Host", walletInfo.host)
appendKeyValue("Token", infoHolder.token)
appendKeyValue("Error", error)
- appendDelimiter(this)
+ appendDelimiter()
appendKeyValue("Source address", walletInfo.address)
appendKeyValue("Destination address", infoHolder.destinationAddress)
appendKeyValue("Amount", infoHolder.amount)
appendKeyValue("Fee", infoHolder.fee)
- appendBlankLine(this)
+ appendBlankLine()
appendKeyValue("Phone model", infoHolder.phoneModel)
appendKeyValue("OS version", infoHolder.osVersion)
appendKeyValue("App version", infoHolder.appVersion)
@@ -288,16 +299,16 @@ class SendTransactionFailedEmail(private val error: String) : EmailData {
}
class FeedbackEmail : EmailData {
- override val subject: String
+ override val subjectResId: Int
get() = if (isS2CCard) s2cSubject else tangemSubject
- override val mainMessage: String
+ override val mainMessageResId: Int
get() = if (isS2CCard) s2cMainMessage else tangemMainMessage
- private val tangemSubject = "Tangem feedback"
- private val tangemMainMessage = "Hi support team,"
+ private val tangemSubject: Int = R.string.feedback_subject_support_tangem
+ private val tangemMainMessage: Int = R.string.feedback_preface_support
- private val s2cSubject = "Feedback"
- private val s2cMainMessage = "Hi support team,"
+ private val s2cSubject: Int = R.string.feedback_subject_support
+ private val s2cMainMessage: Int = R.string.feedback_preface_support
private var isS2CCard = false
@@ -312,26 +323,26 @@ class FeedbackEmail : EmailData {
builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
infoHolder.walletsInfo.forEach {
- appendDelimiter(builder)
+ builder.appendDelimiter()
builder.appendKeyValue("Blockchain", it.blockchain.fullName)
builder.appendKeyValue("Host", it.host)
builder.appendKeyValue("Wallet address", it.address)
builder.appendKeyValue("Explorer link", it.explorerLink)
}
- appendBlankLine(builder)
+ builder.appendBlankLine()
infoHolder.tokens.forEach { tokens ->
- appendDelimiter(builder)
+ builder.appendDelimiter()
builder.appendKeyValue("Blockchain", tokens.key.fullName)
builder.appendKeyValue("Tokens", tokens.value.map { "${it.name} - ${it.symbol}" }.toString())
}
- appendDelimiter(builder)
- appendBlankLine(builder)
-
+ builder.appendDelimiter()
+ builder.appendBlankLine()
// appendKeyValue("Outputs count", infoHolder.outputsCount)
builder.appendKeyValue("Phone model", infoHolder.phoneModel)
builder.appendKeyValue("OS version", infoHolder.osVersion)
builder.appendKeyValue("App version", infoHolder.appVersion)
+
return builder.toString()
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
index fae5ee9060..b577781a18 100644
--- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
@@ -2,35 +2,25 @@ package com.tangem.tap.features.home
import android.os.Bundle
import android.view.View
-import androidx.transition.TransitionInflater
-import com.tangem.tap.common.extensions.hide
-import com.tangem.tap.common.redux.navigation.FragmentShareTransition
-import com.tangem.tap.common.redux.navigation.ShareElement
-import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget
-import com.tangem.tap.common.toggleWidget.ViewStateWidget
-import com.tangem.tap.common.transitions.FrontCardEnterTransition
-import com.tangem.tap.common.transitions.FrontCardExitTransition
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import com.google.accompanist.appcompattheme.AppCompatTheme
+import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
-import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_onboarding_main.*
-import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
-import kotlinx.android.synthetic.main.layout_onboarding_home_top.*
-import kotlinx.android.synthetic.main.view_bg_home.*
+import org.rekotlin.StoreSubscriber
-class HomeFragment : BaseOnboardingFragment() {
+class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber {
- private lateinit var btnScanCard: ViewStateWidget
-
- override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_home_top
+ var homeState: MutableState = mutableStateOf(store.state.homeState)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val inflater = TransitionInflater.from(requireContext())
- exitTransition = inflater.inflateTransition(R.transition.fade)
store.dispatch(HomeAction.Init)
}
@@ -39,50 +29,36 @@ class HomeFragment : BaseOnboardingFragment() {
store.dispatch(BackupAction.CheckForUnfinishedBackup)
- toolbar.hide()
- val shareTransition = FragmentShareTransition(
- listOf(
- ShareElement(imv_front_card, ShareElement.imvFrontCard),
- ShareElement(imv_back_card, ShareElement.imvBackCard),
- ShareElement(bg_circle_large),
- ShareElement(bg_circle_medium),
- ShareElement(bg_circle_min),
- ),
- FrontCardEnterTransition(),
- FrontCardExitTransition()
- )
-
- store.dispatch(HomeAction.SetFragmentShareTransition(shareTransition))
-
- tv_header.setText(R.string.home_welcome_header)
- tv_body.setText(R.string.home_welcome_body)
-
- btn_main_action.setText(R.string.home_button_scan)
- btn_main_action.setOnClickListener { store.dispatch(HomeAction.ReadCard) }
- btn_alternative_action.setText(R.string.home_button_get_new_card)
- btn_alternative_action.setOnClickListener { store.dispatch(HomeAction.GoToShop) }
-
- btnScanCard = IndeterminateProgressButtonWidget(btn_main_action, progress)
+ getView()?.findViewById(R.id.cv_stories)?.setContent {
+ AppCompatTheme {
+ StoriesScreen(
+ homeState,
+ onScanButtonClick = { store.dispatch(HomeAction.ReadCard) },
+ onShopButtonClick = { store.dispatch(HomeAction.GoToShop) }
+ )
+ }
+ }
}
- override fun subscribeToStore() {
+ override fun onStart() {
+ super.onStart()
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.homeState == newState.homeState
}.select { it.homeState }
}
- storeSubscribersList.add(this)
}
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+
override fun newState(state: HomeState) {
- if (activity == null) return
+ if (activity == null || view == null) return
- btnScanCard.changeState(state.btnScanState.progressState)
- btnScanCard.mainView.isEnabled = state.btnScanState.enabled
+ homeState.value = state
}
- override fun onDestroyView() {
- store.dispatch(HomeAction.SetFragmentShareTransition(null))
- super.onDestroyView()
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/AutoSizeText.kt b/app/src/main/java/com/tangem/tap/features/home/compose/AutoSizeText.kt
new file mode 100644
index 0000000000..a7d9b2b606
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/AutoSizeText.kt
@@ -0,0 +1,45 @@
+package com.tangem.tap.features.home.compose
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.ExperimentalUnitApi
+
+@OptIn(ExperimentalUnitApi::class)
+@Composable
+fun AutoSizeText(
+ text: String,
+ textStyle: TextStyle,
+ modifier: Modifier = Modifier
+) {
+ val scaledTextStyle = remember { mutableStateOf(textStyle) }
+ val readyToDraw = remember { mutableStateOf(false) }
+
+ val onTextLayout: (TextLayoutResult) -> Unit = { result ->
+ if (result.didOverflowWidth) {
+ val fontSize = result.size.width / result.multiParagraph.width
+ scaledTextStyle.value =
+// scaledTextStyle.value.copy(fontSize = TextUnit(fontSize, TextUnitType.Sp))
+ scaledTextStyle.value.copy(fontSize = scaledTextStyle.value.fontSize * 0.9)
+ } else {
+ readyToDraw.value = true
+ }
+ }
+
+ Text(
+ text = text,
+ modifier = modifier.drawWithContent {
+ if (readyToDraw.value) {
+ drawContent()
+ }
+ },
+ softWrap = false,
+ onTextLayout = onTextLayout,
+ style = scaledTextStyle.value,
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/FirstStoriesContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/FirstStoriesContent.kt
new file mode 100644
index 0000000000..92929c0027
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/FirstStoriesContent.kt
@@ -0,0 +1,141 @@
+package com.tangem.tap.features.home.compose
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.slideInVertically
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.tangem.wallet.R
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun FirstStoriesContent(
+ paused: Boolean, duration: Int = 8_000,
+ hideContent: (Boolean) -> Unit
+) {
+ val screenState = remember { mutableStateOf(StartingScreenState.INIT) }
+ val progress = remember { Animatable(0f) }
+
+ LaunchedEffect(paused) {
+ if (paused) {
+ progress.stop()
+ } else {
+ progress.animateTo(
+ targetValue = 2f,
+ animationSpec = tween(
+ durationMillis = duration,
+ easing = LinearEasing
+ )
+ )
+ }
+ }
+
+ when (progress.value) {
+ in 0f..0.2f -> screenState.value = StartingScreenState.INIT
+ in 0.2f..0.3f -> screenState.value = StartingScreenState.BUY
+ in 0.3f..0.4f -> screenState.value = StartingScreenState.STORE
+ in 0.4f..0.5f -> screenState.value = StartingScreenState.SEND
+ in 0.5f..0.6f -> screenState.value = StartingScreenState.PAY
+ in 0.6f..0.7f -> screenState.value = StartingScreenState.EXCHANGE
+ in 0.7f..0.8f -> screenState.value = StartingScreenState.BORROW
+ in 0.8f..1f -> screenState.value = StartingScreenState.LEND
+ in 1f..1.2f -> screenState.value = StartingScreenState.SHOW_CARD
+ in 1.2f..2f -> screenState.value = StartingScreenState.MEET_TANGEM
+ }
+
+ if (screenState.value == StartingScreenState.INIT) hideContent(true)
+ if (screenState.value == StartingScreenState.BUY) hideContent(false)
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier =
+ Modifier
+ .fillMaxSize(),
+ ) {
+
+ val text = when (screenState.value) {
+ StartingScreenState.INIT -> null
+ StartingScreenState.BUY -> R.string.story_meet_buy
+ StartingScreenState.STORE -> R.string.story_meet_store
+ StartingScreenState.SEND -> R.string.story_meet_send
+ StartingScreenState.PAY -> R.string.story_meet_pay
+ StartingScreenState.EXCHANGE -> R.string.story_meet_exchange
+ StartingScreenState.BORROW -> R.string.story_meet_borrow
+ StartingScreenState.LEND -> R.string.story_meet_lend
+ StartingScreenState.SHOW_CARD -> R.string.story_meet_title
+ StartingScreenState.MEET_TANGEM -> R.string.story_meet_title
+ }
+
+ val style = TextStyle(
+ fontSize = 60.sp,
+ fontWeight = FontWeight.SemiBold,
+ color = Color.White,
+ textAlign = TextAlign.Center,
+ )
+
+ if (screenState.value != StartingScreenState.MEET_TANGEM) {
+ AutoSizeText(text = text?.let { stringResource(text) } ?: "", textStyle = style,
+ modifier = Modifier
+ .padding(start = 40.dp, end = 40.dp)
+ .alpha(if (screenState.value == StartingScreenState.SHOW_CARD) 0f else 1f),)
+ }
+
+ AnimatedVisibility(
+ visible = screenState.value == StartingScreenState.MEET_TANGEM,
+ enter = slideInVertically() { it / 2 }
+ ) {
+ AutoSizeText(text = text?.let { stringResource(text) } ?: "", textStyle = style,
+ modifier = Modifier
+ .padding(start = 40.dp, end = 40.dp)
+ )
+ }
+
+ AnimatedVisibility(
+ visible = screenState.value == StartingScreenState.SHOW_CARD ||
+ screenState.value == StartingScreenState.MEET_TANGEM,
+ enter = scaleIn(initialScale = 3f)
+ ) {
+ Image(
+ painter = painterResource(
+ id = R.drawable.meet_tangem
+ ),
+ contentDescription = null,
+ contentScale = ContentScale.FillWidth,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ }
+}
+
+enum class StartingScreenState {
+ INIT, BUY, STORE, SEND, PAY, EXCHANGE, BORROW, LEND, SHOW_CARD, MEET_TANGEM
+}
+
+
+@Preview
+@Composable
+fun Stories1Preview() {
+ FirstStoriesContent(false, 7_000) {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt
new file mode 100644
index 0000000000..040c72557f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt
@@ -0,0 +1,68 @@
+package com.tangem.tap.features.home.compose
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.tangem.wallet.R
+
+@Composable
+fun HomeButtons(
+ isDarkBackground: Boolean, modifier: Modifier = Modifier,
+ onScanButtonClick: () -> Unit,
+ onShopButtonClick: () -> Unit
+) {
+ val darkColorBackground = Color(0xFF26292E)
+ val darkColorButton = Color(0xFF080C10)
+
+ Row(
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ modifier = modifier
+ ) {
+ Button(
+ modifier = Modifier
+ .weight(1f)
+ .height(48.dp),
+ onClick = onScanButtonClick,
+ colors = ButtonDefaults.textButtonColors(
+ backgroundColor = if (isDarkBackground) darkColorBackground else Color.White,
+ contentColor = if (isDarkBackground) Color.White else darkColorButton
+ )
+ ) {
+ Text(
+ text = stringResource(id = R.string.home_button_scan),
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ textAlign = TextAlign.Center,
+ maxLines = 1
+ )
+ }
+ Spacer(modifier = Modifier.size(8.dp))
+ Button(
+ modifier = Modifier
+ .weight(1f)
+ .height(48.dp),
+ onClick = onShopButtonClick,
+ colors = ButtonDefaults.textButtonColors(
+ backgroundColor = if (isDarkBackground) Color.White else darkColorBackground,
+ contentColor = if (isDarkBackground) darkColorButton else Color.White
+ )
+ ) {
+ Text(
+ text = stringResource(id = R.string.home_button_order),
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ textAlign = TextAlign.Center,
+ maxLines = 1
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt b/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt
new file mode 100644
index 0000000000..15728ae713
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt
@@ -0,0 +1,157 @@
+package com.tangem.tap.features.home.compose
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.tap.features.home.redux.HomeState
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.wallet.R
+import kotlin.math.max
+
+@Composable
+fun StoriesScreen(
+ homeState: MutableState = mutableStateOf(HomeState()),
+ onScanButtonClick: () -> Unit,
+ onShopButtonClick: () -> Unit
+) {
+ val steps = 6
+ val stepDuration = 8_000
+
+ val currentStep = remember { mutableStateOf(1) }
+
+ val isDarkBackground = currentStep.value !in 3..5
+
+ val goToPreviousScreen = {
+ currentStep.value = max(1, currentStep.value - 1)
+ }
+ val goToNextScreen = {
+ currentStep.value = if (currentStep.value < steps) currentStep.value + 1 else 1
+ }
+
+ val isPressed = remember { mutableStateOf(false) }
+ val needsToBePaused =
+ remember(homeState) { mutableStateOf(homeState.value.btnScanState.progressState == ProgressState.Loading) }
+ val pause = isPressed.value || needsToBePaused.value
+
+ val hideContent = remember { mutableStateOf(true) }
+
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(Color(0xFF090E13))
+ ) {
+ Row(
+ Modifier.fillMaxSize()
+ ) {
+ Box(
+ Modifier
+ .weight(1f)
+ .fillMaxHeight()
+ .pointerInput(isPressed) {
+ detectTapGestures(
+ onPress = {
+ val pressStartTime = System.currentTimeMillis()
+ isPressed.value = true
+ this.tryAwaitRelease()
+ val pressEndTime = System.currentTimeMillis()
+ val totalPressTime = pressEndTime - pressStartTime
+ if (totalPressTime < 200) goToPreviousScreen()
+ isPressed.value = false
+ },
+ )
+ }
+ )
+ Box(
+ Modifier.weight(1f)
+ .fillMaxHeight()
+ .pointerInput(isPressed) {
+ detectTapGestures(
+ onPress = {
+ val pressStartTime = System.currentTimeMillis()
+ isPressed.value = true
+ this.tryAwaitRelease()
+ val pressEndTime = System.currentTimeMillis()
+ val totalPressTime = pressEndTime - pressStartTime
+ if (totalPressTime < 200) goToNextScreen()
+ isPressed.value = false
+ },
+ )
+ }
+ )
+ }
+ if (!isDarkBackground) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_overlay),
+ contentDescription = null,
+ modifier = Modifier
+ .fillMaxSize(),
+ contentScale = ContentScale.FillBounds
+ )
+ }
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Spacer(modifier = Modifier.size(24.dp))
+ StoriesProgressBar(
+ steps = steps,
+ currentStep = currentStep.value,
+// paused = isPressed.value,
+ stepDuration = stepDuration,
+ paused = pause,
+ onStepFinished = goToNextScreen,
+ )
+ Image(
+ painter = painterResource(id = R.drawable.ic_tangem_logo),
+ contentDescription = null,
+ contentScale = ContentScale.FillHeight,
+ modifier = Modifier
+ .padding(start = 16.dp, top = 10.dp)
+ .height(17.dp)
+ .alpha(if (hideContent.value) 0f else 1f)
+ .align(Alignment.Start),
+ colorFilter = if (isDarkBackground) null else ColorFilter.tint(Color.Black)
+ )
+ when (currentStep.value) {
+ 1 -> FirstStoriesContent(pause, stepDuration) { hideContent.value = it }
+ 2 -> StoriesRevolutionaryWallet()
+ 3 -> StoriesUltraSecureBackup()
+ 4 -> StoriesThousandsOfCurrencies()
+ 5 -> StoriesWeb3()
+ 6 -> StoriesWalletForEveryone()
+ }
+ }
+ HomeButtons(
+ isDarkBackground = isDarkBackground,
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(16.dp, 0.dp, 16.dp, 37.dp)
+ .fillMaxWidth(),
+ onScanButtonClick = onScanButtonClick,
+ onShopButtonClick = onShopButtonClick
+ )
+ }
+}
+
+
+@Preview
+@Composable
+fun InstagramScreenPreview() {
+ StoriesScreen(onScanButtonClick = {}, onShopButtonClick = {})
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt
new file mode 100644
index 0000000000..b12be36802
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt
@@ -0,0 +1,120 @@
+package com.tangem.tap.features.home.compose
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.tangem.wallet.R
+
+@Composable
+fun StoriesGeneralContent(
+ titleText: String,
+ subtitleText: String,
+ imageSource: Int?,
+ isDarkBackground: Boolean,
+ imageComposable: (() -> Unit)? = null,
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier =
+ Modifier
+ .fillMaxWidth()
+ .fillMaxHeight(),
+ ) {
+
+ Text(
+ text = titleText,
+ fontSize = 32.sp,
+ fontWeight = FontWeight.SemiBold,
+ modifier = Modifier
+ .padding(start = 40.dp, end = 40.dp),
+ color = if (isDarkBackground) Color.White else Color(0xFF090E13),
+ textAlign = TextAlign.Center
+ )
+
+ Spacer(modifier = Modifier.size(16.dp))
+
+ Text(
+ text = subtitleText,
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Normal,
+ modifier = Modifier
+ .padding(start = 40.dp, end = 40.dp),
+ color = Color(0xFFA6AAAD),
+ textAlign = TextAlign.Center
+ )
+
+ Spacer(modifier = Modifier.size(25.dp))
+
+ if (imageSource != null) {
+ Image(
+ painter = painterResource(id = imageSource),
+ contentDescription = null,
+ contentScale = if(isDarkBackground) ContentScale.Inside else ContentScale.FillWidth,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ imageComposable?.invoke()
+ }
+}
+
+@Composable
+fun StoriesRevolutionaryWallet() {
+ StoriesGeneralContent(
+ titleText = stringResource(id = R.string.story_awe_title),
+ subtitleText = stringResource(id = R.string.story_awe_description),
+ imageSource = R.drawable.revolutionary_wallet,
+ isDarkBackground = true
+ )
+}
+
+@Composable
+fun StoriesUltraSecureBackup() {
+ StoriesGeneralContent(
+ titleText = stringResource(id = R.string.story_backup_title),
+ subtitleText = stringResource(id = R.string.story_backup_description),
+ imageSource = R.drawable.floating_cards,
+ isDarkBackground = false
+ )
+}
+
+@Composable
+fun StoriesThousandsOfCurrencies() {
+ StoriesGeneralContent(
+ titleText = stringResource(id = R.string.story_currencies_title),
+ subtitleText = stringResource(id = R.string.story_currencies_description),
+ imageSource = R.drawable.thousands_of_currencies,
+ isDarkBackground = false
+ )
+}
+
+@Composable
+fun StoriesWeb3() {
+ StoriesGeneralContent(
+ titleText = stringResource(id = R.string.story_web3_title),
+ subtitleText = stringResource(id = R.string.story_web3_description),
+ imageSource = R.drawable.web_3,
+ isDarkBackground = false
+ )
+}
+
+@Composable
+fun StoriesWalletForEveryone() {
+ StoriesGeneralContent(
+ titleText = stringResource(id = R.string.story_finish_title),
+ subtitleText = stringResource(id = R.string.story_finish_description),
+ imageSource = R.drawable.wallet_for_everyone,
+ isDarkBackground = true
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesProgressBar.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesProgressBar.kt
new file mode 100644
index 0000000000..b8dd83e070
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesProgressBar.kt
@@ -0,0 +1,80 @@
+package com.tangem.tap.features.home.compose
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun StoriesProgressBar(
+ steps: Int,
+ currentStep: Int,
+ paused: Boolean = false,
+ stepDuration: Int = 8_000,
+ onStepFinished: () -> Unit = {},
+) {
+ val progress = remember(currentStep) { Animatable(0f) }
+
+ LaunchedEffect(paused, currentStep) {
+ if (paused) {
+ progress.stop()
+ } else {
+ progress.animateTo(
+ targetValue = 1f,
+ animationSpec = tween(
+ durationMillis = (stepDuration * (1f - progress.value)).toInt(),
+ easing = LinearEasing
+ )
+ )
+ progress.snapTo(0f)
+ onStepFinished()
+ }
+
+ }
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+// .height()
+ .padding(start = 9.dp, end = 9.dp, top = 16.dp),
+ ) {
+ for (index in 1..steps) {
+ Row(
+ modifier = Modifier
+ .height(2.dp)
+ .weight(1f)
+ .background(Color.White.copy(alpha = 0.4f))
+ ) {
+ Box(
+ modifier = Modifier
+ .background(Color.White)
+ .fillMaxHeight().let {
+ when (index) {
+ currentStep -> it.fillMaxWidth(progress.value)
+ in 0..currentStep -> it.fillMaxWidth(1f)
+ else -> it
+ }
+ },
+ ) {}
+ }
+ if (index != steps) {
+ Spacer(modifier = Modifier.width(4.dp))
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun StoriesProgressBarPreview() {
+ StoriesProgressBar(steps = 3, currentStep = 2, paused = false) { }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
index 14f762f6c4..d14d4cf71b 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
@@ -1,7 +1,6 @@
package com.tangem.tap.features.home.redux
import com.tangem.tap.common.entities.IndeterminateProgressButton
-import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import org.rekotlin.Action
sealed class HomeAction : Action {
@@ -10,10 +9,9 @@ sealed class HomeAction : Action {
object GoToShop : HomeAction()
// internal
- data class ShouldScanCardOnResume(val shouldScanCard: Boolean):HomeAction()
+ data class ShouldScanCardOnResume(val shouldScanCard: Boolean) : HomeAction()
object Init : HomeAction()
- data class SetFragmentShareTransition(val shareTransition: FragmentShareTransition?) : HomeAction()
data class SetTermsOfUseState(val isDisclaimerAccepted: Boolean) : HomeAction()
data class ChangeScanCardButtonState(val state: IndeterminateProgressButton) : HomeAction()
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
index fb59fee5b6..89c34cbf42 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
@@ -4,7 +4,6 @@ import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.GetCardSourceParams
import com.tangem.tap.common.entities.IndeterminateProgressButton
-import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.common.post
@@ -39,7 +38,7 @@ private val homeMiddleware: Middleware = { dispatch, state ->
when (action) {
is HomeAction.Init -> {
store.dispatch(GlobalAction.RestoreAppCurrency)
- store.dispatch(GlobalAction.GetMoonPayStatus)
+ store.dispatch(GlobalAction.InitCurrencyExchangeManager)
store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
}
is HomeAction.ShouldScanCardOnResume -> {
@@ -50,7 +49,7 @@ private val homeMiddleware: Middleware = { dispatch, state ->
}
is HomeAction.ReadCard -> handleReadCard()
is HomeAction.GoToShop -> {
- store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI)
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
store.state.globalState.analyticsHandlers?.triggerEvent(
event = AnalyticsEvent.GET_CARD,
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.WELCOME.param)
@@ -74,14 +73,14 @@ private fun handleReadCard() {
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
val navigateTo = OnboardingHelper.whereToNavigate(scanResponse)
store.dispatch(GlobalAction.Onboarding.Start(scanResponse))
- navigateTo(navigateTo, store.state.homeState.shareTransition)
+ navigateTo(navigateTo)
} else {
scope.launch {
store.onCardScanned(scanResponse)
withMainContext {
if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly))
- navigateTo(AppScreen.OnboardingTwins, store.state.homeState.shareTransition)
+ navigateTo(AppScreen.OnboardingTwins)
} else {
navigateTo(AppScreen.Wallet, null)
}
@@ -99,7 +98,7 @@ private fun changeButtonState(state: ButtonState) {
store.dispatch(HomeAction.ChangeScanCardButtonState(btnState))
}
-private fun navigateTo(screen: AppScreen, transition: FragmentShareTransition?) {
+private fun navigateTo(screen: AppScreen, transition: FragmentShareTransition? = null) {
post(DELAY_SDK_DIALOG_CLOSE) {
changeButtonState(ButtonState.ENABLED)
store.dispatch(NavigationAction.NavigateTo(screen, transition))
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
index d4f11f31f9..f4a7a9d407 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
@@ -15,9 +15,6 @@ private fun internalReduce(action: Action, state: AppState): HomeState {
var state = state.homeState
when (action) {
- is HomeAction.SetFragmentShareTransition -> {
- state = state.copy(shareTransition = action.shareTransition)
- }
is HomeAction.ShouldScanCardOnResume -> {
state = state.copy(shouldScanCardOnResume = action.shouldScanCard)
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
index 8c9949cf4c..38b19013d8 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
@@ -1,12 +1,10 @@
package com.tangem.tap.features.home.redux
import com.tangem.tap.common.entities.IndeterminateProgressButton
-import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.features.send.redux.states.ButtonState
import org.rekotlin.StateType
data class HomeState(
val shouldScanCardOnResume: Boolean = false,
- val shareTransition: FragmentShareTransition? = null,
val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED),
) : StateType
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt
index c747e1a745..e9f47acef9 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt
@@ -1,6 +1,8 @@
package com.tangem.tap.features.onboarding
import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.common.extensions.copyToClipboard
@@ -8,11 +10,11 @@ import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchShare
import com.tangem.tap.common.extensions.dispatchToastNotification
import com.tangem.tap.common.redux.AppDialog
+import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.dialog_onboarding_address_info.*
-import kotlinx.android.synthetic.main.layout_pseudo_toolbar.*
+import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
/**
[REDACTED_AUTHOR]
@@ -22,30 +24,40 @@ class AddressInfoBottomSheetDialog(
context: Context
) : BottomSheetDialog(context) {
- init {
- this.setContentView(R.layout.dialog_onboarding_address_info)
+ var binding: DialogOnboardingAddressInfoBinding? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DialogOnboardingAddressInfoBinding
+ .inflate(LayoutInflater.from(context))
+ setContentView(binding!!.root)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
- setOnCancelListener { store.dispatchDialogHide() }
+ setOnCancelListener {
+ store.dispatchDialogHide()
+ binding = null
+ }
}
override fun show() {
super.show()
- val data = stateDialog.addressData
+ showData(data = stateDialog.addressData)
+ }
- imv_close.setOnClickListener {
+ private fun showData(data: AddressData) = with(binding!!) {
+ pseudoToolbar.imvClose.setOnClickListener {
dismissWithAnimation = true
cancel()
}
- imv_qr_code.setImageBitmap(data.qrCode)
- tv_address.text = data.address
- btn_fl_copy_address.setOnClickListener {
+ imvQrCode.setImageBitmap(data.qrCode)
+ tvAddress.text = data.address
+ btnFlCopyAddress.setOnClickListener {
context.copyToClipboard(data.address)
store.dispatchToastNotification(R.string.copy_toast_msg)
}
- btn_fl_share.setOnClickListener {
+ btnFlShare.setOnClickListener {
store.dispatchShare(data.shareUrl)
}
- tv_recieve_message.text = getQRReceiveMessage(tv_recieve_message.context, stateDialog.currency)
+ tvReceiveMessage.text = getQRReceiveMessage(tvReceiveMessage.context, stateDialog.currency)
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt
index b33b3118f5..f29444abc5 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt
@@ -1,14 +1,12 @@
package com.tangem.tap.features.onboarding.products
-import android.os.Bundle
-import android.view.View
-import com.tangem.tap.common.extensions.inflate
+import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.transitions.HomeToOnboardingTransition
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_onboarding_main.*
-import kotlinx.android.synthetic.main.view_confetti.*
+import com.tangem.wallet.databinding.FragmentOnboardingMainBinding
+import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
import org.rekotlin.StoreSubscriber
/**
@@ -16,26 +14,22 @@ import org.rekotlin.StoreSubscriber
*/
abstract class BaseOnboardingFragment : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber {
+ protected val binding: FragmentOnboardingMainBinding by viewBinding(FragmentOnboardingMainBinding::bind)
+ protected val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind)
+
override fun configureTransitions() {
enterTransition = HomeToOnboardingTransition()
exitTransition = HomeToOnboardingTransition()
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- onboarding_top_container.inflate(getOnboardingTopContainerId(), true)
- }
-
- abstract fun getOnboardingTopContainerId(): Int
-
- protected fun showConfetti(show: Boolean) {
- lav_confetti.show(show)
+ protected fun showConfetti(show: Boolean) = with(binding.vConfetti) {
+ lavConfetti.show(show)
if (show) {
- lav_confetti.playAnimation()
+ lavConfetti.playAnimation()
} else {
- lav_confetti.cancelAnimation()
+ lavConfetti.cancelAnimation()
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt
index d9d832d5a6..3125784ba9 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt
@@ -26,11 +26,6 @@ import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteStat
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteStep
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_onboarding_main.*
-import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
-import kotlinx.android.synthetic.main.layout_onboarding_container_top.*
-import kotlinx.android.synthetic.main.view_onboarding_progress.*
-import kotlinx.android.synthetic.main.view_onboarding_tv_balance.*
/**
[REDACTED_AUTHOR]
@@ -41,8 +36,6 @@ class OnboardingNoteFragment : BaseOnboardingFragment() {
private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
- override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
@@ -52,11 +45,11 @@ class OnboardingNoteFragment : BaseOnboardingFragment() {
super.onViewCreated(view, savedInstanceState)
addBackPressHandler(this)
- imv_front_card.transitionName = ShareElement.imvFrontCard
+ binding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
startPostponedEnterTransition()
- toolbar.setTitle(R.string.onboarding_title)
- btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container)
+ binding.toolbar.setTitle(R.string.onboarding_title)
+ btnRefreshBalanceWidget = RefreshBalanceWidget(binding.onboardingTopContainer.onboardingMainContainer)
store.dispatch(OnboardingNoteAction.LoadCardArtwork)
store.dispatch(OnboardingNoteAction.DetermineStepOfScreen)
@@ -72,19 +65,19 @@ class OnboardingNoteFragment : BaseOnboardingFragment() {
}
override fun newState(state: OnboardingNoteState) {
- if (activity == null) return
+ if (activity == null || view == null) return
Picasso.get()
- .load(state.cardArtworkUrl)
- .error(R.drawable.card_placeholder_black)
- .placeholder(R.drawable.card_placeholder_black)
- ?.into(imv_front_card)
+ .load(state.cardArtworkUrl)
+ .error(R.drawable.card_placeholder_black)
+ .placeholder(R.drawable.card_placeholder_black)
+ ?.into(binding.onboardingTopContainer.imvFrontCard)
- pb_state.max = state.steps.size - 1
- pb_state.progress = state.progress
+ pbBinding.pbState.max = state.steps.size - 1
+ pbBinding.pbState.progress = state.progress
when (state.currentStep) {
- OnboardingNoteStep.None -> setupNoneState(state)
+ OnboardingNoteStep.None -> setupNoneState()
OnboardingNoteStep.CreateWallet -> setupCreateWalletState(state)
OnboardingNoteStep.TopUpWallet -> setupTopUpWalletState(state)
OnboardingNoteStep.Done -> setupDoneState(state)
@@ -97,77 +90,82 @@ class OnboardingNoteFragment : BaseOnboardingFragment() {
private fun setBalance(state: OnboardingNoteState) {
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
- if (state.balanceCriticalError == null) {
- val balanceValue = state.walletBalance.value.stripZeroPlainString()
- tv_balance_value.text = balanceValue
- tv_balance_currency.text = state.walletBalance.currency.currencySymbol
- } else {
- tv_balance_value.text = "–"
- tv_balance_currency.text = ""
+ with (binding.onboardingTopContainer.onboardingTvBalance) {
+ if (state.balanceCriticalError == null) {
+ val balanceValue = state.walletBalance.value.stripZeroPlainString()
+ tvBalanceValue.text = balanceValue
+ tvBalanceCurrency.text = state.walletBalance.currency.currencySymbol
+ } else {
+ tvBalanceValue.text = "–"
+ tvBalanceCurrency.text = ""
+ }
}
}
- private fun setupNoneState(state: OnboardingNoteState) {
- btn_main_action.isVisible = false
- btn_alternative_action.isVisible = false
+ private fun setupNoneState() = with(binding.onboardingActionContainer) {
+ btnMainAction.isVisible = false
+ btnAlternativeAction.isVisible = false
btnRefreshBalanceWidget.show(false)
- tv_header.isVisible = false
- tv_body.isVisible = false
+ tvHeader.isVisible = false
+ tvBody.isVisible = false
- btn_main_action.text = ""
- btn_alternative_action.text = ""
- tv_header.text = ""
- tv_body.text = ""
+ btnMainAction.text = ""
+ btnAlternativeAction.text = ""
+ tvHeader.text = ""
+ tvBody.text = ""
}
- private fun setupCreateWalletState(state: OnboardingNoteState) {
- btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
- btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) }
- btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean)
- btn_alternative_action.setOnClickListener { }
+ private fun setupCreateWalletState(state: OnboardingNoteState) =
+ with(binding.onboardingActionContainer) {
+ btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
+ btnMainAction.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) }
+ btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
+ btnAlternativeAction.setOnClickListener { }
- btnRefreshBalanceWidget.mainView.setOnClickListener(null)
+ btnRefreshBalanceWidget.mainView.setOnClickListener(null)
- tv_header.setText(R.string.onboarding_create_wallet_header)
- tv_body.setText(R.string.onboarding_create_wallet_body)
+ tvHeader.setText(R.string.onboarding_create_wallet_header)
+ tvBody.setText(R.string.onboarding_create_wallet_body)
- imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle))
- updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet)
+ binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
+ requireContext().getDrawableCompat(R.drawable.shape_circle)
+ )
+ updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet)
- btn_alternative_action.isVisible = false // temporary
- }
+ btnAlternativeAction.isVisible = false // temporary
+ }
- private fun setupTopUpWalletState(state: OnboardingNoteState) {
+ private fun setupTopUpWalletState(state: OnboardingNoteState) = with(binding.onboardingActionContainer) {
if (state.isBuyAllowed) {
- btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto)
- btn_main_action.setOnClickListener {
+ btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
+ btnMainAction.setOnClickListener {
store.dispatch(OnboardingNoteAction.TopUp)
}
- btn_alternative_action.isVisible = true
- btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address)
- btn_alternative_action.setOnClickListener {
+ btnAlternativeAction.isVisible = true
+ btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
+ btnAlternativeAction.setOnClickListener {
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
}
} else {
- btn_main_action.setText(R.string.onboarding_button_receive_crypto)
- btn_main_action.setOnClickListener {
+ btnMainAction.setText(R.string.onboarding_button_receive_crypto)
+ btnMainAction.setOnClickListener {
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
}
- btn_alternative_action.isVisible = false
+ btnAlternativeAction.isVisible = false
}
- tv_header.setText(R.string.onboarding_top_up_header)
+ tvHeader.setText(R.string.onboarding_top_up_header)
if (state.balanceNonCriticalError == null) {
- tv_body.setText(R.string.onboarding_top_up_body)
+ tvBody.setText(R.string.onboarding_top_up_body)
} else {
state.walletBalance.amountToCreateAccount?.let { amount ->
val tvBodyMessage = getString(
R.string.onboarding_top_up_body_no_account_error,
amount, state.walletBalance.currency.currencySymbol
)
- tv_body.text = tvBodyMessage
+ tvBody.text = tvBodyMessage
}
}
@@ -177,38 +175,43 @@ class OnboardingNoteFragment : BaseOnboardingFragment() {
store.dispatch(OnboardingNoteAction.Balance.Update)
}
}
- imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
+ binding.onboardingTopContainer.imvCardBackground
+ .setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet)
}
- private fun setupDoneState(state: OnboardingNoteState) {
- btn_main_action.setText(R.string.onboarding_done_button_continue)
- btn_main_action.setOnClickListener {
+ private fun setupDoneState(state: OnboardingNoteState) = with(binding.onboardingActionContainer) {
+ btnMainAction.setText(R.string.onboarding_done_button_continue)
+ btnMainAction.setOnClickListener {
showConfetti(false)
store.dispatch(OnboardingNoteAction.Done)
}
- btn_alternative_action.isVisible = false
- btn_alternative_action.setText("")
- btn_alternative_action.setOnClickListener { }
+ btnAlternativeAction.isVisible = false
+ btnAlternativeAction.setText("")
+ btnAlternativeAction.setOnClickListener { }
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
- tv_header.setText(R.string.onboarding_done_header)
- tv_body.setText(R.string.onboarding_done_body)
+ tvHeader.setText(R.string.onboarding_done_header)
+ tvBody.setText(R.string.onboarding_done_body)
- imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
+ binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
+ requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
+ )
updateConstraints(state.currentStep, R.layout.lp_onboarding_done_activation)
}
private fun updateConstraints(currentStep: OnboardingNoteStep, @LayoutRes layoutId: Int) {
if (this.previousStep == currentStep) return
- val constraintSet = ConstraintSet()
- constraintSet.clone(requireContext(), layoutId)
- constraintSet.applyTo(onboarding_main_container)
- val transition = InternalNoteLayoutTransition()
- transition.interpolator = OvershootInterpolator()
- TransitionManager.beginDelayedTransition(onboarding_main_container, transition)
+ with(binding.onboardingTopContainer) {
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(requireContext(), layoutId)
+ constraintSet.applyTo(onboardingMainContainer)
+ val transition = InternalNoteLayoutTransition()
+ transition.interpolator = OvershootInterpolator()
+ TransitionManager.beginDelayedTransition(onboardingMainContainer, transition)
+ }
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt
index 6a8cc146b7..a611b435d6 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt
@@ -13,6 +13,7 @@ import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
+import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.scope
@@ -33,14 +34,16 @@ class OnboardingNoteMiddleware {
private val onboardingNoteMiddleware: Middleware = { dispatch, state ->
{ next ->
{ action ->
- handleNoteAction(action, dispatch)
+ handleNoteAction(state, action, dispatch)
next(action)
}
}
}
-private fun handleNoteAction(action: Action, dispatch: DispatchFunction) {
+private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch: DispatchFunction) {
if (action !is OnboardingNoteAction) return
+ if (DemoHelper.tryHandle(appState, action)) return
+
val globalState = store.state.globalState
val onboardingManager = globalState.onboardingState.onboardingManager ?: return
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
index 150c551f0b..77b12445f7 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
@@ -27,7 +27,7 @@ data class OnboardingNoteState(
get() = steps.indexOf(currentStep)
val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property ->
- store.state.globalState.moonpayStatus?.buyIsAllowed(walletBalance.currency) ?: false
+ store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt
index 22e479721b..996fdd67fe 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt
@@ -18,18 +18,12 @@ import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOt
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsStep
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_onboarding_main.*
-import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
-import kotlinx.android.synthetic.main.layout_onboarding_container_top.*
-import kotlinx.android.synthetic.main.view_onboarding_progress.*
/**
[REDACTED_AUTHOR]
*/
class OnboardingOtherCardsFragment : BaseOnboardingFragment() {
- override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
@@ -39,10 +33,10 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment setupCreateWalletState(state)
- OnboardingOtherCardsStep.Done -> setupDoneState(state)
+ OnboardingOtherCardsStep.CreateWallet -> setupCreateWalletState()
+ OnboardingOtherCardsStep.Done -> setupDoneState()
}
showConfetti(state.showConfetti)
}
- private fun setupCreateWalletState(state: OnboardingOtherCardsState) {
- btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
- btn_main_action.setOnClickListener { store.dispatch(OnboardingOtherCardsAction.CreateWallet) }
- btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean)
- btn_alternative_action.setOnClickListener { }
+ private fun setupCreateWalletState() = with(binding) {
- tv_header.setText(R.string.onboarding_create_wallet_header)
- tv_body.setText(R.string.onboarding_create_wallet_body)
+ with(onboardingActionContainer) {
+ btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
+ btnMainAction.setOnClickListener { store.dispatch(OnboardingOtherCardsAction.CreateWallet) }
+ btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
+ btnAlternativeAction.setOnClickListener { }
- imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle))
+ tvHeader.setText(R.string.onboarding_create_wallet_header)
+ tvBody.setText(R.string.onboarding_create_wallet_body)
+
+ btnAlternativeAction.isVisible = false // temporary
+ }
+
+
+ binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
+ requireContext().getDrawableCompat(R.drawable.shape_circle)
+ )
updateConstraints(R.layout.lp_onboarding_create_wallet)
- btn_alternative_action.isVisible = false // temporary
}
- private fun setupDoneState(state: OnboardingOtherCardsState) {
- btn_main_action.setText(R.string.onboarding_done_button_continue)
- btn_main_action.setOnClickListener {
+ private fun setupDoneState() = with(binding.onboardingActionContainer) {
+ btnMainAction.setText(R.string.onboarding_done_button_continue)
+ btnMainAction.setOnClickListener {
showConfetti(false)
store.dispatch(OnboardingOtherCardsAction.Done)
}
- btn_alternative_action.isVisible = false
- btn_alternative_action.setText("")
- btn_alternative_action.setOnClickListener { }
+ btnAlternativeAction.isVisible = false
+ btnAlternativeAction.setText("")
+ btnAlternativeAction.setOnClickListener { }
- tv_header.setText(R.string.onboarding_done_header)
- tv_body.setText(R.string.onboarding_done_body)
+ tvHeader.setText(R.string.onboarding_done_header)
+ tvBody.setText(R.string.onboarding_done_body)
- imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
+ binding.onboardingTopContainer.imvCardBackground?.setBackgroundDrawable(
+ requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
+ )
updateConstraints(R.layout.lp_onboarding_done)
}
private fun updateConstraints(@LayoutRes layoutId: Int) {
- val constraintSet = ConstraintSet()
- constraintSet.clone(requireContext(), layoutId)
- constraintSet.applyTo(onboarding_main_container)
- val transition = InternalNoteLayoutTransition()
- transition.interpolator = OvershootInterpolator()
- TransitionManager.beginDelayedTransition(onboarding_main_container, transition)
+ with(binding.onboardingTopContainer) {
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(requireContext(), layoutId)
+ constraintSet.applyTo(onboardingMainContainer)
+ val transition = InternalNoteLayoutTransition()
+ transition.interpolator = OvershootInterpolator()
+ TransitionManager.beginDelayedTransition(onboardingMainContainer, transition)
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
index 9d68635c08..eb05dd7402 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
@@ -57,7 +57,7 @@ data class TwinCardsState(
get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property ->
- store.state.globalState.moonpayStatus?.buyIsAllowed(walletBalance.currency) ?: false
+ store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt
index 12e87f2a70..b1c97b64ee 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.features.onboarding.products.twins.ui
import android.os.Bundle
+import android.util.TypedValue
import android.view.View
import android.view.animation.OvershootInterpolator
import androidx.annotation.LayoutRes
@@ -31,12 +32,7 @@ import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_onboarding_main.*
-import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
-import kotlinx.android.synthetic.main.layout_onboarding_container_top.*
-import kotlinx.android.synthetic.main.view_bg_twins_welcome.*
-import kotlinx.android.synthetic.main.view_onboarding_progress.*
-import kotlinx.android.synthetic.main.view_onboarding_tv_balance.*
+import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
class TwinsCardsFragment : BaseOnboardingFragment() {
@@ -47,12 +43,11 @@ class TwinsCardsFragment : BaseOnboardingFragment() {
private val assetReader: AssetReader by lazy {
object : AssetReader {
- override fun readAssetAsString(name: String): String = requireContext().readAssetAsString(name)
+ override fun readAssetAsString(name: String): String =
+ requireContext().readAssetAsString(name)
}
}
- override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
@@ -74,36 +69,44 @@ class TwinsCardsFragment : BaseOnboardingFragment() {
super.onViewCreated(view, savedInstanceState)
addBackPressHandler(this)
- reconfigureLayoutForTwins()
- twinsWidget = TwinsCardWidget(LeapfrogWidget(cards_container)) { 285f }
- btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container)
+ reconfigureLayoutForTwins(binding.onboardingTopContainer)
- toolbar.title = getText(R.string.twins_recreate_toolbar)
+ val typedValue = TypedValue()
+ resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
+ val deviceScaleFactorForWelcomeState = typedValue.float
+
+ twinsWidget = TwinsCardWidget(LeapfrogWidget(binding.onboardingTopContainer.cardsContainer), deviceScaleFactorForWelcomeState) {
+ 285f * deviceScaleFactorForWelcomeState
+ }
+ btnRefreshBalanceWidget = RefreshBalanceWidget(binding.onboardingTopContainer.onboardingMainContainer)
+
+ binding.toolbar.title = getText(R.string.twins_recreate_toolbar)
Picasso.get()
- .load(Artwork.TWIN_CARD_1)
- .error(R.drawable.card_placeholder_black)
- .placeholder(R.drawable.card_placeholder_black)
- ?.into(imv_twin_front_card)
+ .load(Artwork.TWIN_CARD_1)
+ .error(R.drawable.card_placeholder_black)
+ .placeholder(R.drawable.card_placeholder_black)
+ ?.into(binding.onboardingTopContainer.imvTwinFrontCard)
Picasso.get()
- .load(Artwork.TWIN_CARD_2)
- .error(R.drawable.card_placeholder_white)
- .placeholder(R.drawable.card_placeholder_white)
- ?.into(imv_twin_back_card)
+ .load(Artwork.TWIN_CARD_2)
+ .error(R.drawable.card_placeholder_white)
+ .placeholder(R.drawable.card_placeholder_white)
+ ?.into(binding.onboardingTopContainer.imvTwinBackCard)
}
- private fun reconfigureLayoutForTwins() {
- imv_front_card.hide()
- imv_card_background.hide()
- cards_container.show()
+ private fun reconfigureLayoutForTwins(containerBinding: LayoutOnboardingContainerTopBinding) =
+ with(containerBinding) {
+ imvFrontCard.hide()
+ imvCardBackground.hide()
+ cardsContainer.show()
- imv_twin_front_card.transitionName = ShareElement.imvFrontCard
- imv_twin_back_card.transitionName = ShareElement.imvBackCard
+ imvTwinFrontCard.transitionName = ShareElement.imvFrontCard
+ imvTwinBackCard.transitionName = ShareElement.imvBackCard
- // if don't this, the bg_circle_... is overflow the app_bar
- app_bar.bringToFront()
- }
+ // if don't this, the bg_circle_... is overflow the app_bar
+ binding.appBar.bringToFront()
+ }
override fun subscribeToStore() {
store.subscribe(this) { state ->
@@ -115,10 +118,10 @@ class TwinsCardsFragment : BaseOnboardingFragment() {
}
override fun newState(state: TwinCardsState) {
- if (activity == null) return
+ if (activity == null || view == null) return
- pb_state.max = state.steps.size - 1
- pb_state.progress = state.progress
+ pbBinding.pbState.max = state.steps.size - 1
+ pbBinding.pbState.progress = state.progress
when (state.currentStep) {
TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state)
@@ -139,13 +142,15 @@ class TwinsCardsFragment : BaseOnboardingFragment() {
private fun setBalance(state: TwinCardsState) {
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
- if (state.balanceCriticalError == null) {
- val balanceValue = state.walletBalance.value.stripZeroPlainString()
- tv_balance_value.text = balanceValue
- tv_balance_currency.text = state.walletBalance.currency.currencySymbol
- } else {
- tv_balance_value.text = "–"
- tv_balance_currency.text = ""
+ with(binding.onboardingTopContainer.onboardingTvBalance) {
+ if (state.balanceCriticalError == null) {
+ val balanceValue = state.walletBalance.value.stripZeroPlainString()
+ tvBalanceValue.text = balanceValue
+ tvBalanceCurrency.text = state.walletBalance.currency.currencySymbol
+ } else {
+ tvBalanceValue.text = "–"
+ tvBalanceCurrency.text = ""
+ }
}
}
@@ -160,203 +165,235 @@ class TwinsCardsFragment : BaseOnboardingFragment() {
setupWelcomeState(state) { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet)) }
}
- private fun setupWelcomeState(state: TwinCardsState, mainAction: VoidCallback) {
+ private fun setupWelcomeState(state: TwinCardsState, mainAction: VoidCallback) =
+ with(binding.onboardingActionContainer) {
+ twinsWidget.toWelcome(false) { startPostponedEnterTransition() }
+
+ binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.show()
+ pbBinding.pbState.hide()
+
+ tvHeader.setText(R.string.twins_onboarding_subtitle)
+ tvBody.text = getString(
+ R.string.twins_onboarding_description_format,
+ state.cardNumber?.pairIndexNumber()
+ )
+
+ btnMainAction.setText(R.string.common_continue)
+ btnMainAction.setOnClickListener { mainAction() }
+ }
+
+ private fun setupWarningState(state: TwinCardsState) = with(binding.onboardingActionContainer) {
twinsWidget.toWelcome(false) { startPostponedEnterTransition() }
- onboarding_twins_welcome_bg.show()
- pb_state.hide()
+ binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
+ pbBinding.pbState.hide()
+ chbUnderstand.show()
- tv_header.setText(R.string.twins_onboarding_subtitle)
- tv_body.text = getString(R.string.twins_onboarding_description_format, state.cardNumber?.pairIndexNumber())
+ tvHeader.setText(R.string.common_warning)
+ tvBody.setText(R.string.twins_recreate_warning)
- btn_main_action.setText(R.string.common_continue)
- btn_main_action.setOnClickListener { mainAction() }
- }
-
- private fun setupWarningState(state: TwinCardsState) {
- twinsWidget.toWelcome(false) { startPostponedEnterTransition() }
-
- onboarding_twins_welcome_bg.hide()
- pb_state.hide()
- chb_understand.show()
-
- tv_header.setText(R.string.common_warning)
- tv_body.setText(R.string.twins_recreate_warning)
-
- chb_understand.setOnCheckedChangeListener { buttonView, isChecked ->
+ chbUnderstand.setOnCheckedChangeListener { buttonView, isChecked ->
store.dispatch(TwinCardsAction.SetUserUnderstand(isChecked))
}
- btn_main_action.isEnabled = state.userWasUnderstandIfWalletRecreate
- btn_main_action.setText(R.string.common_continue)
- btn_main_action.setOnClickListener {
+ btnMainAction.isEnabled = state.userWasUnderstandIfWalletRecreate
+ btnMainAction.setText(R.string.common_continue)
+ btnMainAction.setOnClickListener {
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet))
}
}
- private fun setupCreateFirstWalletState(state: TwinCardsState) {
- onboarding_twins_welcome_bg.hide()
- bg_circle_large.hide()
- bg_circle_medium.hide()
- bg_circle_min.hide()
- chb_understand.hide()
- pb_state.show()
+ private fun setupCreateFirstWalletState(state: TwinCardsState) =
+ with(binding.onboardingActionContainer) {
+ binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
+ binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleLarge.hide()
+ binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMedium.hide()
+ binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMin.hide()
+ chbUnderstand.hide()
+ pbBinding.pbState.show()
- onboarding_main_container.beginDelayedTransition()
+ binding.onboardingTopContainer.onboardingMainContainer.beginDelayedTransition()
- when (previousStep) {
- TwinCardsStep.None -> {
- twinsWidget.toLeapfrog(false) {
- when (state.cardNumber) {
- TwinCardNumber.First -> startPostponedEnterTransition()
- TwinCardNumber.Second -> {
- switchToCard(state.cardNumber, false) { startPostponedEnterTransition() }
+ when (previousStep) {
+ TwinCardsStep.None -> {
+ twinsWidget.toLeapfrog(false) {
+ when (state.cardNumber) {
+ TwinCardNumber.First -> startPostponedEnterTransition()
+ TwinCardNumber.Second -> {
+ switchToCard(
+ state.cardNumber,
+ false
+ ) { startPostponedEnterTransition() }
+ }
}
}
}
+ TwinCardsStep.Welcome, TwinCardsStep.Warning -> {
+ twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) })
+ }
}
- TwinCardsStep.Welcome, TwinCardsStep.Warning -> {
- twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) })
+
+ val twinIndexNumber = state.cardNumber?.indexNumber()
+ tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
+ tvBody.setText(R.string.onboarding_twins_interrupt_warning)
+
+ btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
+ btnMainAction.setOnClickListener {
+ store.dispatch(
+ TwinCardsAction.Wallet.LaunchFirstStep(
+ Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
+ assetReader
+ )
+ )
+ }
+
+ btnAlternativeAction.isVisible = false
+ btnAlternativeAction.isClickable = false
+ }
+
+ private fun setupCreateSecondWalletState(state: TwinCardsState) =
+ with(binding.onboardingActionContainer) {
+ switchToCard(state.cardNumber?.pairNumber())
+
+ val twinPairIndexNumber = state.cardNumber?.pairIndexNumber()
+ tvHeader.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber)
+ tvBody.setText(R.string.onboarding_twins_interrupt_warning)
+
+ btnMainAction.text =
+ getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
+ btnMainAction.setOnClickListener {
+ store.dispatch(
+ TwinCardsAction.Wallet.LaunchSecondStep(
+ Message(
+ getString(
+ R.string.twins_recreate_title_format,
+ twinPairIndexNumber
+ )
+ ),
+ Message(getString(R.string.twins_recreate_title_preparing)),
+ Message(getString(R.string.twins_recreate_title_creating_wallet)),
+ )
+ )
}
}
- val twinIndexNumber = state.cardNumber?.indexNumber()
- tv_header.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
- tv_body.setText(R.string.onboarding_twins_interrupt_warning)
+ private fun setupCreateThirdWalletState(state: TwinCardsState) =
+ with(binding.onboardingActionContainer) {
+ switchToCard(state.cardNumber)
- btn_main_action.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
- btn_main_action.setOnClickListener {
- store.dispatch(TwinCardsAction.Wallet.LaunchFirstStep(
- Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
- assetReader
- ))
+ val twinIndexNumber = state.cardNumber?.indexNumber()
+ tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
+ tvBody.setText(R.string.onboarding_twins_interrupt_warning)
+
+ btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
+ btnMainAction.setOnClickListener {
+ store.dispatch(
+ TwinCardsAction.Wallet.LaunchThirdStep(
+ Message(getString(R.string.twins_recreate_title_format, twinIndexNumber))
+ )
+ )
+ }
}
- btn_alternative_action.isVisible = false
- btn_alternative_action.isClickable = false
- }
- private fun setupCreateSecondWalletState(state: TwinCardsState) {
- switchToCard(state.cardNumber?.pairNumber())
-
- val twinPairIndexNumber = state.cardNumber?.pairIndexNumber()
- tv_header.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber)
- tv_body.setText(R.string.onboarding_twins_interrupt_warning)
-
- btn_main_action.text = getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
- btn_main_action.setOnClickListener {
- store.dispatch(TwinCardsAction.Wallet.LaunchSecondStep(
- Message(getString(R.string.twins_recreate_title_format, twinPairIndexNumber)),
- Message(getString(R.string.twins_recreate_title_preparing)),
- Message(getString(R.string.twins_recreate_title_creating_wallet)),
- ))
- }
- }
-
- private fun setupCreateThirdWalletState(state: TwinCardsState) {
- switchToCard(state.cardNumber)
-
- val twinIndexNumber = state.cardNumber?.indexNumber()
- tv_header.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
- tv_body.setText(R.string.onboarding_twins_interrupt_warning)
-
- btn_main_action.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
- btn_main_action.setOnClickListener {
- store.dispatch(TwinCardsAction.Wallet.LaunchThirdStep(
- Message(getString(R.string.twins_recreate_title_format, twinIndexNumber))
- ))
- }
- }
-
-
- private fun setupTopUpWalletState(state: TwinCardsState) {
- when (previousStep) {
- TwinCardsStep.None -> {
- when (state.cardNumber) {
- TwinCardNumber.First -> {
- twinsWidget.leapfrogWidget.unfold(false) {
- twinsWidget.toActivate(false) {
+ private fun setupTopUpWalletState(state: TwinCardsState) =
+ with(binding.onboardingActionContainer) {
+ when (previousStep) {
+ TwinCardsStep.None -> {
+ when (state.cardNumber) {
+ TwinCardNumber.First -> {
+ twinsWidget.leapfrogWidget.unfold(false) {
+ twinsWidget.toActivate(false) {
+ startPostponedEnterTransition()
+ }
+ }
+ }
+ TwinCardNumber.Second -> {
+ switchToCard(state.cardNumber, false) {
+ twinsWidget.toActivate(false)
startPostponedEnterTransition()
}
}
}
- TwinCardNumber.Second -> {
- switchToCard(state.cardNumber, false) {
- twinsWidget.toActivate(false)
- startPostponedEnterTransition()
- }
- }
+ }
+ TwinCardsStep.CreateThirdWallet -> {
+ twinsWidget.toActivate()
}
}
- TwinCardsStep.CreateThirdWallet -> {
- twinsWidget.toActivate()
- }
- }
- if (state.isBuyAllowed) {
- btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto)
- btn_main_action.setOnClickListener {
- store.dispatch(TwinCardsAction.TopUp)
+ if (state.isBuyAllowed) {
+ btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
+ btnMainAction.setOnClickListener {
+ store.dispatch(TwinCardsAction.TopUp)
+ }
+
+ btnAlternativeAction.isVisible = true
+ btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
+ btnAlternativeAction.setOnClickListener {
+ store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
+ }
+ } else {
+ btnMainAction.setText(R.string.onboarding_button_receive_crypto)
+ btnMainAction.setOnClickListener {
+ store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
+ }
+
+ btnAlternativeAction.isVisible = false
}
- btn_alternative_action.isVisible = true
- btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address)
- btn_alternative_action.setOnClickListener {
- store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
+ tvHeader.setText(R.string.onboarding_top_up_header)
+ tvBody.setText(R.string.onboarding_top_up_body)
+
+ btnRefreshBalanceWidget.changeState(state.walletBalance.state)
+ if (btnRefreshBalanceWidget.isShowing != true) {
+ btnRefreshBalanceWidget.mainView.setOnClickListener {
+ store.dispatch(TwinCardsAction.Balance.Update)
+ }
}
- } else {
- btn_main_action.setText(R.string.onboarding_button_receive_crypto)
- btn_main_action.setOnClickListener {
- store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
+ binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
+ requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
+ )
+ updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins)
+ }
+
+ private fun setupDoneState(state: TwinCardsState) =
+ with(binding.onboardingActionContainer) {
+ btnMainAction.setText(R.string.onboarding_done_button_continue)
+ btnMainAction.setOnClickListener {
+ store.dispatch(TwinCardsAction.Confetti.Hide)
+ store.dispatch(TwinCardsAction.Done)
}
- btn_alternative_action.isVisible = false
- }
+ btnAlternativeAction.isVisible = false
+ btnAlternativeAction.isClickable = false
- tv_header.setText(R.string.onboarding_top_up_header)
- tv_body.setText(R.string.onboarding_top_up_body)
+ tvHeader.setText(R.string.onboarding_done_header)
+ tvBody.setText(R.string.onboarding_done_body)
- btnRefreshBalanceWidget.changeState(state.walletBalance.state)
- if (btnRefreshBalanceWidget.isShowing != true) {
- btnRefreshBalanceWidget.mainView.setOnClickListener {
- store.dispatch(TwinCardsAction.Balance.Update)
+ val layout = when (state.mode) {
+ CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
+ CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
}
+ updateConstraints(state.currentStep, layout)
}
- imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
- updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins)
- }
-
- private fun setupDoneState(state: TwinCardsState) {
- btn_main_action.setText(R.string.onboarding_done_button_continue)
- btn_main_action.setOnClickListener {
- store.dispatch(TwinCardsAction.Confetti.Hide)
- store.dispatch(TwinCardsAction.Done)
- }
-
- btn_alternative_action.isVisible = false
- btn_alternative_action.isClickable = false
-
- tv_header.setText(R.string.onboarding_done_header)
- tv_body.setText(R.string.onboarding_done_body)
-
- val layout = when (state.mode) {
- CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
- CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
- }
- updateConstraints(state.currentStep, layout)
- }
private fun updateConstraints(currentStep: TwinCardsStep, @LayoutRes layoutId: Int) {
if (this.previousStep == currentStep) return
- val constraintSet = ConstraintSet()
- constraintSet.clone(requireContext(), layoutId)
- constraintSet.applyTo(onboarding_main_container)
- val transition = InternalNoteLayoutTransition()
- transition.interpolator = OvershootInterpolator()
- TransitionManager.beginDelayedTransition(onboarding_main_container, transition)
+ with(binding.onboardingTopContainer) {
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(requireContext(), layoutId)
+ constraintSet.applyTo(onboardingMainContainer)
+ val transition = InternalNoteLayoutTransition()
+ transition.interpolator = OvershootInterpolator()
+ TransitionManager.beginDelayedTransition(onboardingMainContainer, transition)
+ }
}
- private fun switchToCard(cardNumber: TwinCardNumber?, animate: Boolean = true, onEnd: VoidCallback = {}) {
+ private fun switchToCard(
+ cardNumber: TwinCardNumber?,
+ animate: Boolean = true,
+ onEnd: VoidCallback = {}
+ ) {
val position = cardNumber?.number ?: return
if (twinsWidget.leapfrogWidget.getViewPositionByIndex(1) != position - 1) {
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
index 29cdd561b2..9abbebd018 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
@@ -8,7 +8,6 @@ import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.GetCardSourceParams
import com.tangem.tap.common.extensions.dispatchOnMain
-import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
@@ -16,6 +15,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.domain.tasks.product.ScanResponse
+import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL
import com.tangem.tap.features.wallet.redux.Artwork
@@ -124,10 +124,15 @@ private fun handleWalletAction(action: Action) {
} else {
null
}
- BackupAction.IntroduceBackup(url)
+ if (walletState.backupState.backupStep == BackupStep.InitBackup ||
+ walletState.backupState.backupStep == BackupStep.Finished) {
+ BackupAction.IntroduceBackup(url)
+ } else {
+ null
+ }
}
}
- store.dispatch(newAction)
+ newAction?.let { store.dispatch(it) }
}
OnboardingWalletAction.OnBackPressed -> {
when (walletState.backupState.backupStep) {
@@ -164,14 +169,16 @@ class BackupMiddleware {
val backupMiddleware: Middleware = { dispatch, state ->
{ next ->
{ action ->
- if (action is BackupAction) handleBackupAction(action)
+ if (action is BackupAction) handleBackupAction(state, action)
next(action)
}
}
}
}
-private fun handleBackupAction(action: BackupAction) {
+private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) {
+ if (DemoHelper.tryHandle(appState, action)) return
+
val backupState = store.state.onboardingWalletState.backupState
val globalState = store.state.globalState
@@ -215,13 +222,11 @@ private fun handleBackupAction(action: BackupAction) {
}
}
is BackupAction.GoToShop -> {
- backupState.buyAdditionalCardsUrl?.let { url ->
- store.dispatchOpenUrl(url)
- store.state.globalState.analyticsHandlers?.triggerEvent(
- event = AnalyticsEvent.GET_CARD,
- params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.ONBOARDING.param)
- )
- }
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
+ store.state.globalState.analyticsHandlers?.triggerEvent(
+ event = AnalyticsEvent.GET_CARD,
+ params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.ONBOARDING.param)
+ )
}
is BackupAction.FinishAddingBackupCards -> {
if (backupService.addedBackupCardsCount == 1) {
@@ -233,9 +238,11 @@ private fun handleBackupAction(action: BackupAction) {
}
is BackupAction.CheckAccessCode -> {
if (action.accessCode.length < 4) {
- store.dispatch(BackupAction.SetAccessCodeError(
- AccessCodeError.CodeTooShort
- ))
+ store.dispatch(
+ BackupAction.SetAccessCodeError(
+ AccessCodeError.CodeTooShort
+ )
+ )
} else {
store.dispatch(BackupAction.SetAccessCodeError(null))
store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode))
@@ -247,9 +254,11 @@ private fun handleBackupAction(action: BackupAction) {
backupService.setAccessCode(action.accessCodeConfirmation)
store.dispatch(BackupAction.PrepareToWritePrimaryCard)
} else {
- store.dispatch(BackupAction.SetAccessCodeError(
- AccessCodeError.CodesDoNotMatch
- ))
+ store.dispatch(
+ BackupAction.SetAccessCodeError(
+ AccessCodeError.CodesDoNotMatch
+ )
+ )
}
}
is BackupAction.WritePrimaryCard -> {
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt
index f7044f9986..3388e72a96 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt
@@ -1,28 +1,30 @@
package com.tangem.tap.features.onboarding.products.wallet.ui
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView
import com.tangem.tap.common.extensions.getString
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.item_backup_info_adapter.view.*
+import com.tangem.wallet.databinding.ItemBackupInfoAdapterBinding
class BackupInfoAdapter : RecyclerView.Adapter() {
private val data = backupInfoSnippets
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackupInfoViewHolder =
- BackupInfoViewHolder(LayoutInflater.from(parent.context)
- .inflate(R.layout.item_backup_info_adapter, parent, false))
+ BackupInfoViewHolder(
+ ItemBackupInfoAdapterBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
+ )
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: BackupInfoViewHolder, position: Int) =
- holder.itemView.run {
- tv_header.text = getString(data[position].header)
- tv_body.text = getString(data[position].body)
+ holder.binding.run {
+ tvHeader.text = root.getString(data[position].header)
+ tvBody.text = root.getString(data[position].body)
}
}
@@ -51,4 +53,5 @@ private val backupInfoSnippets = listOf(
)
-class BackupInfoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
\ No newline at end of file
+class BackupInfoViewHolder(val binding: ItemBackupInfoAdapterBinding) :
+ RecyclerView.ViewHolder(binding.root)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt
index 398ee44b55..056eb28b12 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt
@@ -1,18 +1,21 @@
package com.tangem.tap.features.onboarding.products.wallet.ui
import android.os.Bundle
+import android.util.TypedValue
import android.view.*
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import androidx.transition.TransitionManager
+import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayoutMediator
import com.squareup.picasso.Picasso
import com.tangem.common.CardIdFormatter
import com.tangem.common.core.CardIdDisplayFormat
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
+import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.FragmentOnBackPressedHandler
@@ -21,18 +24,21 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.*
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.fragment_onboarding_wallet.*
-import kotlinx.android.synthetic.main.layout_onboarding_buttons_add_cards.*
-import kotlinx.android.synthetic.main.layout_onboarding_buttons_common.*
-import kotlinx.android.synthetic.main.view_confetti.*
-import kotlinx.android.synthetic.main.view_onboarding_progress.*
+import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
+import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
import org.rekotlin.StoreSubscriber
class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
StoreSubscriber, FragmentOnBackPressedHandler {
private var accessCodeDialog: AccessCodeDialog? = null
- private lateinit var cardsWidget: BackupCardsWidget
+ private lateinit var cardsWidget: WalletCardsWidget
+ private val binding: FragmentOnboardingWalletBinding by viewBinding(
+ FragmentOnboardingWalletBinding::bind
+ )
+ private val pbBinding: ViewOnboardingProgressBinding by viewBinding(
+ ViewOnboardingProgressBinding::bind)
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -47,19 +53,29 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- val leapfrog = LeapfrogWidget(fl_cards_container)
- cardsWidget = BackupCardsWidget(leapfrog) { 200f }
+ val typedValue = TypedValue()
+ resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
+ val deviceScaleFactor = typedValue.float
+
+ val leapfrogCalculator = PropertyCalculator(
+ yTranslationFactor = 25f * deviceScaleFactor,
+ )
+ val leapfrog = LeapfrogWidget(binding.flCardsContainer, leapfrogCalculator)
+ cardsWidget = WalletCardsWidget(leapfrog, deviceScaleFactor) { 200f * deviceScaleFactor }
startPostponedEnterTransition()
- view_pager_backup_info.adapter = BackupInfoAdapter()
- TabLayoutMediator(tab_layout_backup_info, view_pager_backup_info) { tab, position ->
+ binding.viewPagerBackupInfo.adapter = BackupInfoAdapter()
+ TabLayoutMediator(
+ binding.tabLayoutBackupInfo,
+ binding.viewPagerBackupInfo
+ ) { tab, position ->
//Some implementation
}.attach()
- (activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
+ (activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
store.dispatch(OnboardingWalletAction.Init)
- toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
+ binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
store.dispatch(OnboardingWalletAction.LoadArtwork)
@@ -81,16 +97,18 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
}
override fun newState(state: OnboardingWalletState) {
- if (activity == null) return
+ if (activity == null || view == null) return
requireActivity().invalidateOptionsMenu()
- loadImageIntoImageView(state.cardArtworkUrl, imv_front_card)
- loadImageIntoImageView(state.cardArtworkUrl, imv_first_backup_card)
- loadImageIntoImageView(state.cardArtworkUrl, imv_second_backup_card)
+ with(binding) {
+ loadImageIntoImageView(state.cardArtworkUrl, imvFrontCard)
+ loadImageIntoImageView(state.cardArtworkUrl, imvFirstBackupCard)
+ loadImageIntoImageView(state.cardArtworkUrl, imvSecondBackupCard)
+ }
- pb_state.max = 6
- pb_state.progress = state.getProgressStep()
+ pbBinding.pbState.max = 6
+ pbBinding.pbState.progress = state.getProgressStep()
when (state.step) {
OnboardingWalletStep.None -> {
@@ -111,25 +129,24 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
?.into(view)
}
- private fun setupCreateWalletState() {
- btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
- btn_main_action.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) }
- btn_alternative_action.hide()
+ private fun setupCreateWalletState() = with(binding) {
+ layoutButtonsCommon.btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
+ layoutButtonsCommon.btnMainAction.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) }
+ layoutButtonsCommon.btnAlternativeAction.hide()
toolbar.title = getText(R.string.onboarding_getting_started)
- tv_header.setText(R.string.onboarding_create_wallet_header)
- tv_body.setText(R.string.onboarding_create_wallet_body)
+ tvHeader.setText(R.string.onboarding_create_wallet_header)
+ tvBody.setText(R.string.onboarding_create_wallet_body)
- cardsWidget.toFolded()
- startPostponedEnterTransition()
+ cardsWidget.toFolded(false) { startPostponedEnterTransition() }
}
private fun setBackupState(state: BackupState) {
when (state.backupStep) {
BackupStep.InitBackup -> showBackupIntro(state)
- BackupStep.ScanOriginCard -> showScanOriginCard(state)
+ BackupStep.ScanOriginCard -> showScanOriginCard()
BackupStep.AddBackupCards -> showAddBackupCards(state)
BackupStep.SetAccessCode -> showSetAccessCode()
BackupStep.EnterAccessCode -> showEnterAccessCode(state)
@@ -140,65 +157,70 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
}
}
- private fun showBackupIntro(state: BackupState) {
- imv_first_backup_card.show()
- imv_second_backup_card.show()
+ private fun showBackupIntro(state: BackupState) = with(binding) {
+ imvFirstBackupCard.show()
+ imvSecondBackupCard.show()
cardsWidget.toWelcome()
- tv_header.hide()
- tv_body.hide()
- view_pager_backup_info.show()
- tab_layout_backup_info.show()
+ tvHeader.hide()
+ tvBody.hide()
+ viewPagerBackupInfo.show()
+ tabLayoutBackupInfo.show()
+
+ with(layoutButtonsCommon) {
+ btnMainAction.text = getText(R.string.onboarding_button_backup_now)
+ btnAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
+ btnAlternativeAction.show(state.canSkipBackup)
+ btnMainAction.setOnClickListener { store.dispatch(BackupAction.StartBackup) }
+ btnAlternativeAction.setOnClickListener { store.dispatch(BackupAction.DismissBackup) }
+ }
- btn_main_action.text = getText(R.string.onboarding_button_backup_now)
- btn_alternative_action.text = getText(R.string.onboarding_button_skip_backup)
- btn_alternative_action.show(state.canSkipBackup)
- btn_main_action.setOnClickListener { store.dispatch(BackupAction.StartBackup) }
- btn_alternative_action.setOnClickListener { store.dispatch(BackupAction.DismissBackup) }
startPostponedEnterTransition()
}
- private fun showScanOriginCard(state: BackupState) {
+ private fun showScanOriginCard() = with(binding) {
prepareBackupView()
cardsWidget.toFolded()
- tv_header.text = getText(R.string.onboarding_title_scan_origin_card)
- tv_body.text = getString(
+ tvHeader.text = getText(R.string.onboarding_title_scan_origin_card)
+ tvBody.text = getString(
R.string.onboarding_subtitle_scan_origin_card,
)
- btn_main_action.text = getString(R.string.onboarding_button_scan_origin_card)
- btn_alternative_action.hide()
- btn_main_action.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
+ with(layoutButtonsCommon) {
+ btnMainAction.text = getString(R.string.onboarding_button_scan_origin_card)
+ btnAlternativeAction.hide()
+ btnMainAction.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
+ }
}
- private fun prepareBackupView() {
+ private fun prepareBackupView() = with(binding) {
toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup)
- tv_header.show()
- tv_body.show()
- view_pager_backup_info.hide()
- tab_layout_backup_info.hide()
+ tvHeader.show()
+ tvBody.show()
+ viewPagerBackupInfo.hide()
+ tabLayoutBackupInfo.hide()
- imv_first_backup_card.show()
- imv_second_backup_card.show()
+ imvFirstBackupCard.show()
+ imvSecondBackupCard.show()
}
- private fun showAddBackupCards(state: BackupState) {
+ private fun showAddBackupCards(state: BackupState) = with(binding) {
prepareBackupView()
accessCodeDialog?.dismiss()
accessCodeDialog = null
- layout_buttons_add_cards.show()
- layout_buttons_common.hide()
- btn_add_card.text = getText(R.string.onboarding_button_add_backup_card)
+ layoutButtonsAddCards.root.show()
+ layoutButtonsCommon.root.hide()
+ layoutButtonsAddCards.btnAddCard.text = getText(R.string.onboarding_button_add_backup_card)
if (state.backupCardsNumber < state.maxBackupCards) {
- btn_add_card.setOnClickListener { store.dispatch(BackupAction.AddBackupCard) }
+ layoutButtonsAddCards.btnAddCard.setOnClickListener { store.dispatch(BackupAction.AddBackupCard) }
} else {
- btn_add_card.isEnabled = false
+ layoutButtonsAddCards.btnAddCard.isEnabled = false
}
when (state.backupCardsNumber) {
@@ -207,27 +229,30 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
cardsWidget.getFirstBackupCardView().animate().alpha(0.6f).setDuration(200)
cardsWidget.getSecondBackupCardView().animate().alpha(0.2f).setDuration(200)
}
- tv_header.text = getText(R.string.onboarding_title_no_backup_cards)
- tv_body.text = getText(R.string.onboarding_subtitle_no_backup_cards)
+ tvHeader.text = getText(R.string.onboarding_title_no_backup_cards)
+ tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards)
}
1 -> {
- tv_header.text = getText(R.string.onboarding_title_one_backup_card)
- tv_body.text = getText(R.string.onboarding_subtitle_one_backup_card)
+ tvHeader.text = getText(R.string.onboarding_title_one_backup_card)
+ tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card)
+ cardsWidget.toFan(false)
cardsWidget.getFirstBackupCardView().animate().alpha(1f).setDuration(400)
cardsWidget.getSecondBackupCardView().alpha = 0.2f
}
2 -> {
- tv_header.text = getText(R.string.onboarding_title_two_backup_cards)
- tv_body.text = getText(R.string.onboarding_subtitle_two_backup_cards)
+ tvHeader.text = getText(R.string.onboarding_title_two_backup_cards)
+ tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards)
+
+ cardsWidget.toFan(false)
cardsWidget.getFirstBackupCardView().alpha = 1f
cardsWidget.getSecondBackupCardView().animate().alpha(1f).setDuration(400)
}
}
- btn_continue.text = getText(R.string.onboarding_button_finalize_backup)
- btn_continue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) }
- btn_continue.isEnabled = state.backupCardsNumber != 0
+ layoutButtonsAddCards.btnContinue.text = getText(R.string.onboarding_button_finalize_backup)
+ layoutButtonsAddCards.btnContinue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) }
+ layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0
}
private fun showSetAccessCode() {
@@ -258,7 +283,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
accessCodeDialog?.showError(state.accessCodeError)
}
- private fun showWritePrimaryCard(state: BackupState) {
+ private fun showWritePrimaryCard(state: BackupState) = with(binding) {
accessCodeDialog?.dismiss()
prepareViewForFinalizeStep()
@@ -270,33 +295,33 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
}
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
- tv_header.text = getText(R.string.onboarding_title_prepare_origin)
- tv_body.text = getString(
+ tvHeader.text = getText(R.string.onboarding_title_prepare_origin)
+ tvBody.text = getString(
R.string.onboarding_subtitle_scan_primary_card_format,
state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) }
)
- btn_main_action.text = getText(R.string.onboarding_button_backup_origin)
- btn_main_action.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) }
+ layoutButtonsCommon.btnMainAction.text = getText(R.string.onboarding_button_backup_origin)
+ layoutButtonsCommon.btnMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) }
}
- private fun prepareViewForFinalizeStep() {
- layout_buttons_add_cards.hide()
- layout_buttons_common.show()
+ private fun prepareViewForFinalizeStep() = with(binding) {
+ layoutButtonsAddCards.root.hide()
+ layoutButtonsCommon.root.show()
- imv_first_backup_card.show()
- imv_second_backup_card.show()
+ imvFirstBackupCard.show()
+ imvSecondBackupCard.show()
toolbar.title = getText(R.string.onboarding_button_finalize_backup)
- imv_card_background.hide()
+ imvCardBackground.hide()
cardsWidget.toLeapfrog()
- btn_alternative_action.hide()
+ layoutButtonsCommon.btnAlternativeAction.hide()
}
- private fun showWriteBackupCard(state: BackupState) {
+ private fun showWriteBackupCard(state: BackupState) = with(binding) {
prepareViewForFinalizeStep()
cardsWidget.getSecondBackupCardView().show(state.backupCardsNumber == 2)
@@ -316,46 +341,47 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
}
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
- tv_header.text =
+ tvHeader.text =
getString(R.string.onboarding_title_backup_card_format, cardNumber)
- tv_body.text = getString(
+ tvBody.text = getString(
R.string.onboarding_subtitle_scan_backup_card_format,
cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1])
)
- btn_main_action.text = getString(
+ layoutButtonsCommon.btnMainAction.text = getString(
R.string.onboarding_button_backup_card_format,
cardNumber
)
-
- btn_main_action.setOnClickListener { store.dispatch(BackupAction.WriteBackupCard(cardNumber)) }
+ layoutButtonsCommon.btnMainAction.setOnClickListener {
+ store.dispatch(BackupAction.WriteBackupCard(cardNumber))
+ }
}
- private fun showSuccess() {
- tv_header.text = getText(R.string.onboarding_done_header)
+ private fun showSuccess() = with(binding) {
+ tvHeader.text = getText(R.string.onboarding_done_header)
- tv_header.show()
- tv_body.show()
- view_pager_backup_info.hide()
- tab_layout_backup_info.hide()
+ tvHeader.show()
+ tvBody.show()
+ viewPagerBackupInfo.hide()
+ tabLayoutBackupInfo.hide()
- tv_body.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
- btn_main_action.text = getText(R.string.onboarding_button_continue_wallet)
- btn_alternative_action.hide()
- btn_main_action.setOnClickListener {
- lav_confetti.cancelAnimation()
- lav_confetti.hide()
+ tvBody.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
+ layoutButtonsCommon.btnMainAction.text = getText(R.string.onboarding_button_continue_wallet)
+ layoutButtonsCommon.btnAlternativeAction.hide()
+ layoutButtonsCommon.btnMainAction.setOnClickListener {
+ vConfetti.lavConfetti.cancelAnimation()
+ vConfetti.lavConfetti.hide()
store.dispatch(OnboardingWalletAction.FinishOnboarding)
}
cardsWidget.leapfrogWidget.fold {
- fl_cards_container?.hide()
- imv_card_background?.hide()
- lav_confetti?.show()
- lav_confetti?.playAnimation()
- imv_success?.alpha = 0f
- imv_success?.show()
+ flCardsContainer.hide()
+ imvCardBackground.hide()
+ vConfetti.lavConfetti.show()
+ vConfetti.lavConfetti.playAnimation()
+ imvSuccess.alpha = 0f
+ imvSuccess.show()
- imv_success?.animate()
+ imvSuccess.animate()
?.alpha(1f)
?.setDuration(400)
}
@@ -379,7 +405,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
val shopMenuShouldBeVisible =
(backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards) &&
- backupState.buyAdditionalCardsUrl != null
+ backupState.buyAdditionalCardsUrl != null
menu.getItem(0).isVisible = shopMenuShouldBeVisible
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt
similarity index 86%
rename from app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt
rename to app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt
index 1b4ecba2a7..9f42b3be0b 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt
@@ -10,8 +10,9 @@ import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
-class BackupCardsWidget(
+class WalletCardsWidget(
val leapfrogWidget: LeapfrogWidget,
+ private val deviceScaleFactor: Float = 1f,
val getTopOfAnchorViewForActivateState: () -> Float,
) {
@@ -42,7 +43,7 @@ class BackupCardsWidget(
createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)),
createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP))
)
- leapfrogWidget.fold { animator.start() }
+ leapfrogWidget.fold(animate) { animator.start() }
}
fun toFan(animate: Boolean = true, onEnd: () -> Unit = {}) {
@@ -98,26 +99,26 @@ class BackupCardsWidget(
private fun createWelcomeProperties(cardType: BackupCardType): CardProperties {
return when (cardType) {
BackupCardType.ORIGIN -> CardProperties(
- xTranslation = 440f,
- yTranslation = 0f,
- rotation = 75f,
- elevation = 2f,
- scale = .9f,
- )
+ xTranslation = 440f * deviceScaleFactor,
+ yTranslation = 70f,
+ rotation = 75f,
+ elevation = 2f,
+ scale = .85f * deviceScaleFactor,
+ )
BackupCardType.FIRST_BACKUP -> CardProperties(
- xTranslation = 10f,
- yTranslation = -100f,
- rotation = 100f,
- elevation = 1f,
- scale = .9f,
-)
+ xTranslation = 10f * deviceScaleFactor,
+ yTranslation = -30f,
+ rotation = 100f,
+ elevation = 1f,
+ scale = .85f * deviceScaleFactor,
+ )
BackupCardType.SECOND_BACKUP -> CardProperties(
- xTranslation = -440f,
- yTranslation = -100f,
- rotation = 70f,
- elevation = 0f,
- scale = .9f,
- )
+ xTranslation = -440f * deviceScaleFactor,
+ yTranslation = -30f,
+ rotation = 70f,
+ elevation = 0f,
+ scale = .85f * deviceScaleFactor,
+ )
}
}
@@ -125,24 +126,24 @@ class BackupCardsWidget(
return when (cardType) {
BackupCardType.ORIGIN -> CardProperties(
xTranslation = 0f,
- yTranslation = 0f,
+ yTranslation = 30f,
rotation = 5f,
elevation = 2f,
- scale = 1f,
+ scale = 1f * deviceScaleFactor,
)
BackupCardType.FIRST_BACKUP -> CardProperties(
xTranslation = 0f,
- yTranslation = -80f,
+ yTranslation = -50f,
rotation = -5f,
elevation = 1f,
- scale = 0.9f,
+ scale = 0.9f * deviceScaleFactor,
)
BackupCardType.SECOND_BACKUP -> CardProperties(
xTranslation = 10f,
- yTranslation = -170f,
+ yTranslation = -120f,
rotation = -20f,
elevation = 0f,
- scale = 0.8f,
+ scale = 0.8f * deviceScaleFactor,
)
}
}
@@ -154,21 +155,21 @@ class BackupCardsWidget(
yTranslation = 0f,
rotation = 0f,
elevation = 2f,
- scale = 1f,
+ scale = 1f * deviceScaleFactor,
)
BackupCardType.FIRST_BACKUP -> CardProperties(
xTranslation = 0f,
yTranslation = 0f,
rotation = 0f,
elevation = 1f,
- scale = 0.9f,
+ scale = 0.9f * deviceScaleFactor,
)
BackupCardType.SECOND_BACKUP -> CardProperties(
xTranslation = 0f,
yTranslation = 0f,
rotation = 0f,
elevation = 0f,
- scale = 0.8f,
+ scale = 0.8f * deviceScaleFactor,
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt
index 9433d4e274..c8b5e3ccb0 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt
@@ -1,8 +1,9 @@
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
import android.content.Context
+import android.content.DialogInterface
+import android.os.Bundle
import android.view.LayoutInflater
-import android.view.View
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.common.extensions.hide
@@ -11,64 +12,69 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.AccessCodeError
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.layout_backup_access_code.*
-import kotlinx.android.synthetic.main.layout_backup_access_code_info.*
-import kotlinx.android.synthetic.main.layout_backup_access_code_submit.*
+import com.tangem.wallet.databinding.LayoutBackupAccessCodeBinding
class AccessCodeDialog(context: Context) : BottomSheetDialog(context) {
+ var binding: LayoutBackupAccessCodeBinding? = null
- init {
- behavior.state = BottomSheetBehavior.STATE_EXPANDED
- val dialogView = LayoutInflater.from(context).inflate(R.layout.layout_backup_access_code, null)
- setContentView(dialogView)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = LayoutBackupAccessCodeBinding
+ .inflate(LayoutInflater.from(context))
+ setContentView(binding!!.root)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
- override fun setContentView(view: View) {
- super.setContentView(view)
- behavior.state = BottomSheetBehavior.STATE_EXPANDED
+ override fun setOnDismissListener(listener: DialogInterface.OnDismissListener?) {
+ super.setOnDismissListener(listener)
+ binding = null
}
-
- fun showInfoScreen() {
+ fun showInfoScreen() = with(binding!!) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
- access_code_title.text = context.getText(R.string.onboarding_access_code_intro_title)
- layout_backup_access_code_info.show()
- layout_backup_access_code_submit.hide()
- btn_access_code_create.setOnClickListener {
+ accessCodeTitle.text = context.getText(R.string.onboarding_access_code_intro_title)
+ layoutBackupAccessCodeInfo.root.show()
+ layoutBackupAccessCodeSubmit.root.hide()
+ layoutBackupAccessCodeInfo.btnAccessCodeCreate.setOnClickListener {
store.dispatch(BackupAction.ShowEnterAccessCodeScreen)
}
}
- fun showEnterAccessCode() {
- layout_backup_access_code_info.hide()
- layout_backup_access_code_submit.show()
- access_code_title.text = context.getText(R.string.onboarding_access_code_intro_title)
- btn_access_code_submit.text = context.getText(R.string.common_continue)
- btn_access_code_submit.setOnClickListener {
- store.dispatch(BackupAction.CheckAccessCode(et_access_code.text.toString()))
+ fun showEnterAccessCode() = with(binding!!) {
+ layoutBackupAccessCodeInfo.root.hide()
+ layoutBackupAccessCodeSubmit.root.show()
+ accessCodeTitle.text = context.getText(R.string.onboarding_access_code_intro_title)
+
+ with(layoutBackupAccessCodeSubmit) {
+ btnAccessCodeSubmit.text = context.getText(R.string.common_continue)
+ btnAccessCodeSubmit.setOnClickListener {
+ store.dispatch(BackupAction.CheckAccessCode(etAccessCode.text.toString()))
+ }
}
}
- fun showReenterAccessCode() {
- layout_backup_access_code_info.hide()
- layout_backup_access_code_submit.show()
- access_code_title.text = context.getText(R.string.onboarding_access_code_repeat_code_title)
- et_access_code.setText("")
- btn_access_code_submit.text = context.getText(R.string.common_submit)
- btn_access_code_submit.setOnClickListener {
- store.dispatch(BackupAction.SaveAccessCodeConfirmation(et_access_code.text.toString()))
+ fun showReenterAccessCode() = with(binding!!) {
+ layoutBackupAccessCodeInfo.root.hide()
+ layoutBackupAccessCodeSubmit.root.show()
+ accessCodeTitle.text = context.getText(R.string.onboarding_access_code_repeat_code_title)
+ with(layoutBackupAccessCodeSubmit) {
+ etAccessCode.setText("")
+ btnAccessCodeSubmit.text = context.getText(R.string.common_submit)
+ btnAccessCodeSubmit.setOnClickListener {
+ store.dispatch(BackupAction.SaveAccessCodeConfirmation(etAccessCode.text.toString()))
+ }
}
+
}
- fun showError(error: AccessCodeError?) {
+ fun showError(error: AccessCodeError?) = with(binding!!.layoutBackupAccessCodeSubmit) {
when (error) {
AccessCodeError.CodeTooShort ->
- til_access_code.error = context.getString(R.string.onboarding_access_code_too_short)
+ tilAccessCode.error = context.getString(R.string.onboarding_access_code_too_short)
AccessCodeError.CodesDoNotMatch ->
- til_access_code.error = context.getString(R.string.onboarding_access_codes_doesnt_match)
- null -> til_access_code.error = null
+ tilAccessCode.error = context.getString(R.string.onboarding_access_codes_doesnt_match)
+ null -> tilAccessCode.error = null
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
index 04d5b420d5..29165f8bad 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
@@ -6,6 +6,8 @@ import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.features.demo.DemoTransactionSender
+import com.tangem.tap.features.demo.isDemoWallet
import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.ReceiptAction
@@ -38,7 +40,11 @@ class RequestFeeMiddleware {
val destinationAddress = sendState.addressPayIdState.destinationWalletAddress!!
val destinationAmount = Amount(typedAmount, sendState.amountState.amountToSendCrypto)
- val txSender = walletManager as TransactionSender
+ val txSender = if (walletManager.isDemoWallet()) {
+ DemoTransactionSender(walletManager)
+ } else {
+ walletManager as TransactionSender
+ }
scope.launch {
val feeResult = txSender.getFee(destinationAmount, destinationAddress)
withContext(Dispatchers.Main) {
@@ -84,15 +90,15 @@ class FeeMock {
suspend fun feeZero(blockchain: Blockchain): List = listOf(Amount(BigDecimal.ZERO, blockchain))
suspend fun feeStandard(blockchain: Blockchain): List = listOf(
- Amount(0.001500.toBigDecimal(), blockchain),
- Amount(0.0030.toBigDecimal(), blockchain),
- Amount(0.0045001.toBigDecimal(), blockchain)
+ Amount(0.001500.toBigDecimal(), blockchain),
+ Amount(0.0030.toBigDecimal(), blockchain),
+ Amount(0.0045001.toBigDecimal(), blockchain)
)
suspend fun feeStandardBig(blockchain: Blockchain): List = listOf(
- Amount(1.76.toBigDecimal(), blockchain),
- Amount(2.30.toBigDecimal(), blockchain),
- Amount(3.45.toBigDecimal(), blockchain)
+ Amount(1.76.toBigDecimal(), blockchain),
+ Amount(2.30.toBigDecimal(), blockchain),
+ Amount(3.45.toBigDecimal(), blockchain)
)
suspend fun feeSingle(blockchain: Blockchain): List = listOf(Amount(0.0015.toBigDecimal(), blockchain))
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 572658b793..f103fc3817 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
@@ -13,6 +13,7 @@ import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
@@ -22,6 +23,8 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.minimalAmount
+import com.tangem.tap.features.demo.DemoTransactionSender
+import com.tangem.tap.features.demo.isDemoWallet
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
import com.tangem.tap.features.send.redux.states.ButtonState
@@ -32,6 +35,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
+import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -164,9 +168,12 @@ private fun sendTransaction(
)
)
}
-
val sendResult = try {
- (walletManager as TransactionSender).send(txData, signer)
+ if (walletManager.isDemoWallet()) {
+ DemoTransactionSender(walletManager).send(txData, signer)
+ } else {
+ (walletManager as TransactionSender).send(txData, signer)
+ }
} catch (ex: Exception) {
FirebaseCrashlytics.getInstance().recordException(ex)
delay(DELAY_SDK_DIALOG_CLOSE)
@@ -238,6 +245,13 @@ private fun sendTransaction(
message.contains("Target account is not created. To create account send 1+ XLM.") -> {
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
+ message.contains(DemoTransactionSender.ID) -> {
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ store.dispatchDialogShow(AppDialog.SimpleOkDialogRes(
+ R.string.common_done,
+ R.string.alert_demo_feature_disabled
+ ) { dispatch(NavigationAction.PopBackTo()) })
+ }
else -> {
(sendResult.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
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 eab62a8479..d999d79bcb 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
@@ -11,6 +11,7 @@ import androidx.core.view.postDelayed
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.textfield.TextInputEditText
import com.tangem.Message
import com.tangem.tangem_sdk_new.extensions.dpToPx
@@ -40,10 +41,7 @@ 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.layout_send_address_payid.*
-import kotlinx.android.synthetic.main.layout_send_amount.*
-import kotlinx.android.synthetic.main.layout_send_fee.*
+import com.tangem.wallet.databinding.FragmentSendBinding
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
@@ -62,6 +60,8 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
private val sendSubscriber = SendStateSubscriber(this)
private lateinit var keyboardObserver: KeyboardObserver
+ val binding: FragmentSendBinding by viewBinding(FragmentSendBinding::bind)
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
addBackPressHandler(this)
@@ -77,7 +77,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
store.dispatch(SendActionUi.CheckIfTransactionDataWasProvided)
}
- private fun initSendButtonStates() {
+ private fun initSendButtonStates() = with(binding) {
btnSend.setOnClickListener {
store.dispatch(SendActionUi.SendAmountToRecipient(
Message(getString(R.string.initial_message_sign_header))
@@ -86,7 +86,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
sendBtn = IndeterminateProgressButtonWidget(btnSend, progress)
}
- private fun setupAddressOrPayIdLayout() {
+ private fun setupAddressOrPayIdLayout() = with(binding.lSendAddressPayid) {
store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") })
store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString()))
@@ -113,7 +113,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
- private fun setupTransactionExtrasLayout() {
+ private fun setupTransactionExtrasLayout() = with(binding.lSendAddressPayid) {
etXlmMemo.inputtedTextAsFlow()
.debounce(400)
.filter {
@@ -151,9 +151,9 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
// Delayed launch is needed in order for the UI to be drawn and to process the sent events.
// If do not use the delay, then etAmount error field is not displayed when
// inserting an incorrect amount by shareUri
- imvQrCode.postDelayed({
+ binding.lSendAddressPayid.imvQrCode.postDelayed({
store.dispatch(PasteAddressPayId(scannedCode))
- store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
+ store.dispatch(TruncateOrRestore(!binding.lSendAddressPayid.etAddressOrPayId.isFocused))
}, 200)
}
@@ -165,7 +165,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
val decimalSeparator = DecimalFormatSymbols.getInstance().decimalSeparator.toString()
store.dispatch(AmountAction.SetDecimalSeparator(decimalSeparator))
- tvAmountCurrency.setOnClickListener {
+ binding.lSendAmount.tvAmountCurrency.setOnClickListener {
store.dispatch(ToggleMainCurrency)
store.dispatch(ReceiptAction.RefreshReceipt)
store.dispatch(SendAction.ChangeSendButtonState(store.state.sendState.getButtonState()))
@@ -223,8 +223,8 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
- private fun setupFeeLayout() {
- flExpandCollapse.setOnClickListener {
+ private fun setupFeeLayout() = with(binding.clNetworkFee) {
+ flExpandCollapse.flExpandCollapse.setOnClickListener {
store.dispatch(ToggleControlsVisibility)
}
chipGroup.check(FeeUiHelper.toId(FeeType.NORMAL))
@@ -240,12 +240,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
- private fun setupWarningMessages() {
+ private fun setupWarningMessages() = with(binding) {
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
+ rvWarningMessages.layoutManager = layoutManager
+ rvWarningMessages.addItemDecoration(SpacesItemDecoration(rvWarningMessages.dpToPx(16f).toInt()))
+ rvWarningMessages.adapter = warningsAdapter
store.dispatch(SendAction.Warnings.Update)
}
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 894d9809ba..3c49da49fb 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
@@ -26,32 +26,30 @@ 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.*
-import kotlinx.android.synthetic.main.fragment_send.*
-import kotlinx.android.synthetic.main.fragment_send.clReceiptContainer
-import kotlinx.android.synthetic.main.layout_receipt_total.*
-import kotlinx.android.synthetic.main.layout_receipt_total.view.*
-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.*
-import kotlinx.android.synthetic.main.layout_send_receipt.*
/**
[REDACTED_AUTHOR]
*/
-class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber(fragment) {
+class SendStateSubscriber(fragment: BaseStoreFragment) :
+ FragmentStateSubscriber(fragment) {
private var dialog: Dialog? = null
override fun updateWithNewState(fg: BaseStoreFragment, state: SendState) {
+ fg.view ?: return
+ if (fg !is SendFragment) return
+
val lastChangedStates = state.lastChangedStates.toList()
state.lastChangedStates.clear()
- fg.main_send_container.beginDelayedTransition()
+ (fg as? SendFragment)?.binding?.mainSendContainer?.beginDelayedTransition()
lastChangedStates.forEach {
when (it) {
StateId.SEND_SCREEN -> handleSendScreen(fg, state)
StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState)
- StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(fg, state.transactionExtrasState)
+ StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(
+ fg,
+ state.transactionExtrasState
+ )
StateId.AMOUNT -> handleAmountState(fg, state.amountState)
StateId.FEE -> handleFeeState(fg, state.feeState)
StateId.RECEIPT -> handleReceiptState(fg, state.receiptState)
@@ -59,57 +57,58 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
}
}
- private fun handleTransactionExtrasState(fg: BaseStoreFragment, infoState: TransactionExtrasState) {
- fun showView(view: View, info: Any?) {
- view.show(info != null)
- }
- showView(fg.xlmMemoContainer, infoState.xlmMemo)
- showView(fg.xrpDestinationTagContainer, infoState.xrpDestinationTag)
- showView(fg.binanceMemoContainer, infoState.binanceMemo)
+ private fun handleTransactionExtrasState(fg: SendFragment, infoState: TransactionExtrasState) =
+ with(fg.binding.lSendAddressPayid) {
- infoState.xlmMemo?.let {
- fg.etXlmMemo.inputType = when (it.selectedMemoType) {
- XlmMemoType.TEXT -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
- XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER
+ fun showView(view: View, info: Any?) {
+ view.show(info != null)
}
- if (!it.viewFieldValue.isFromUserInput) fg.etXlmMemo.setText(it.viewFieldValue.value)
- if (it.error != null) {
- if (it.error == TransactionExtraError.INVALID_XLM_MEMO) {
- fg.tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
- }
- } else {
- fg.tilXlmMemo.error = null
- }
- }
- infoState.xrpDestinationTag?.let {
- if (infoState.xrpDestinationTag.error != null) {
- if (infoState.xrpDestinationTag.error == TransactionExtraError.INVALID_DESTINATION_TAG) {
- fg.tilDestinationTag.error = fg.getText(R.string.send_error_invalid_destination_tag)
- }
- } else {
- fg.tilDestinationTag.error = null
- }
- if (!it.viewFieldValue.isFromUserInput) {
- fg.etDestinationTag.setText(it.viewFieldValue.value)
- }
- }
- infoState.binanceMemo?.let {
- if (infoState.binanceMemo.error != null) {
- if (infoState.binanceMemo.error == TransactionExtraError.INVALID_BINANCE_MEMO) {
- fg.tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
- }
- } else {
- fg.tilBinanceMemo.error = null
- }
- if (!it.viewFieldValue.isFromUserInput) {
- fg.etBinanceMemo.setText(it.viewFieldValue.value)
- }
- }
- }
+ showView(xlmMemoContainer, infoState.xlmMemo)
+ showView(xrpDestinationTagContainer, infoState.xrpDestinationTag)
+ showView(binanceMemoContainer, infoState.binanceMemo)
- private fun handleSendScreen(fg: BaseStoreFragment, state: SendState) {
- val sendFragment = (fg as? SendFragment) ?: return
+ infoState.xlmMemo?.let {
+ etXlmMemo.inputType = when (it.selectedMemoType) {
+ XlmMemoType.TEXT -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+ XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER
+ }
+ if (!it.viewFieldValue.isFromUserInput) etXlmMemo.setText(it.viewFieldValue.value)
+ if (it.error != null) {
+ if (it.error == TransactionExtraError.INVALID_XLM_MEMO) {
+ tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
+ }
+ } else {
+ tilXlmMemo.error = null
+ }
+ }
+ infoState.xrpDestinationTag?.let {
+ if (infoState.xrpDestinationTag.error != null) {
+ if (infoState.xrpDestinationTag.error == TransactionExtraError.INVALID_DESTINATION_TAG) {
+ tilDestinationTag.error =
+ fg.getText(R.string.send_error_invalid_destination_tag)
+ }
+ } else {
+ tilDestinationTag.error = null
+ }
+ if (!it.viewFieldValue.isFromUserInput) {
+ etDestinationTag.setText(it.viewFieldValue.value)
+ }
+ }
+ infoState.binanceMemo?.let {
+ if (infoState.binanceMemo.error != null) {
+ if (infoState.binanceMemo.error == TransactionExtraError.INVALID_BINANCE_MEMO) {
+ tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
+ }
+ } else {
+ tilBinanceMemo.error = null
+ }
+ if (!it.viewFieldValue.isFromUserInput) {
+ etBinanceMemo.setText(it.viewFieldValue.value)
+ }
+ }
+ }
+ private fun handleSendScreen(fg: SendFragment, state: SendState) = with(fg.binding) {
when (state.dialog) {
is SendAction.Dialog.TezosWarningDialog -> {
if (dialog == null) {
@@ -129,147 +128,158 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
}
}
- sendFragment.sendBtn.changeState(state.sendButtonState.progressState)
- sendFragment.sendBtn.mainView.isEnabled = state.sendButtonState.enabled
+ fg.sendBtn.changeState(state.sendButtonState.progressState)
+ fg.sendBtn.mainView.isEnabled = state.sendButtonState.enabled
- val rv = fg.rv_warning_messages
+ val rv = rvWarningMessages
val adapter = rv.adapter as? WarningMessagesAdapter ?: return
adapter.submitList(state.sendWarningsList)
rv.show(state.sendWarningsList.isNotEmpty())
- fg.toolbar.title = fg.getString(
+ toolbar.title = fg.getString(
R.string.send_title_currency_format,
state.amountState.mainCurrency.currencySymbol
)
}
- private fun handleAddressPayIdState(fg: BaseStoreFragment, state: AddressPayIdState) {
- fun parseError(context: Context, error: Error?): String? {
- val resId = when (error) {
- Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_error_payid_unsupported_by_blockchain
- Error.PAY_ID_NOT_REGISTERED -> R.string.send_error_payid_not_registered
- Error.PAY_ID_REQUEST_FAILED -> R.string.send_error_payid_request_failed
- Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_validation_invalid_address
- Error.ADDRESS_SAME_AS_WALLET -> R.string.send_error_address_same_as_wallet
- else -> null
+ private fun handleAddressPayIdState(fg: SendFragment, state: AddressPayIdState) =
+ with(fg.binding.lSendAddressPayid) {
+ fun parseError(context: Context, error: Error?): String? {
+ val resId = when (error) {
+ Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_error_payid_unsupported_by_blockchain
+ Error.PAY_ID_NOT_REGISTERED -> R.string.send_error_payid_not_registered
+ Error.PAY_ID_REQUEST_FAILED -> R.string.send_error_payid_request_failed
+ Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_validation_invalid_address
+ Error.ADDRESS_SAME_AS_WALLET -> R.string.send_error_address_same_as_wallet
+ else -> null
+ }
+ return if (resId == null) null else context.getString(resId, "", "")
}
- return if (resId == null) null else context.getString(resId, "", "")
+
+ imvPaste.isEnabled = state.pasteIsEnabled
+
+ val et = etAddressOrPayId
+ val til = tilAddressOrPayId
+ val parsedError = parseError(til.context, state.error)
+
+ til.isEnabled = state.inputIsEnabled
+ imvPaste.show(state.inputIsEnabled)
+ imvQrCode.show(state.inputIsEnabled)
+ flPaste.show(state.inputIsEnabled)
+ flQrCode.show(state.inputIsEnabled)
+
+ val hintResId = if (state.sendingToPayIdEnabled) {
+ R.string.send_destination_hint_address_payid
+ } else {
+ R.string.send_destination_hint_address
+ }
+ til.hint = til.getString(hintResId)
+ til.error = parsedError
+ til.isErrorEnabled = parsedError != null
+ til.helperText = state.destinationWalletAddress
+ til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
+
+ if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value)
}
- fg.imvPaste.isEnabled = state.pasteIsEnabled
- val et = fg.etAddressOrPayId
- val til = fg.tilAddressOrPayId
- val parsedError = parseError(til.context, state.error)
-
- til.isEnabled = state.inputIsEnabled
- fg.imvPaste.show(state.inputIsEnabled)
- fg.imvQrCode.show(state.inputIsEnabled)
- fg.flPaste.show(state.inputIsEnabled)
- fg.flQrCode.show(state.inputIsEnabled)
-
- val hintResId = if (state.sendingToPayIdEnabled) {
- R.string.send_destination_hint_address_payid
- } else {
- R.string.send_destination_hint_address
- }
- til.hint = til.getString(hintResId)
- til.error = parsedError
- til.isErrorEnabled = parsedError != null
- til.helperText = state.destinationWalletAddress
- til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
-
- if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value)
- }
-
- private fun handleAmountState(fg: BaseStoreFragment, state: AmountState) {
+ private fun handleAmountState(fg: SendFragment, state: AmountState) = with(fg.binding.lSendAmount) {
if (state.error != null) {
val context = fg.requireContext()
val message = when (state.error) {
is MultiMessageError -> {
val multiError = state.error as MultiMessageError
- val messageList = multiError.assembleErrors().map { getMessageString(context, it.first, it.second) }
+ val messageList = multiError.assembleErrors()
+ .map { getMessageString(context, it.first, it.second) }
multiError.builder(messageList)
}
else -> context.getString(state.error.messageResource)
}
- fg.tilAmountToSend.enableError(true, message)
+ tilAmountToSend.enableError(true, message)
} else {
- fg.tilAmountToSend.enableError(false)
+ tilAmountToSend.enableError(false)
}
val filter = DecimalDigitsInputFilter(12, state.maxLengthOfAmount, state.decimalSeparator)
- fg.etAmountToSend.filters = arrayOf(filter)
+ etAmountToSend.filters = arrayOf(filter)
val amountToSend = state.viewAmountValue
- if (!amountToSend.isFromUserInput) fg.etAmountToSend.update(amountToSend.value)
+ if (!amountToSend.isFromUserInput) etAmountToSend.update(amountToSend.value)
- fg.tvAmountCurrency.update(state.mainCurrency.currencySymbol)
+ tvAmountCurrency.update(state.mainCurrency.currencySymbol)
(fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.type)
val balanceText = when (state.mainCurrency.type) {
- MainCurrencyType.FIAT -> fg.getString(R.string.send_balance_subtitle_format,
- state.viewBalanceValue, state.mainCurrency.currencySymbol).remove(":")
- MainCurrencyType.CRYPTO -> fg.getString(R.string.send_balance_subtitle_format,
- state.mainCurrency.currencySymbol, state.viewBalanceValue)
+ MainCurrencyType.FIAT -> fg.getString(
+ R.string.send_balance_subtitle_format,
+ state.viewBalanceValue, state.mainCurrency.currencySymbol
+ ).remove(":")
+ MainCurrencyType.CRYPTO -> fg.getString(
+ R.string.send_balance_subtitle_format,
+ state.mainCurrency.currencySymbol, state.viewBalanceValue
+ )
}
- fg.tvBalance.update(balanceText)
+ tvBalance.update(balanceText)
- fg.tilAmountToSend.isEnabled = state.inputIsEnabled
+ tilAmountToSend.isEnabled = state.inputIsEnabled
val imageRes = if (state.inputIsEnabled) R.drawable.ic_arrows_up_down else 0
- fg.tvAmountCurrency.setCompoundDrawablesWithIntrinsicBounds(0, 0, imageRes, 0)
+ tvAmountCurrency.setCompoundDrawablesWithIntrinsicBounds(0, 0, imageRes, 0)
val textColor = if (state.inputIsEnabled) R.color.blue else R.color.textGray
- fg.tvAmountCurrency.setTextColor(fg.getColor(textColor))
-
+ tvAmountCurrency.setTextColor(fg.getColor(textColor))
}
- private fun handleFeeState(fg: BaseStoreFragment, state: FeeState) {
- fg.chipGroup.fitChipsByGroupWidth()
+ private fun handleFeeState(fg: SendFragment, state: FeeState) = with(fg.binding.clNetworkFee) {
+// chipGroup.fitChipsByGroupWidth()
fg.view?.findViewById(R.id.clNetworkFee)?.show(state.mainLayoutIsVisible)
+ flExpandCollapse.imvExpandCollapse.rotation = if (state.controlsLayoutIsVisible) 0f else 180f
+ llFeeControlsContainer.show(state.controlsLayoutIsVisible)
+ chipGroup.show(state.feeChipGroupIsVisible)
- fg.imvExpandCollapse.rotation = if (state.controlsLayoutIsVisible) 0f else 180f
- fg.llFeeControlsContainer.show(state.controlsLayoutIsVisible)
- fg.chipGroup.show(state.feeChipGroupIsVisible)
-
- fg.swIncludeFee.isEnabled = state.includeFeeSwitcherIsEnabled
- if (fg.swIncludeFee.isChecked != state.feeIsIncluded) {
- fg.swIncludeFee.isChecked = state.feeIsIncluded
+ swIncludeFee.isEnabled = state.includeFeeSwitcherIsEnabled
+ if (swIncludeFee.isChecked != state.feeIsIncluded) {
+ swIncludeFee.isChecked = state.feeIsIncluded
}
if (state.error == FeeAction.Error.REQUEST_FAILED) {
- fg.showRetrySnackbar(fg.requireContext().getString(R.string.send_error_fee_request_failed)) {
+ fg.showRetrySnackbar(
+ fg.requireContext().getString(R.string.send_error_fee_request_failed)
+ ) {
store.dispatch(FeeAction.RequestFee)
}
}
val chipId = FeeUiHelper.toId(state.selectedFeeType)
- if (fg.chipGroup.checkedChipId != chipId && chipId != View.NO_ID) fg.chipGroup.check(chipId)
+ if (chipGroup.checkedChipId != chipId && chipId != View.NO_ID) chipGroup.check(chipId)
}
- private fun handleReceiptState(fg: BaseStoreFragment, state: ReceiptState) {
- val mainLayout = fg.clReceiptContainer as ViewGroup
- val totalLayout = fg.llTotal as ViewGroup
- val totalTokenLayout = fg.flTotalTokenCrypto as ViewGroup
+ private fun handleReceiptState(fg: SendFragment, state: ReceiptState) = with(fg.binding.clReceiptContainer) {
+ val mainLayout = clReceiptContainer as ViewGroup
+ val totalLayout = llTotalContainer.llTotal as ViewGroup
+ val totalTokenLayout = llTotalContainer.flTotalTokenCrypto as ViewGroup
fun getString(id: Int, vararg formatStrings: String): String =
- mainLayout.context.getString(id, *formatStrings)
+ mainLayout.context.getString(id, *formatStrings)
val rough = getString(R.string.sign_rough)
- fun roughOrEmpty(value: String): String = if (value == ReceiptReducer.EMPTY) value else "$rough $value"
+ fun roughOrEmpty(value: String): String =
+ if (value == ReceiptReducer.EMPTY) value else "$rough $value"
when (state.visibleTypeOfReceipt) {
ReceiptLayoutType.FIAT -> {
val receipt = state.fiat ?: return
+ llTotalContainer.tvTotalValue
totalLayout.show(true)
totalTokenLayout.show(false)
- fg.tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
- fg.tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
- totalLayout.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}")
+ tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
+ tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
+ llTotalContainer.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}")
- val willSent = getString(R.string.send_total_subtitle_format,
- receipt.willSentCrypto, receipt.symbols.crypto)
- totalLayout.tvWillBeSentValue.update(willSent)
+ val willSent = getString(
+ R.string.send_total_subtitle_format,
+ receipt.willSentCrypto, receipt.symbols.crypto
+ )
+ llTotalContainer.tvWillBeSentValue.update(willSent)
}
ReceiptLayoutType.CRYPTO -> {
@@ -277,34 +287,34 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
totalLayout.show(true)
totalTokenLayout.show(false)
- fg.tvReceiptAmountValue.update("${receipt.amountCrypto} ${receipt.symbols.crypto}")
- fg.tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.crypto}")
- totalLayout.tvTotalValue.update("${receipt.totalCrypto} ${receipt.symbols.crypto}")
+ tvReceiptAmountValue.update("${receipt.amountCrypto} ${receipt.symbols.crypto}")
+ tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.crypto}")
+ llTotalContainer.tvTotalValue.update("${receipt.totalCrypto} ${receipt.symbols.crypto}")
val willSent = SpannableStringBuilder()
- .bold {
- append(roughOrEmpty(receipt.willSentFiat)).append(" ")
- append(receipt.symbols.fiat)
- append(" (fee: ${receipt.feeFiat} ")
- append(receipt.symbols.fiat).append(")")
- }
- totalLayout.tvWillBeSentValue.update(willSent.toString())
+ .bold {
+ append(roughOrEmpty(receipt.willSentFiat)).append(" ")
+ append(receipt.symbols.fiat)
+ append(" (fee: ${receipt.feeFiat} ")
+ append(receipt.symbols.fiat).append(")")
+ }
+ llTotalContainer.tvWillBeSentValue.update(willSent.toString())
}
ReceiptLayoutType.TOKEN_FIAT -> {
val receipt = state.tokenFiat ?: return
totalLayout.show(true)
totalTokenLayout.show(false)
- fg.tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
- fg.tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
- totalLayout.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}")
+ tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
+ tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
+ llTotalContainer.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}")
val willSent = getString(
R.string.send_total_subtitle_asset_format,
receipt.symbols.token ?: "", receipt.willSentToken,
receipt.symbols.crypto, receipt.willSentFeeCoin
)
- totalLayout.tvWillBeSentValue.update(willSent)
+ llTotalContainer.tvWillBeSentValue.update(willSent)
}
ReceiptLayoutType.TOKEN_CRYPTO -> {
val receipt = state.tokenCrypto ?: return
@@ -312,15 +322,15 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
totalLayout.show(false)
totalTokenLayout.show(true)
- fg.tvReceiptAmountValue.update("${receipt.amountToken} ${receipt.symbols.token}")
- fg.tvReceiptFeeValue.update("${receipt.feeCoin} ${receipt.symbols.crypto}")
+ tvReceiptAmountValue.update("${receipt.amountToken} ${receipt.symbols.token}")
+ tvReceiptFeeValue.update("${receipt.feeCoin} ${receipt.symbols.crypto}")
val willSent = SpannableStringBuilder()
- .bold {
- append(roughOrEmpty(receipt.totalFiat)).append(" ")
- append(receipt.symbols.fiat)
- }
- totalTokenLayout.tvTotalTokenCryptoValue.update(willSent.toString())
+ .bold {
+ append(roughOrEmpty(receipt.totalFiat)).append(" ")
+ append(receipt.symbols.fiat)
+ }
+ llTotalContainer.tvTotalTokenCryptoValue.update(willSent.toString())
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt
new file mode 100644
index 0000000000..500cc39285
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt
@@ -0,0 +1,40 @@
+package com.tangem.tap.features.shop.redux
+
+import android.content.Intent
+import com.tangem.tap.common.shop.GooglePayService
+import com.tangem.tap.common.shop.data.ProductType
+import com.tangem.tap.common.shop.data.TangemProduct
+import org.rekotlin.Action
+
+sealed class ShopAction : Action {
+
+ object LoadProducts : ShopAction() {
+ data class Success(val products: List) : ShopAction()
+ }
+
+ data class ApplyPromoCode(val promoCode: String) : ShopAction() {
+ data class Success(val promoCode: String?, val products: List) : ShopAction()
+ object InvalidPromoCode : ShopAction()
+ }
+
+ object BuyWithGooglePay : ShopAction() {
+ object UserCancelled : ShopAction()
+ data class HandleGooglePayResponse(val resultCode: Int, val data: Intent?) : ShopAction()
+
+ data class Failure(val exception: Throwable) : ShopAction()
+ object Success : ShopAction()
+ }
+
+ object StartWebCheckout : ShopAction()
+
+ data class CheckIfGooglePayAvailable(val googlePayService: GooglePayService) : ShopAction() {
+ object Success : ShopAction()
+ object Failure : ShopAction()
+ }
+
+ data class SelectProduct(val productType: ProductType) : ShopAction()
+
+ object FinishSuccessfulOrder : ShopAction()
+
+ object ResetState : ShopAction()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt
new file mode 100644
index 0000000000..464a99de96
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt
@@ -0,0 +1,127 @@
+package com.tangem.tap.features.shop.redux
+
+import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.extensions.dispatchOpenUrl
+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.scope
+import com.tangem.tap.shopService
+import com.tangem.tap.store
+import kotlinx.coroutines.launch
+import org.rekotlin.Action
+import org.rekotlin.Middleware
+
+class ShopMiddleware {
+
+ val shopMiddleware: Middleware = { dispatch, state ->
+ { next ->
+ { action ->
+ handle(action)
+ next(action)
+ }
+ }
+ }
+}
+
+private fun handle(action: Action) {
+
+ val shopState = store.state.shopState
+
+ if (action is NavigationAction.NavigateTo && action.screen == AppScreen.Shop) {
+ store.dispatch(ShopAction.LoadProducts)
+ }
+
+ if (action !is ShopAction) return
+
+ when (action) {
+ is ShopAction.ApplyPromoCode -> {
+ scope.launch {
+ if (action.promoCode.isBlank() && shopState.promoCode == null) {
+ store.dispatchOnMain(ShopAction.ApplyPromoCode.InvalidPromoCode)
+ return@launch
+ }
+
+ val result = shopService.applyPromoCode(action.promoCode)
+
+ result.onSuccess { products ->
+ store.dispatchOnMain(
+ ShopAction.ApplyPromoCode.Success(
+ promoCode = products.first { it.type == shopState.selectedProduct }.appliedDiscount,
+ products = products
+ )
+ )
+ }
+ result.onFailure { store.dispatchOnMain(ShopAction.ApplyPromoCode.InvalidPromoCode) }
+ }
+ }
+ ShopAction.BuyWithGooglePay -> {
+ shopService.buyWithGooglePay(shopState.selectedProduct)
+// shopService.subscribeToGooglePayResult(productType = shopState.selectedProduct) { result ->
+// result.onSuccess {
+// store.dispatch(ShopAction.BuyWithGooglePay.Success)
+// }
+// result.onFailure { error ->
+// if (error is TangemSdkError.UserCancelled) {
+// store.dispatch(ShopAction.BuyWithGooglePay.UserCancelled)
+// } else {
+// store.dispatch(ShopAction.BuyWithGooglePay.Failure(error))
+// }
+// }
+// }
+ }
+ is ShopAction.BuyWithGooglePay.HandleGooglePayResponse -> {
+ scope.launch {
+ val result = shopService.handleGooglePayResult(
+ action.resultCode,
+ action.data,
+ shopState.selectedProduct
+ )
+ result.onSuccess {
+ store.dispatchOnMain(ShopAction.BuyWithGooglePay.Success)
+ }
+ result.onFailure {
+ store.dispatchOnMain(ShopAction.BuyWithGooglePay.Failure(it))
+ }
+ }
+
+ }
+ ShopAction.LoadProducts -> {
+ scope.launch {
+ val result = shopService.getProducts()
+ result.onSuccess {
+ store.dispatchOnMain(ShopAction.LoadProducts.Success(it))
+ }
+ }
+ }
+ is ShopAction.CheckIfGooglePayAvailable -> {
+ scope.launch {
+ val isAvailable =
+ shopService.checkIfGooglePayAvailable(action.googlePayService).getOrNull()
+ ?: false
+ val newAction = if (isAvailable) {
+ ShopAction.CheckIfGooglePayAvailable.Success
+ } else {
+ ShopAction.CheckIfGooglePayAvailable.Failure
+ }
+ store.dispatchOnMain(newAction)
+
+ }
+ }
+ ShopAction.StartWebCheckout -> {
+ store.dispatchOpenUrl(shopService.getCheckoutUrl(shopState.selectedProduct))
+ store.dispatch(ShopAction.FinishSuccessfulOrder)
+ }
+
+ is ShopAction.FinishSuccessfulOrder -> {
+ scope.launch {
+ shopService.waitForCheckout(
+ productType = shopState.selectedProduct,
+ analyticsHandler = store.state.globalState.analyticsHandlers
+ )
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt
new file mode 100644
index 0000000000..ee12f53109
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt
@@ -0,0 +1,69 @@
+package com.tangem.tap.features.shop.redux
+
+import org.rekotlin.Action
+
+class ShopReducer {
+ companion object {
+ fun reduce(action: Action, state: ShopState): ShopState = internalReduce(action, state)
+ }
+}
+
+private fun internalReduce(action: Action, state: ShopState): ShopState {
+
+ if (action !is ShopAction) return state
+
+ return when (action) {
+ is ShopAction.ApplyPromoCode -> state.copy(
+ promoCode = action.promoCode,
+ promoCodeLoading = true
+ )
+ ShopAction.BuyWithGooglePay -> state
+ ShopAction.LoadProducts -> state
+ is ShopAction.LoadProducts.Success -> {
+ state.copy(
+ availableProducts = action.products,
+ )
+ }
+ ShopAction.StartWebCheckout -> state
+ ShopAction.ApplyPromoCode.InvalidPromoCode -> state.copy(
+ promoCode = null, promoCodeLoading = false
+ )
+ is ShopAction.ApplyPromoCode.Success -> {
+ state.copy(
+ promoCode = action.promoCode,
+ availableProducts = action.products,
+ promoCodeLoading = false
+
+ )
+ }
+ is ShopAction.SelectProduct -> {
+ state.copy(
+ selectedProduct = action.productType,
+ )
+ }
+ is ShopAction.CheckIfGooglePayAvailable -> {
+ state
+ }
+ ShopAction.CheckIfGooglePayAvailable.Failure -> {
+ state.copy(isGooglePayAvailable = false)
+ }
+ ShopAction.CheckIfGooglePayAvailable.Success -> {
+ state.copy(isGooglePayAvailable = false) // TODO: change when we add support for GPay
+
+ }
+ is ShopAction.BuyWithGooglePay.Failure -> {
+ state
+ }
+ is ShopAction.BuyWithGooglePay.HandleGooglePayResponse -> {
+ state
+ }
+ ShopAction.BuyWithGooglePay.Success -> {
+ state
+ }
+ ShopAction.BuyWithGooglePay.UserCancelled -> {
+ state
+ }
+ ShopAction.FinishSuccessfulOrder -> state
+ ShopAction.ResetState -> ShopState()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt
new file mode 100644
index 0000000000..a4ead917a3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt
@@ -0,0 +1,25 @@
+package com.tangem.tap.features.shop.redux
+
+import com.tangem.tap.common.shop.data.ProductType
+import com.tangem.tap.common.shop.data.TangemProduct
+import org.rekotlin.StateType
+
+data class ShopState(
+ val availableProducts: List = emptyList(),
+ val selectedProduct: ProductType = ProductType.WALLET_3_CARDS,
+ val promoCode: String? = null,
+ val promoCodeLoading: Boolean = false,
+ val isGooglePayAvailable: Boolean = false, // TODO: change when we add support for GPay
+) : StateType {
+ val total: String?
+ get() = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum?.finalValue
+
+ val priceBeforeDiscount: String?
+ get() {
+ val totalSum = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum
+ if (totalSum?.finalValue != totalSum?.beforeDiscount) {
+ return totalSum?.beforeDiscount
+ }
+ return null
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt
new file mode 100644
index 0000000000..51fe6cb54c
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt
@@ -0,0 +1,185 @@
+package com.tangem.tap.features.shop.ui
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import android.view.View.OnFocusChangeListener
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
+import androidx.activity.OnBackPressedCallback
+import by.kirich1409.viewbindingdelegate.viewBinding
+import com.tangem.tap.common.GlobalLayoutStateHandler
+import com.tangem.tap.common.KeyboardObserver
+import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.common.shop.data.ProductType
+import com.tangem.tap.features.BaseStoreFragment
+import com.tangem.tap.features.shop.redux.ShopAction
+import com.tangem.tap.features.shop.redux.ShopState
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import com.tangem.wallet.databinding.FragmentShopBinding
+import org.rekotlin.StoreSubscriber
+
+
+class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber {
+
+ private val binding: FragmentShopBinding by viewBinding(FragmentShopBinding::bind)
+
+ lateinit var keyboardObserver: KeyboardObserver
+
+ override fun subscribeToStore() {
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.shopState == newState.shopState
+ }.select { it.shopState }
+ }
+ storeSubscribersList.add(this)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ store.dispatch(NavigationAction.PopBackTo())
+ store.dispatch(ShopAction.ResetState)
+ }
+ })
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ keyboardObserver.unregisterListener()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setupCardsImages()
+ setupProductSelection()
+ setupPromoCodeEditText()
+
+ binding.toolbar.setNavigationOnClickListener {
+ requireActivity().onBackPressed()
+ }
+
+ keyboardObserver = KeyboardObserver(requireActivity()).apply {
+ registerListener { isVisible ->
+ binding.flCards.show(!isVisible)
+ }
+ }
+ }
+
+ private var cardTranslationY = 70f
+ private fun setupCardsImages() {
+ GlobalLayoutStateHandler(binding.imvSecond).apply {
+ onStateChanged = {
+ cardTranslationY = it.height * 0.15f
+ binding.imvSecond.animate()
+ .translationY(cardTranslationY)
+ .scaleX(0.9f)
+ .scaleY(0.9f)
+ .start()
+ binding.imvThird.animate()
+ .translationY(cardTranslationY * 2)
+ .scaleX(0.8f)
+ .scaleY(0.8f)
+ .start()
+ detach()
+ }
+ }
+
+ }
+
+ private fun setupProductSelection() = with(binding) {
+ chipProduct1.setOnCheckedChangeListener { _, isChecked ->
+ if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_3_CARDS))
+ }
+ chipProduct2.setOnCheckedChangeListener { _, isChecked ->
+ if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_2_CARDS))
+ }
+ }
+
+ private fun setupPromoCodeEditText() = with(binding) {
+ etPromoCode.setOnEditorActionListener { view, actionId, event ->
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ val imm: InputMethodManager =
+ requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.hideSoftInputFromWindow(view.windowToken, 0)
+ view.clearFocus()
+ return@setOnEditorActionListener true
+ }
+ return@setOnEditorActionListener false
+ }
+
+ etPromoCode.onFocusChangeListener = OnFocusChangeListener { _, hasFocus ->
+ if (!hasFocus) {
+ store.dispatch(ShopAction.ApplyPromoCode(etPromoCode.text.toString()))
+ }
+ }
+ }
+
+ override fun newState(state: ShopState) {
+ if (activity == null || view == null) return
+
+ animateProductSelection(state.selectedProduct)
+ handlePriceState(state)
+ handlePromoCodeState(state)
+ handleButtonsState(state)
+ }
+
+ private fun animateProductSelection(selectedProduct: ProductType) {
+ val show = when (selectedProduct) {
+ ProductType.WALLET_2_CARDS -> false
+ ProductType.WALLET_3_CARDS -> true
+ }
+ showOrHideThirdCardWithAnimation(show)
+ }
+
+ private fun showOrHideThirdCardWithAnimation(show: Boolean) = with(binding) {
+ val translationY = if (show) cardTranslationY * 2 else cardTranslationY
+ if (show) imvThird.show()
+ imvThird.animate()
+ .translationY(translationY)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ super.onAnimationEnd(animation)
+ imvThird.show(show)
+ }
+ })
+ }
+
+ private fun handlePriceState(state: ShopState) = with(binding) {
+ tvTotal.text = state.total
+ tvTotalBeforeDiscount.text = state.priceBeforeDiscount
+
+ pbPrice.show(state.total == null)
+
+ }
+
+ private fun handlePromoCodeState(state: ShopState) = with(binding) {
+ if (state.promoCode == null && !etPromoCode.hasFocus()) {
+ etPromoCode.setText("")
+ }
+ pbPromoCode.show(state.promoCodeLoading)
+ }
+
+ private fun handleButtonsState(state: ShopState) = with(binding) {
+ btnPayGooglePay.root.show(state.isGooglePayAvailable)
+ btnAlternativePayment.show(state.isGooglePayAvailable)
+ btnMainAction.show(!state.isGooglePayAvailable)
+
+ if (state.total != null) {
+ btnAlternativePayment.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) }
+ btnMainAction.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) }
+ btnPayGooglePay.root.setOnClickListener { store.dispatch(ShopAction.BuyWithGooglePay) }
+ }
+ }
+
+ override fun handleOnBackPressed() {
+ store.dispatch(ShopAction.ResetState)
+ super.handleOnBackPressed()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt
index 475cffff6c..8f242ed6c7 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt
@@ -23,12 +23,11 @@ sealed class CurrencyListItem {
blockchains: List,
tokens: List,
): List {
- return createBlockchainList(blockchains) +
- createTokensList(R.string.add_tokens_subtitle_ethereum_tokens, Blockchain.Ethereum, tokens) +
- createTokensList(R.string.add_tokens_subtitle_bsc_tokens, Blockchain.BSC, tokens) +
- createTokensList(R.string.add_tokens_subtitle_binance_tokens, Blockchain.Binance, tokens) +
- createTokensList(R.string.add_tokens_subtitle_polygon_tokens, Blockchain.Polygon, tokens) +
- createTokensList(R.string.add_tokens_subtitle_avalanche_tokens, Blockchain.Avalanche, tokens)
+ val blockchainsItemList = createBlockchainList(blockchains)
+ val tokenItemsLists = tokens.groupBy { it.blockchain }.map {
+ createTokensList(R.string.add_tokens_subtitle_format, it.key, it.value)
+ }
+ return blockchainsItemList + tokenItemsLists.flatten()
}
private fun createBlockchainList(blockchains: List): List {
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
index 69f9a8940e..123e5c25ff 100644
--- 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
@@ -49,8 +49,7 @@ class TokensMiddleware {
cardFirmware = scanResponse.card.firmwareVersion,
isTestNet = isTestcard
).sortedBy { it.fullName }
- val currencies =
- CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList()
+ val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens)
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
}
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
index 178d8d514f..fd7caf6b08 100644
--- 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
@@ -11,6 +11,8 @@ import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
+import by.kirich1409.viewbindingdelegate.viewBinding
+import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.tokens.redux.TokensState
@@ -18,7 +20,7 @@ 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 com.tangem.wallet.databinding.FragmentAddTokensBinding
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import org.rekotlin.StoreSubscriber
@@ -28,6 +30,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
StoreSubscriber {
private lateinit var viewAdapter: CurrenciesAdapter
+ private val binding: FragmentAddTokensBinding by viewBinding(FragmentAddTokensBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -56,29 +59,29 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
store.unsubscribe(this)
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) {
super.onViewCreated(view, savedInstanceState)
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
setupPopularTokensRecyclerView()
- btn_tokens_save_changes.setOnClickListener {
+ btnTokensSaveChanges.setOnClickListener {
store.dispatch(TokensAction.SaveChanges(viewAdapter.getAddedItems()))
}
}
- private fun setupPopularTokensRecyclerView() {
+ private fun setupPopularTokensRecyclerView() = with(binding) {
viewAdapter = CurrenciesAdapter()
viewAdapter.setOnItemAddListener {
- btn_tokens_save_changes.isEnabled = viewAdapter.getAddedItems().isNotEmpty()
+ btnTokensSaveChanges.isEnabled = viewAdapter.getAddedItems().isNotEmpty()
}
- rv_popular_tokens.layoutManager = LinearLayoutManager(context)
- rv_popular_tokens.adapter = viewAdapter
+ rvPopularTokens.layoutManager = LinearLayoutManager(context)
+ rvPopularTokens.adapter = viewAdapter
}
override fun newState(state: TokensState) {
- if (activity == null) return
+ if (activity == null || view == null) return
viewAdapter.addedCurrencies = state.addedCurrencies
viewAdapter.submitUnfilteredList(state.shownCurrencies)
@@ -96,7 +99,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
val menuItem = menu.findItem(R.id.menu_search)
val searchView: SearchView = menuItem.actionView as SearchView
- searchView.queryHint = "Type here to search"
+ searchView.queryHint = searchView.getString(R.string.add_token_search_hint)
searchView.maxWidth = android.R.attr.width
searchView.inputtedTextAsFlow()
.debounce(400)
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
index bc2b3177ae..250c6587d6 100644
--- 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
@@ -1,7 +1,6 @@
package com.tangem.tap.features.tokens.ui.adapters
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@@ -15,12 +14,13 @@ import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.loadCurrenciesIcon
import com.tangem.tap.common.extensions.show
import com.tangem.tap.domain.tokens.CardCurrencies
+import com.tangem.tap.domain.tokens.getTokensName
import com.tangem.tap.features.tokens.redux.CurrencyListItem
import com.tangem.tap.features.tokens.redux.TokensAction
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 com.tangem.wallet.databinding.ItemCurrencySubtitleBinding
+import com.tangem.wallet.databinding.ItemPopularTokenBinding
import java.util.*
class CurrenciesAdapter : ListAdapter(DiffUtilCallback) {
@@ -61,18 +61,18 @@ class CurrenciesAdapter : ListAdapter
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)
+ binding = ItemCurrencySubtitleBinding
+ .inflate(LayoutInflater.from(parent.context), parent, false)
)
1 -> CurrenciesViewHolder(
- LayoutInflater.from(parent.context)
- .inflate(R.layout.item_popular_token, parent, false),
- vhOnItemAddListener
+ binding = ItemPopularTokenBinding
+ .inflate(LayoutInflater.from(parent.context), parent, false),
+ onItemAddListener = vhOnItemAddListener
)
else -> CurrenciesViewHolder(
- LayoutInflater.from(parent.context)
- .inflate(R.layout.item_popular_token, parent, false),
- vhOnItemAddListener
+ binding = ItemPopularTokenBinding
+ .inflate(LayoutInflater.from(parent.context), parent, false),
+ onItemAddListener = vhOnItemAddListener
)
}
}
@@ -108,15 +108,15 @@ class CurrenciesAdapter : ListAdapter
is CurrencyListItem.BlockchainListItem -> {
element.blockchain.currency.toLowerCase(Locale.US)
.contains(queryNormalized) ||
- element.blockchain.fullName.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)
+ element.token.symbol.toLowerCase(Locale.US)
+ .contains(queryNormalized)
}
is CurrencyListItem.TitleListItem -> true
}
@@ -128,9 +128,9 @@ class CurrenciesAdapter : ListAdapter
}
class CurrenciesViewHolder(
- private val view: View,
+ private val binding: ItemPopularTokenBinding,
private val onItemAddListener: ((CurrencyListItem) -> Unit),
- ) : RecyclerView.ViewHolder(view) {
+ ) : RecyclerView.ViewHolder(binding.root) {
fun bind(
currency: CurrencyListItem,
@@ -140,12 +140,14 @@ class CurrenciesAdapter : ListAdapter
when (currency) {
is CurrencyListItem.BlockchainListItem -> {
val addedBlockchains = addedCurrencies?.blockchains ?: listOf()
- val currentlyAdded = currentlyAddedItems.filterIsInstance()
+ val currentlyAdded =
+ currentlyAddedItems.filterIsInstance()
bindBlockchain(currency, addedBlockchains, currentlyAdded)
}
is CurrencyListItem.TokenListItem -> {
val addedTokens = addedCurrencies?.tokens ?: listOf()
- val currentlyAdded = currentlyAddedItems.filterIsInstance()
+ val currentlyAdded =
+ currentlyAddedItems.filterIsInstance()
bindToken(currency, addedTokens, currentlyAdded)
}
}
@@ -155,13 +157,13 @@ class CurrenciesAdapter : ListAdapter
currency: CurrencyListItem.BlockchainListItem,
addedBlockchains: List,
currentlyAdded: List
- ) {
+ ) = with(binding) {
val blockchain = currency.blockchain
- Picasso.get().loadCurrenciesIcon(view.iv_currency, view.tv_token_letter, null, blockchain)
+ Picasso.get().loadCurrenciesIcon(ivCurrency, tvTokenLetter, null, blockchain)
- view.tv_currency_name.text = blockchain.fullName
- view.tv_currency_symbol.text = blockchain.currency
- view.btn_add_token.setOnClickListener {
+ tvCurrencyName.text = blockchain.fullName
+ tvCurrencySymbol.text = blockchain.currency
+ btnAddToken.setOnClickListener {
onItemAddListener.invoke(currency)
currency.isAdded = true
modifyAddTokenButton(currency)
@@ -177,16 +179,17 @@ class CurrenciesAdapter : ListAdapter
currency: CurrencyListItem.TokenListItem,
addedTokens: List,
currentlyAdded: List
- ) {
+ ) = with(binding) {
val token = currency.token
- Picasso.get().loadCurrenciesIcon(view.iv_currency, view.tv_token_letter, token, token.blockchain)
+ Picasso.get()
+ .loadCurrenciesIcon(ivCurrency, tvTokenLetter, token, token.blockchain)
- view.tv_currency_name.text = token.name
- view.tv_currency_symbol.text = view.context.getString(
+ tvCurrencyName.text = token.name
+ tvCurrencySymbol.text = tvCurrencySymbol.getString(
R.string.token_symbol_address_format,
token.symbol, token.contractAddress
)
- view.btn_add_token.setOnClickListener {
+ btnAddToken.setOnClickListener {
onItemAddListener.invoke(currency)
currency.isAdded = true
modifyAddTokenButton(currency)
@@ -199,48 +202,52 @@ class CurrenciesAdapter : ListAdapter
modifyAddTokenButton(currency)
}
- private fun modifyAddTokenButton(currency: CurrencyListItem) {
+ private fun modifyAddTokenButton(currency: CurrencyListItem) = with(binding) {
if (currency.isLocked) {
- view.btn_add_token.setText(R.string.common_add)
- view.btn_add_token.isEnabled = false
+ btnAddToken.setText(R.string.common_add)
+ btnAddToken.isEnabled = false
} else {
val text = if (currency.isAdded) R.string.add_token_added else R.string.common_add
- view.btn_add_token.setText(text)
- view.btn_add_token.isEnabled = !currency.isAdded
+ btnAddToken.setText(text)
+ btnAddToken.isEnabled = !currency.isAdded
}
}
}
- class TitleViewHolder(val view: View) :
- RecyclerView.ViewHolder(view) {
- fun bind(title: CurrencyListItem.TitleListItem) {
- view.tv_subtitle.text = view.getString(title.titleResId).uppercase()
+ class TitleViewHolder(val binding: ItemCurrencySubtitleBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ fun bind(title: CurrencyListItem.TitleListItem) = with(binding) {
if (title.blockchain != null) {
- view.cl_subtitle_container.setOnClickListener {
+ val subtitle = root.getString(title.titleResId, title.blockchain.getTokensName())
+ tvSubtitle.text = subtitle.uppercase()
+ clSubtitleContainer.setOnClickListener {
val rotation = if (title.isContentShown) -90f else 90f
- store.dispatch(TokensAction.ToggleShowTokensForBlockchain(
- title.isContentShown, title.blockchain
- ))
- view.iv_toggle_sublist_visibility.animate().rotation(rotation).withEndAction {
- showArrow(view, title.isContentShown)
+ store.dispatch(
+ TokensAction.ToggleShowTokensForBlockchain(
+ title.isContentShown, title.blockchain
+ )
+ )
+ ivToggleSublistVisibility.animate().rotation(rotation).withEndAction {
+ showArrow(title.isContentShown)
}
}
- showArrow(view, title.isContentShown)
+ showArrow(title.isContentShown)
} else {
- view.iv_toggle_sublist_visibility.hide()
+ tvSubtitle.text = root.getString(title.titleResId).uppercase()
+ ivToggleSublistVisibility.hide()
}
}
- private fun showArrow(view: View, isContentShown: Boolean) {
- view.iv_toggle_sublist_visibility.show()
+ private fun showArrow(isContentShown: Boolean) = with(binding) {
+ ivToggleSublistVisibility.show()
val drawable = if (isContentShown) {
R.drawable.ic_arrow_angle_down
} else {
R.drawable.ic_arrow_angle_right
}
- view.iv_toggle_sublist_visibility.setImageResource(drawable)
- view.iv_toggle_sublist_visibility.rotation = 0f
+ ivToggleSublistVisibility.setImageResource(drawable)
+ ivToggleSublistVisibility.rotation = 0f
}
}
}
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 a449bb17ea..722b2c7d03 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
@@ -6,10 +6,12 @@ import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.Wallet
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.domain.extensions.toSendableAmounts
+import java.math.BigDecimal
data class PendingTransaction(
val address: String?,
- val amount: String?,
+ val amount: BigDecimal?,
+ val amountUi: String?,
val currency: String,
val type: PendingTransactionType
)
@@ -38,6 +40,7 @@ fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransact
return PendingTransaction(
if (address == "unknown") null else address,
+ this.amount.value,
this.amount.value?.toFormattedString(amount.decimals),
this.amount.currencySymbol,
type
@@ -61,8 +64,12 @@ fun List.toPendingTransactionsForToken(token: Token, walletAddr
return this.mapNotNull { it.toPendingTransactionForToken(token, walletAddress) }
}
-fun Wallet.getPendingTransactions(): List {
- return recentTransactions.toPendingTransactions(address)
+fun Wallet.getPendingTransactions(type: PendingTransactionType? = null): List {
+ val txs = recentTransactions.toPendingTransactions(address)
+ return when(type) {
+ null -> txs
+ else -> txs.filter { it.type == type }
+ }
}
fun Wallet.hasPendingTransactions(): Boolean {
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 029b6b88b5..57a03d4313 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
@@ -9,7 +9,6 @@ import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.CardCurrencies
-import com.tangem.tap.network.moonpay.MoonpayStatus
import com.tangem.wallet.R
import org.rekotlin.Action
import java.math.BigDecimal
@@ -25,11 +24,7 @@ sealed class WalletAction : Action {
}
- data class LoadWallet(
- val moonpayStatus: MoonpayStatus? = null,
- val blockchain: Blockchain? = null,
- ) :
- WalletAction() {
+ data class LoadWallet(val blockchain: Blockchain? = null) : WalletAction() {
data class Success(val wallet: Wallet) : WalletAction()
data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : WalletAction()
data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction()
@@ -146,5 +141,5 @@ sealed class WalletAction : Action {
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
- data class SetWalletRent(val blockchain: Blockchain, val minRent: String, val rentExempt: String): WalletAction()
+ data class SetWalletRent(val blockchain: Blockchain, val minRent: String, val rentExempt: String) : WalletAction()
}
\ 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 4dd980e98c..92491013ad 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
@@ -19,7 +19,7 @@ import com.tangem.tap.features.wallet.models.toPendingTransactions
import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
-import com.tangem.tap.network.moonpay.MoonpayStatus
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.store
import org.rekotlin.StateType
import java.math.BigDecimal
@@ -160,12 +160,18 @@ data class WalletState(
return updatedWallets + remainingWallets
}
- fun updateTradeCryptoState(moonpayStatus: MoonpayStatus?, walletData: WalletData): WalletData {
- return walletData.copy(tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletData))
+ fun updateTradeCryptoState(
+ exchangeManager: CurrencyExchangeManager?,
+ walletData: WalletData
+ ): WalletData {
+ return walletData.copy(tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData))
}
- fun updateTradeCryptoState(moonpayStatus: MoonpayStatus?, walletDataList: List): List {
- return walletDataList.map { it.copy(tradeCryptoState = TradeCryptoState.from(moonpayStatus, it)) }
+ fun updateTradeCryptoState(
+ exchangeManager: CurrencyExchangeManager?,
+ walletDataList: List
+ ): List {
+ return walletDataList.map { it.copy(tradeCryptoState = TradeCryptoState.from(exchangeManager, it)) }
}
fun addWalletManagers(newWalletManagers: List): WalletState {
@@ -226,8 +232,8 @@ data class TradeCryptoState(
val buyingAllowed: Boolean = false,
) {
companion object {
- fun from(moonpayStatus: MoonpayStatus?, walletData: WalletData): TradeCryptoState {
- val status = moonpayStatus ?: return walletData.tradeCryptoState
+ fun from(exchangeManager: CurrencyExchangeManager?, walletData: WalletData): TradeCryptoState {
+ val status = exchangeManager ?: return walletData.tradeCryptoState
val currency = walletData.currency
return TradeCryptoState(status.sellIsAllowed(currency), status.buyIsAllowed(currency))
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
index df4c9120f7..19197ad893 100644
--- 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
@@ -1,18 +1,18 @@
package com.tangem.tap.features.wallet.redux.middlewares
-import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.*
import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.safeUpdate
-import com.tangem.tap.common.extensions.stripZeroPlainString
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.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
+import com.tangem.tap.features.demo.DemoHelper
+import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
@@ -22,6 +22,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import java.math.BigDecimal
class MultiWalletMiddleware {
fun handle(
@@ -33,7 +34,9 @@ class MultiWalletMiddleware {
when (action) {
is WalletAction.MultiWallet.AddWalletManagers -> {
globalState.feedbackManager?.infoHolder?.setWalletsInfo(action.walletManagers)
- action.walletManagers.forEach { checkForRentWarning(it) }
+ if (globalState.scanResponse?.isDemoCard() == true) {
+ addDummyBalances(action.walletManagers)
+ }
}
is WalletAction.MultiWallet.SelectWallet -> {
if (action.walletData != null) {
@@ -60,10 +63,7 @@ class MultiWalletMiddleware {
}
}
store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Blockchain(action.blockchain)))
- store.dispatch(WalletAction.LoadWallet(
- moonpayStatus = globalState.moonpayStatus,
- blockchain = action.blockchain
- )
+ store.dispatch(WalletAction.LoadWallet(action.blockchain)
)
}
is WalletAction.MultiWallet.SaveCurrencies -> {
@@ -99,28 +99,25 @@ class MultiWalletMiddleware {
scope.launch {
walletManagers.map { walletManager ->
async(Dispatchers.IO) {
- try {
- walletManager.update()
- val wallet = walletManager.wallet
- val coinAmount = wallet.amounts[AmountType.Coin]?.value
- if (coinAmount != null && !coinAmount.isZero()) {
- scope.launch(Dispatchers.Main) {
- if (walletState?.getWalletData(wallet.blockchain) == null) {
- store.dispatch(
- WalletAction.MultiWallet.AddWalletManagers(
- listOfNotNull(walletManager)
- )
+ walletManager.safeUpdate()
+ val wallet = walletManager.wallet
+ val coinAmount = wallet.amounts[AmountType.Coin]?.value
+ if (coinAmount != null && !coinAmount.isZero()) {
+ scope.launch(Dispatchers.Main) {
+ if (walletState?.getWalletData(wallet.blockchain) == null) {
+ store.dispatch(
+ WalletAction.MultiWallet.AddWalletManagers(
+ listOfNotNull(walletManager)
)
- store.dispatch(
- WalletAction.MultiWallet.AddBlockchain(
- wallet.blockchain
- )
+ )
+ store.dispatch(
+ WalletAction.MultiWallet.AddBlockchain(
+ wallet.blockchain
)
- store.dispatch(WalletAction.LoadWallet.Success(wallet))
- }
+ )
+ store.dispatch(WalletAction.LoadWallet.Success(wallet))
}
}
- } catch (exception: Exception) {
}
}
}
@@ -167,6 +164,14 @@ class MultiWalletMiddleware {
}
}
+ private fun addDummyBalances(walletManagers: List) {
+ walletManagers.forEach {
+ if (it.wallet.fundsAvailable(AmountType.Coin) == BigDecimal.ZERO) {
+ DemoHelper.injectDemoBalance(it)
+ }
+ }
+ }
+
private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) {
val scanResponse = globalState?.scanResponse ?: return
@@ -200,26 +205,6 @@ class MultiWalletMiddleware {
}
}
- private fun checkForRentWarning(walletManager: WalletManager) {
- val rentProvider = walletManager as? RentProvider ?: return
-
- scope.launch {
- when (val result = rentProvider.minimalBalanceForRentExemption()) {
- is Result.Success -> {
- val currency = walletManager.wallet.blockchain.currency
- val minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency")
- val rentExempt = ("${result.data.stripZeroPlainString()} $currency")
- store.dispatchOnMain(WalletAction.SetWalletRent(
- blockchain = walletManager.wallet.blockchain,
- minRent = minRent,
- rentExempt = rentExempt
- ))
- }
- is Result.Failure -> {}
- }
- }
- }
-
private fun addTokens(
tokens: List,
walletState: WalletState?,
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
index 2cd5627545..3667a06c35 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
@@ -3,22 +3,25 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.AmountType
import com.tangem.tap.common.extensions.dispatchOnMain
+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.topup.TopUpManager
-import com.tangem.tap.domain.topup.TradeCryptoHelper
+import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
+import com.tangem.tap.network.exchangeServices.buyErc20Tokens
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch
-import timber.log.Timber
class TradeCryptoMiddleware {
- fun handle(action: WalletAction.TradeCryptoAction) {
+ fun handle(state: () -> AppState?, action: WalletAction.TradeCryptoAction) {
+ if (DemoHelper.tryHandle(state, action)) return
+
when (action) {
is WalletAction.TradeCryptoAction.Buy -> startExchange(action)
is WalletAction.TradeCryptoAction.Sell -> startExchange(action)
@@ -29,44 +32,38 @@ class TradeCryptoMiddleware {
private fun startExchange(action: WalletAction.TradeCryptoAction) {
val selectedWalletData = store.state.walletState.getSelectedWalletData()
- val config = store.state.globalState.configManager?.config ?: return
+ val exchangeManager = store.state.globalState.currencyExchangeManager ?: return
val addresses = selectedWalletData?.walletAddresses ?: return
if (addresses.list.isEmpty()) return
+ val appCurrency = store.state.globalState.appCurrency
val defaultAddress = addresses.list[0].address
val currency = selectedWalletData.currency
- val currencySymbol = selectedWalletData.currency?.currencySymbol ?: return
+ val currencySymbol = selectedWalletData.currency.currencySymbol
val exchangeAction = if (action is WalletAction.TradeCryptoAction.Buy) {
- TradeCryptoHelper.Action.Buy
+ CurrencyExchangeManager.Action.Buy
} else {
- TradeCryptoHelper.Action.Sell
+ CurrencyExchangeManager.Action.Sell
}
- if (exchangeAction == TradeCryptoHelper.Action.Buy &&
+ if (exchangeAction == CurrencyExchangeManager.Action.Buy &&
currency is Currency.Token && currency.blockchain.isTestnet()
) {
val walletManager = store.state.walletState.getWalletManager(currency.token)
if (walletManager !is EthereumWalletManager) return
- scope.launch {
- TopUpManager().topUpTestErc20Tokens(
- walletManager = walletManager, token = currency.token
- )
- }
+ scope.launch { exchangeManager.buyErc20Tokens(walletManager, currency.token) }
return
}
- val url = TradeCryptoHelper.getUrl(
+ exchangeManager.getUrl(
action = exchangeAction,
- blockchain = currency?.blockchain,
+ blockchain = currency.blockchain,
cryptoCurrencyName = currencySymbol,
- walletAddress = defaultAddress,
- apiKey = config.moonPayApiKey,
- secretKey = config.moonPayApiSecretKey
- )
- Timber.d("Moonpay $exchangeAction URL: $url")
- store.dispatchOnMain(NavigationAction.OpenUrl(url))
+ fiatCurrency = appCurrency,
+ walletAddress = defaultAddress)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
+
}
private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) {
@@ -87,8 +84,11 @@ class TradeCryptoMiddleware {
}
private fun openReceiptUrl(transactionId: String) {
+ val exchangeManager = store.state.globalState.currencyExchangeManager ?: return
+
store.dispatchOnMain(NavigationAction.PopBackTo())
- val url = TradeCryptoHelper.getSellCryptoReceiptUrl(transactionId)
- store.dispatchOnMain(NavigationAction.OpenUrl(url))
+ exchangeManager.getSellCryptoReceiptUrl(CurrencyExchangeManager.Action.Sell, transactionId)?.let {
+ store.dispatchOnMain(NavigationAction.OpenUrl(it))
+ }
}
}
\ 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
index 37b71f1c2a..379008a18b 100644
--- 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
@@ -3,13 +3,16 @@ package com.tangem.tap.features.wallet.redux.middlewares
import android.content.Intent
import android.net.Uri
import androidx.core.content.ContextCompat
-import com.tangem.blockchain.common.*
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
+import com.tangem.operations.attestation.Attestation
import com.tangem.operations.attestation.OnlineCardVerifier
-import com.tangem.tap.*
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
@@ -18,10 +21,14 @@ import com.tangem.tap.common.redux.global.GlobalAction
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.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.network.NetworkStateChanged
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
@@ -37,18 +44,20 @@ class WalletMiddleware {
val walletMiddleware: Middleware = { dispatch, state ->
{ next ->
{ action ->
- handleAction(action, dispatch)
+ handleAction(state, action, dispatch)
next(action)
}
}
}
- private fun handleAction(action: Action, dispatch: DispatchFunction) {
+ private fun handleAction(state: () -> AppState?, action: Action, dispatch: DispatchFunction) {
+ if (DemoHelper.tryHandle(state, action)) return
+
val globalState = store.state.globalState
val walletState = store.state.walletState
when (action) {
- is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(action)
+ is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action)
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState)
is WalletAction.LoadWallet -> {
@@ -139,6 +148,9 @@ class WalletMiddleware {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
is WalletAction.LoadCardInfo -> {
+ val attestationFailed = action.card.attestation.status == Attestation.Status.Failed
+ store.dispatchOnMain(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
+
scope.launch {
val response = OnlineCardVerifier().getCardInfo(
action.card.cardId, action.card.cardPublicKey
@@ -148,13 +160,9 @@ class WalletMiddleware {
val actionList = listOf(
WalletAction.SetArtworkId(response.data.artwork?.id),
WalletAction.LoadArtwork(action.card, response.data.artwork?.id),
- GlobalAction.SetIfCardVerifiedOnline(response.data.passed)
)
withMainContext { actionList.forEach { store.dispatch(it) } }
}
- is Result.Failure -> {
- store.dispatchOnMain(GlobalAction.SetIfCardVerifiedOnline(false))
- }
}
store.dispatchOnMain(WalletAction.Warnings.CheckIfNeeded)
}
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
index 408f0ef8a3..e8ea067b6f 100644
--- 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
@@ -17,6 +17,7 @@ import com.tangem.tap.domain.extensions.hasSignedHashes
import com.tangem.tap.domain.extensions.remainingSignatures
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.tasks.product.ScanResponse
+import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.preferencesStorage
@@ -50,12 +51,12 @@ class WarningsMiddleware {
}
is WalletAction.Warnings.CheckRemainingSignatures -> {
if (action.remainingSignatures != null &&
- action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING
+ action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING
) {
store.state.globalState.warningManager
- ?.removeWarnings(
- messageRes = R.string.warning_low_signatures_format
- )
+ ?.removeWarnings(
+ messageRes = R.string.warning_low_signatures_format
+ )
addWarningMessage(
warning =
WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures),
@@ -99,6 +100,9 @@ class WarningsMiddleware {
addWarningMessage(WarningMessagesManager.onlineVerificationFailed())
}
}
+ if (scanResponse.isDemoCard()) {
+ addWarningMessage(WarningMessagesManager.demoCardWarning())
+ }
setWarningMessages()
}
}
@@ -106,7 +110,7 @@ class WarningsMiddleware {
private fun showWarningLowRemainingSignaturesIfNeeded(card: Card) {
val remainingSignatures = card.remainingSignatures
if (remainingSignatures != null &&
- remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING
+ remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING
) {
addWarningMessage(
WarningMessagesManager.remainingSignaturesNotEnough(
@@ -119,7 +123,7 @@ class WarningsMiddleware {
private fun checkIfWarningNeeded(
scanResponse: ScanResponse,
): WarningMessage? {
- if (scanResponse.isTangemTwins()) return null
+ if (scanResponse.isTangemTwins() || scanResponse.isDemoCard()) return null
if (scanResponse.card.isMultiwalletAllowed) {
return if (scanResponse.card.hasSignedHashes()) {
@@ -131,7 +135,7 @@ class WarningsMiddleware {
}
val validator = store.state.walletState.walletManagers.firstOrNull()
- as? SignatureCountValidator
+ as? SignatureCountValidator
return if (validator == null) {
if (scanResponse.card.hasSignedHashes()) {
WarningMessagesManager.alreadySignedHashesWarning()
@@ -156,7 +160,7 @@ class WarningsMiddleware {
if (scanResponse.isTangemTwins() || card.isMultiwalletAllowed) return
val validator = store.state.walletState.walletManagers.firstOrNull()
- as? SignatureCountValidator
+ as? SignatureCountValidator
scope.launch {
val signedHashes = card.getSingleWallet()?.totalSignedHashes ?: 0
val result = validator?.validateSignatureCount(signedHashes)
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
index 1cad2a84c4..c28bea2b5b 100644
--- 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
@@ -29,7 +29,7 @@ class OnWalletLoadedReducer {
private fun onMultiWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState {
val fiatCurrencySymbol = store.state.globalState.appCurrency
- val moonpayStatus = store.state.globalState.moonpayStatus
+ val exchangeManager = store.state.globalState.currencyExchangeManager
val coinAmountValue = wallet.amounts[AmountType.Coin]?.value
if (walletState.getWalletData(wallet.blockchain) == null) {
@@ -62,7 +62,7 @@ class OnWalletLoadedReducer {
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(coinSendButton),
currency = Currency.Blockchain(wallet.blockchain),
- tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletData)
+ tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData)
)
val tokens = wallet.getTokens().mapNotNull { token ->
@@ -86,7 +86,7 @@ class OnWalletLoadedReducer {
),
pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(tokenSendButton),
- tradeCryptoState = TradeCryptoState.from(moonpayStatus, tokenWalletData)
+ tradeCryptoState = TradeCryptoState.from(exchangeManager, tokenWalletData)
)
}
val newWallets = (tokens + newWalletData).mapNotNull { it }
@@ -106,7 +106,7 @@ class OnWalletLoadedReducer {
if (wallet.blockchain != walletState.primaryBlockchain) return walletState
val fiatCurrencySymbol = store.state.globalState.appCurrency
- val moonpayStatus = store.state.globalState.moonpayStatus
+ val exchangeManager = store.state.globalState.currencyExchangeManager
val token = wallet.getFirstToken()
val tokenData = if (token != null) {
@@ -154,7 +154,7 @@ class OnWalletLoadedReducer {
),
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(sendButtonEnabled),
- tradeCryptoState = TradeCryptoState.from(moonpayStatus, walletState.primaryWallet)
+ tradeCryptoState = TradeCryptoState.from(exchangeManager, walletState.primaryWallet)
)
val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
return walletState.copy(
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
index 7005235044..af75afcb1b 100644
--- 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
@@ -34,7 +34,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
if (action !is WalletAction) return state.walletState
- val moonpayStatus = store.state.globalState.moonpayStatus
+ val exchangeManager = store.state.globalState.currencyExchangeManager
var newState = state.walletState
when (action) {
@@ -103,7 +103,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
currencySymbol = wallet.currencyData.currencySymbol,
),
mainButton = WalletMainButton.SendButton(false),
- tradeCryptoState = TradeCryptoState.from(action.moonpayStatus, wallet)
+ tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet)
)
}
newState = newState.copy(
@@ -124,11 +124,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
currencySymbol = wallet.currencyData.currencySymbol,
),
mainButton = WalletMainButton.SendButton(false),
- tradeCryptoState = TradeCryptoState.from(action.moonpayStatus, wallet)
+ tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet)
)
}
val wallets = newState.replaceSomeWallets(newWallets)
- newState = newState.copy(wallets = newState.updateTradeCryptoState(moonpayStatus, wallets))
+ newState = newState.copy(wallets = newState.updateTradeCryptoState(exchangeManager, wallets))
}
}
is WalletAction.LoadWallet.Success -> newState =
@@ -153,7 +153,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
}
newState = newState.copy(
state = progressState,
- wallets = newState.updateTradeCryptoState(moonpayStatus, wallets)
+ wallets = newState.updateTradeCryptoState(exchangeManager, wallets)
)
}
is WalletAction.LoadWallet.Failure -> {
@@ -188,7 +188,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
}
newState = newState.copy(
state = progressState,
- wallets = newState.updateTradeCryptoState(moonpayStatus, wallets)
+ wallets = newState.updateTradeCryptoState(exchangeManager, wallets)
)
}
is WalletAction.SetArtworkId -> {
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 2992b2d543..42527e8528 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
@@ -1,13 +1,10 @@
package com.tangem.tap.features.wallet.ui
import androidx.annotation.IdRes
-import androidx.fragment.app.Fragment
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
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 com.tangem.wallet.databinding.CardBalanceBinding
import java.math.BigDecimal
enum class BalanceStatus {
@@ -34,30 +31,34 @@ data class BalanceWidgetData(
)
data class TokenData(
- val amount: String,
- val tokenSymbol: String,
- val fiatAmount: String? = null,
- val fiatRateString: String? = null,
- val fiatRate: BigDecimal? = null,
+ val amount: String,
+ val tokenSymbol: String,
+ val fiatAmount: String? = null,
+ val fiatRateString: String? = null,
+ val fiatRate: BigDecimal? = null,
)
class BalanceWidget(
- val fragment: Fragment,
- val data: BalanceWidgetData,
- private val isTwinCard: Boolean,
+ private val binding: CardBalanceBinding,
+ private val fragment: WalletFragment,
+ private val data: BalanceWidgetData,
+ private val isTwinCard: Boolean,
) {
fun setup() {
when (data.status) {
BalanceStatus.Loading -> {
- fragment.l_balance.show()
- fragment.l_balance_error.hide()
- fragment.tv_fiat_amount.hide()
+ with(binding) {
+ lBalance.root.show()
+ lBalanceError.root.hide()
+ lBalance.tvFiatAmount.hide()
+
+ lBalance.tvCurrency.text = data.currency
+ lBalance.tvAmount.text = ""
+ }
- fragment.tv_currency.text = data.currency
- fragment.tv_amount.text = ""
showStatus(R.id.tv_status_loading)
@@ -67,18 +68,18 @@ class BalanceWidget(
showBalanceWithoutToken(data, false)
}
}
- BalanceStatus.VerifiedOnline, BalanceStatus.TransactionInProgress -> {
- fragment.l_balance.show()
- fragment.l_balance_error.hide()
+ BalanceStatus.VerifiedOnline, BalanceStatus.TransactionInProgress -> with(binding.lBalance) {
+ root.show()
+ binding.lBalanceError.root.hide()
val statusView = if (data.status == BalanceStatus.VerifiedOnline) {
R.id.tv_status_verified
} else {
- fragment.tv_status_error.text =
- fragment.getText(R.string.wallet_balance_tx_in_progress)
+ tvStatusError.text =
+ fragment.getText(R.string.wallet_balance_tx_in_progress)
R.id.group_error
}
showStatus(statusView)
- fragment.tv_status_error_message.hide()
+ tvStatusErrorMessage.hide()
if (data.token != null) {
showBalanceWithToken(data, true)
@@ -86,79 +87,83 @@ class BalanceWidget(
showBalanceWithoutToken(data, true)
}
}
- BalanceStatus.Unreachable -> {
- fragment.l_balance.show()
- fragment.l_balance_error.hide()
- fragment.tv_fiat_amount.hide()
- fragment.group_base_currency.hide()
+ BalanceStatus.Unreachable -> with(binding.lBalance) {
+ root.show()
+ binding.lBalanceError.root.hide()
+ tvFiatAmount.hide()
+ groupBaseCurrency.hide()
val currency = if (data.token != null) data.token.tokenSymbol else data.currency
- fragment.tv_currency.text = currency
- fragment.tv_amount.text = ""
+ tvCurrency.text = currency
+ tvAmount.text = ""
- fragment.tv_status_error_message.text = data.errorMessage
- fragment.tv_status_error.text =
- fragment.getString(R.string.wallet_balance_blockchain_unreachable)
+ tvStatusErrorMessage.text = data.errorMessage
+ tvStatusError.text =
+ fragment.getString(R.string.wallet_balance_blockchain_unreachable)
showStatus(R.id.group_error)
- fragment.tv_status_error_message.show(!data.errorMessage.isNullOrBlank())
+ tvStatusErrorMessage.show(!data.errorMessage.isNullOrBlank())
}
- BalanceStatus.EmptyCard -> {
- fragment.l_balance.hide()
- fragment.l_balance_error.show()
+ BalanceStatus.EmptyCard -> with(binding.lBalanceError) {
+ binding.lBalance.root.hide()
+ binding.lBalanceError.root.show()
if (isTwinCard) {
- fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_empty_twin_card)
- fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_error_empty_twin_card_subtitle)
+ tvErrorTitle.text = fragment.getText(R.string.wallet_error_empty_twin_card)
+ tvErrorDescriptions.text =
+ fragment.getText(R.string.wallet_error_empty_twin_card_subtitle)
} else {
- fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_empty_card)
- fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_error_empty_card_subtitle)
+ tvErrorTitle.text = fragment.getText(R.string.wallet_error_empty_card)
+ tvErrorDescriptions.text =
+ fragment.getText(R.string.wallet_error_empty_card_subtitle)
}
}
- BalanceStatus.NoAccount -> {
- fragment.l_balance.hide()
- fragment.l_balance_error.show()
- fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_no_account)
- fragment.tv_error_descriptions.text =
- fragment.getString(
- R.string.wallet_error_no_account_subtitle_format,
- data.amountToCreateAccount, data.currencySymbol
- )
+ BalanceStatus.NoAccount -> with(binding.lBalanceError) {
+ binding.lBalance.root.hide()
+ binding.lBalanceError.root.show()
+ tvErrorTitle.text = fragment.getText(R.string.wallet_error_no_account)
+ tvErrorDescriptions.text =
+ fragment.getString(
+ R.string.wallet_error_no_account_subtitle_format,
+ data.amountToCreateAccount, data.currencySymbol
+ )
}
- BalanceStatus.UnknownBlockchain -> {
- fragment.l_balance.hide()
- fragment.l_balance_error.show()
- fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_unsupported_blockchain)
- fragment.tv_error_descriptions.text =
- fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
+ BalanceStatus.UnknownBlockchain -> with(binding.lBalanceError) {
+ binding.lBalance.root.hide()
+ binding.lBalanceError.root.show()
+ tvErrorTitle.text =
+ fragment.getText(R.string.wallet_error_unsupported_blockchain)
+ tvErrorDescriptions.text =
+ fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
}
}
}
- private fun showStatus(@IdRes viewRes: Int) {
- fragment.group_error.show(viewRes == R.id.group_error)
- fragment.tv_status_loading.show(viewRes == R.id.tv_status_loading)
- fragment.tv_status_verified.show(viewRes == R.id.tv_status_verified)
+ private fun showStatus(@IdRes viewRes: Int) = with(binding.lBalance) {
+ groupError.show(viewRes == R.id.group_error)
+ tvStatusLoading.show(viewRes == R.id.tv_status_loading)
+ tvStatusVerified.show(viewRes == R.id.tv_status_verified)
}
- private fun showBalanceWithToken(data: BalanceWidgetData, showAmount: Boolean) {
- fragment.group_base_currency.show()
- fragment.tv_currency.text = data.token?.tokenSymbol
- fragment.tv_base_currency.text = data.currency
- fragment.tv_amount.text = if (showAmount) data.token?.amount else ""
- fragment.tv_base_amount.text = if (showAmount) data.amount else ""
+ private fun showBalanceWithToken(data: BalanceWidgetData, showAmount: Boolean) = with(binding.lBalance) {
+ groupBaseCurrency.show()
+ tvCurrency.text = data.token?.tokenSymbol
+ tvBaseCurrency.text = data.currency
+ tvAmount.text = if (showAmount) data.token?.amount else ""
+ tvBaseAmount.text = if (showAmount) data.amount else ""
if (showAmount) {
- fragment.tv_fiat_amount.show()
- fragment.tv_fiat_amount.text = data.token?.fiatAmount
+ tvFiatAmount.show()
+ tvFiatAmount.text = data.token?.fiatAmount
}
}
- private fun showBalanceWithoutToken(data: BalanceWidgetData, showAmount: Boolean) {
- fragment.group_base_currency.hide()
- fragment.tv_currency.text = data.currency
- fragment.tv_amount.text = if (showAmount) data.amount else ""
- if (showAmount) {
- fragment.tv_fiat_amount.show()
- fragment.tv_fiat_amount.text = data.fiatAmountFormatted
+ private fun showBalanceWithoutToken(data: BalanceWidgetData, showAmount: Boolean) =
+ with(binding.lBalance) {
+ groupBaseCurrency.hide()
+ tvCurrency.text = data.currency
+ tvAmount.text = if (showAmount) data.amount else ""
+ if (showAmount) {
+ tvFiatAmount.show()
+ tvFiatAmount.text = data.fiatAmountFormatted
+ }
}
- }
}
\ 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
index bfd6057d73..57d4b733de 100644
--- 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
@@ -3,12 +3,15 @@ package com.tangem.tap.features.wallet.ui
import android.app.Dialog
import android.os.Bundle
import android.view.*
+import android.widget.TextView
import androidx.activity.OnBackPressedCallback
-import androidx.annotation.IdRes
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
+import by.kirich1409.viewbindingdelegate.viewBinding
import com.squareup.picasso.Picasso
import com.tangem.common.extensions.guard
import com.tangem.tap.common.SnackbarHandler
@@ -22,21 +25,17 @@ 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_wallet_details.*
-import kotlinx.android.synthetic.main.item_currency_wallet.view.*
-import kotlinx.android.synthetic.main.item_popular_token.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 kotlinx.android.synthetic.main.layout_wallet_details.card_balance
-import kotlinx.android.synthetic.main.layout_wallet_details_rent.*
+import com.tangem.wallet.databinding.FragmentWalletDetailsBinding
import org.rekotlin.StoreSubscriber
-class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreSubscriber {
+class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
+ StoreSubscriber {
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
private var dialog: Dialog? = null
+ private val binding: FragmentWalletDetailsBinding by viewBinding(FragmentWalletDetailsBinding::bind)
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
@@ -67,8 +66,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- (activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
- toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
+ (activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
+ binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
setupTransactionsRecyclerView()
@@ -76,91 +75,98 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
}
- private fun setupTransactionsRecyclerView() {
+ private fun setupTransactionsRecyclerView() = with(binding) {
pendingTransactionAdapter = PendingTransactionsAdapter()
- rv_pending_transaction.layoutManager = LinearLayoutManager(requireContext())
- rv_pending_transaction.adapter = pendingTransactionAdapter
+ rvPendingTransaction.layoutManager = LinearLayoutManager(requireContext())
+ rvPendingTransaction.adapter = pendingTransactionAdapter
}
- private fun setupButtons() {
- btn_confirm.text = getString(R.string.wallet_button_send)
- btn_confirm.setOnClickListener { store.dispatch(WalletAction.Send()) }
+ private fun setupButtons() = with(binding) {
+ btnConfirm.text = getString(R.string.wallet_button_send)
+ btnConfirm.setOnClickListener { store.dispatch(WalletAction.Send()) }
- btn_share.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
+ binding.lWalletDetails.btnShare.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
- btn_trade.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Buy) }
+ btnTrade.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Buy) }
- btn_sell.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
+ btnSell.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
}
override fun newState(state: WalletState) {
- if (activity == null) return
+ if (activity == null || view == null) return
if (state.selectedWallet == null) return
val selectedWallet = state.getSelectedWalletData() ?: return
- tv_currency_title.text = selectedWallet.currencyData.currency
- val currency = selectedWallet.currency
- if (currency is Currency.Token) {
- tv_currency_subtitle.text = currency.blockchain.tokenDisplayName()
- tv_currency_subtitle.show()
- } else {
- tv_currency_subtitle.hide()
- }
-
showPendingTransactionsIfPresent(selectedWallet.pendingTransactions)
+ setupCurrency(selectedWallet.currencyData, selectedWallet.currency)
setupAddressCard(selectedWallet)
setupNoInternetHandling(state)
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()))
- }
- }
+ setupButtons(selectedWallet)
handleDialogs(state.walletDialog)
handleCurrencyIcon(selectedWallet)
handleWalletRent(selectedWallet.walletRent)
- srl_wallet_details.setOnRefreshListener {
+
+ binding.srlWalletDetails.setOnRefreshListener {
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
- store.dispatch(WalletAction.LoadWallet(
- blockchain = selectedWallet.currency.blockchain
- ))
+ store.dispatch(
+ WalletAction.LoadWallet(
+ blockchain = selectedWallet.currency.blockchain
+ )
+ )
}
}
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
- srl_wallet_details.isRefreshing = false
+ binding.srlWalletDetails.isRefreshing = false
}
-
- btn_trade.isEnabled = selectedWallet.tradeCryptoState.buyingAllowed
- btn_sell.show(selectedWallet.tradeCryptoState.sellingAllowed)
}
- private fun handleWalletRent(rent: WalletRent?) {
+ private fun setupCurrency(currencyData: BalanceWidgetData, currency: Currency) = with(binding) {
+ tvCurrencyTitle.text = currencyData.currency
+ if (currency is Currency.Token) {
+ binding.tvCurrencySubtitle.text = currency.blockchain.tokenDisplayName()
+ binding.tvCurrencySubtitle.show()
+ } else {
+ binding.tvCurrencySubtitle.hide()
+ }
+ }
+
+ private fun setupButtons(selectedWallet: WalletData) = with(binding) {
+ btnConfirm.isEnabled = selectedWallet.mainButton.enabled
+ lWalletDetails.btnCopy.setOnClickListener {
+ selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
+ store.dispatch(WalletAction.CopyAddress(addressString, requireContext()))
+ }
+ }
+ lWalletDetails.btnShare.setOnClickListener {
+ selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
+ store.dispatch(WalletAction.ShareAddress(addressString, requireContext()))
+ }
+ }
+ btnTrade.isEnabled = selectedWallet.tradeCryptoState.buyingAllowed
+ btnSell.show(selectedWallet.tradeCryptoState.sellingAllowed)
+ }
+
+ private fun handleWalletRent(rent: WalletRent?) = with(binding) {
val rent = rent.guard {
- l_rent_warning.hide()
+ lRentWarning.root.hide()
return
}
-
val warningMessage = requireContext().getString(
- R.string.solana_rent_warning, rent.minRentValue, rent.rentExemptValue)
- tv_rent_warning_message.text = warningMessage
- l_rent_warning.show()
+ R.string.solana_rent_warning, rent.minRentValue, rent.rentExemptValue
+ )
+ lRentWarning.tvRentWarningMessage.text = warningMessage
+ lRentWarning.root.show()
}
- private fun handleCurrencyIcon(wallet: WalletData) {
+ private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) {
Picasso.get().loadCurrenciesIcon(
- imageView = iv_currency,
- textView = tv_token_letter,
+ imageView = ivCurrency,
+ textView = tvTokenLetter,
blockchain = wallet.currency.blockchain,
token = (wallet.currency as? Currency.Token)?.token
)
@@ -169,42 +175,48 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
private fun showPendingTransactionsIfPresent(pendingTransactions: List) {
pendingTransactionAdapter.submitList(pendingTransactions)
- rv_pending_transaction?.show(pendingTransactions.isNotEmpty())
+ binding.rvPendingTransaction?.show(pendingTransactions.isNotEmpty())
}
- private fun setupAddressCard(state: WalletData) {
+ private fun setupAddressCard(state: WalletData) = with(binding.lWalletDetails) {
if (state.walletAddresses != null) {
if (state.shouldShowMultipleAddress() && state.currency is Currency.Blockchain) {
- (card_balance as? ViewGroup)?.beginDelayedTransition()
- chip_group_address_type.show()
- chip_group_address_type.fitChipsByGroupWidth()
+ (cardBalance as? ViewGroup)?.beginDelayedTransition()
+ chipGroupAddressType.show()
+ chipGroupAddressType.fitChipsByGroupWidth()
- val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
- if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
+ val checkedId =
+ MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
+ if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
- chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
+ chipGroupAddressType.setOnCheckedChangeListener { group, checkedId ->
if (checkedId == -1) return@setOnCheckedChangeListener
- val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain)
+ val type =
+ MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain)
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
}
} else {
- chip_group_address_type.hide()
+ chipGroupAddressType.hide()
}
- tv_address.text = state.walletAddresses.selectedAddress.address
- tv_explore?.setOnClickListener {
- store.dispatch(WalletAction.ExploreAddress(
- state.walletAddresses.selectedAddress.exploreUrl,
- requireContext()))
+ tvAddress.text = state.walletAddresses.selectedAddress.address
+ tvExplore?.setOnClickListener {
+ store.dispatch(
+ WalletAction.ExploreAddress(
+ state.walletAddresses.selectedAddress.exploreUrl,
+ requireContext()
+ )
+ )
}
- iv_qr_code.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode())
- tv_recieve_message.text = getQRReceiveMessage(tv_recieve_message.context, state.currency)
+ ivQrCode.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode())
+ tvReceiveMessage.text =
+ getQRReceiveMessage(tvReceiveMessage.context, state.currency)
}
}
private fun setupNoInternetHandling(state: WalletState) {
if (state.state == ProgressState.Error) {
if (state.error == ErrorType.NoInternetConnection) {
- srl_wallet_details?.isRefreshing = false
+ binding.srlWalletDetails.isRefreshing = false
(activity as? SnackbarHandler)?.showSnackbar(
text = R.string.wallet_notification_no_internet,
buttonTitle = R.string.common_retry
@@ -215,74 +227,53 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
}
}
- private fun setupBalanceData(data: BalanceWidgetData) {
+ private fun setupBalanceData(data: BalanceWidgetData) = with(binding.lWalletDetails) {
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)
+ lBalanceError.root.hide()
+ lBalance.root.show()
+ lBalance.groupBalance.show()
+ lBalance.tvError.hide()
+ lBalance.tvAmount.text = data.amount
+ lBalance.tvFiatAmount.text = data.fiatAmountFormatted
+ lBalance.tvStatus.setLoadingStatus(R.string.wallet_balance_loading)
}
BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress,
BalanceStatus.TransactionInProgress -> {
- l_balance.show()
- l_balance_error.hide()
- val statusView = if (data.status == BalanceStatus.VerifiedOnline ||
- data.status == BalanceStatus.SameCurrencyTransactionInProgress) {
- R.id.tv_status_verified
- } else {
- tv_status_error.text =
- getText(R.string.wallet_balance_tx_in_progress)
- R.id.group_error
+ lBalanceError.root.hide()
+ lBalance.root.show()
+ lBalance.groupBalance.show()
+ lBalance.tvError.hide()
+ lBalance.tvAmount.text = data.amount
+ lBalance.tvFiatAmount.text = data.fiatAmountFormatted
+ when (data.status) {
+ BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress -> {
+ lBalance.tvStatus.setVerifiedBalanceStatus(R.string.wallet_balance_verified)
+ }
+ else -> {
+ lBalance.tvStatus.setWarningStatus(R.string.wallet_balance_tx_in_progress)
+ }
}
- 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())
+ lBalanceError.root.hide()
+ lBalance.root.show()
+ lBalance.groupBalance.hide()
+ lBalance.tvError.show()
+ lBalance.tvError.setWarningStatus(R.string.wallet_balance_blockchain_unreachable, data.errorMessage)
}
-
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
- )
+ lBalance.root.hide()
+ lBalanceError.root.show()
+ lBalanceError.tvErrorTitle.text = getText(R.string.wallet_error_no_account)
+ lBalanceError.tvErrorDescriptions.text =
+ getString(
+ R.string.wallet_error_no_account_subtitle_format,
+ data.amountToCreateAccount, data.currencySymbol
+ )
}
}
- card_pending_transaction_warning.show(data.status == BalanceStatus.SameCurrencyTransactionInProgress)
- }
-
- 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.fiatAmountFormatted
- }
+ binding.cardPendingTransactionWarning.show(data.status == BalanceStatus.SameCurrencyTransactionInProgress)
}
private fun handleDialogs(walletDialog: StateDialog?) {
@@ -323,4 +314,22 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
menu.getItem(0).isEnabled = walletCanBeRemoved
}
-}
+ private fun TextView.setWarningStatus(mainMessage: Int, error: String? = null) {
+ val text = getString(mainMessage).appendIfNotNull(error, "\nError: ")
+ setStatus(text, R.color.warning, R.drawable.ic_warning_small)
+ }
+
+ private fun TextView.setVerifiedBalanceStatus(mainMessage: Int) {
+ setStatus(getString(mainMessage), R.color.accent, R.drawable.ic_ok)
+ }
+
+ private fun TextView.setLoadingStatus(mainMessage: Int) {
+ setStatus(getString(mainMessage), R.color.darkGray4, null)
+ }
+
+ private fun TextView.setStatus(text: String, @ColorRes color: Int, @DrawableRes drawable: Int?) {
+ this.text = text
+ setTextColor(getColor(color))
+ setCompoundDrawablesWithIntrinsicBounds(drawable ?: 0, 0, 0, 0)
+ }
+}
\ No newline at end of file
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 f7978e35d8..9f6a876537 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
@@ -11,6 +11,7 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionInflater
+import by.kirich1409.viewbindingdelegate.viewBinding
import com.squareup.picasso.Picasso
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.MainActivity
@@ -29,9 +30,7 @@ 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.fragment_wallet.*
-import kotlinx.android.synthetic.main.fragment_wallet.toolbar
-import kotlinx.android.synthetic.main.fragment_wallet_details.*
+import com.tangem.wallet.databinding.FragmentWalletBinding
import org.rekotlin.StoreSubscriber
@@ -39,6 +38,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber) {
warningsAdapter.submitList(warnings)
- rv_warning_messages.show(warnings.isNotEmpty())
+ binding.rvWarningMessages.show(warnings.isNotEmpty())
}
private fun setupNoInternetHandling(state: WalletState) {
if (state.state == ProgressState.Error) {
if (state.error == ErrorType.NoInternetConnection) {
- srl_wallet_details?.isRefreshing = false
+ binding.srlWallet.isRefreshing = false
(activity as? MainActivity)?.showSnackbar(
text = R.string.wallet_notification_no_internet,
buttonTitle = R.string.common_retry
@@ -152,7 +155,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber(DiffUtilCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionsViewHolder {
- val layout = LayoutInflater.from(parent.context)
- .inflate(R.layout.item_pending_transaction, parent, false)
- return TransactionsViewHolder(layout)
+ val binding = ItemPendingTransactionBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
+ return TransactionsViewHolder(binding)
}
override fun onBindViewHolder(holder: TransactionsViewHolder, position: Int) {
@@ -36,13 +37,13 @@ class PendingTransactionsAdapter
) = oldItem == newItem
}
- class TransactionsViewHolder(val view: View) :
- RecyclerView.ViewHolder(view) {
+ class TransactionsViewHolder(val binding: ItemPendingTransactionBinding) :
+ RecyclerView.ViewHolder(binding.root) {
fun bind(transaction: PendingTransaction) {
if (transaction.type == PendingTransactionType.Unknown) {
- view.hide()
+ binding.root.hide()
}
val transactionDescriptionRes = when (transaction.type) {
@@ -60,17 +61,16 @@ class PendingTransactionsAdapter
PendingTransactionType.Outgoing -> R.drawable.ic_arrow_right_20
PendingTransactionType.Unknown -> return
}
- view.tv_pending_transaction.text = view.context.getString(transactionDescriptionRes)
+ binding.tvPendingTransaction.text = binding.root.getString(transactionDescriptionRes)
- transaction.amount?.let { view.tv_pending_transaction_amount.text = "$it " }
- view.tv_pending_transaction_currency.text = "${transaction.currency}"
+ transaction.amountUi?.let { binding.tvPendingTransactionAmount.text = "$it " }
+ binding.tvPendingTransactionCurrency.text = "${transaction.currency}"
if (transaction.address != null) {
- view.tv_pending_transaction_address.text =
- view.context.getString(transactionAddressRes, transaction.address)
+ binding.tvPendingTransactionAddress.text =
+ binding.root.getString(transactionAddressRes, transaction.address)
}
-
- view.iv_pending_transaction.setImageDrawable(view.context.getDrawableCompat(image))
+ binding.ivPendingTransaction.setImageDrawable(binding.root.context.getDrawableCompat(image))
}
}
}
\ No newline at end of file
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
index 4d96d05004..074d4a882b 100644
--- 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
@@ -1,7 +1,6 @@
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
@@ -9,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
+import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.loadCurrenciesIcon
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.wallet.redux.Currency
@@ -17,7 +17,7 @@ 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 com.tangem.wallet.databinding.ItemCurrencyWalletBinding
class WalletAdapter
: ListAdapter(DiffUtilCallback) {
@@ -32,8 +32,9 @@ class WalletAdapter
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder {
- val layout = LayoutInflater.from(parent.context)
- .inflate(R.layout.item_currency_wallet, parent, false)
+ val layout = ItemCurrencyWalletBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
return WalletsViewHolder(layout)
}
@@ -51,24 +52,24 @@ class WalletAdapter
) = oldItem == newItem
}
- class WalletsViewHolder(val view: View) :
- RecyclerView.ViewHolder(view) {
+ class WalletsViewHolder(val binding: ItemCurrencyWalletBinding) :
+ RecyclerView.ViewHolder(binding.root) {
- fun bind(wallet: WalletData) {
- view.tv_currency.text = wallet.currencyData.currency
- view.tv_amount.text = wallet.currencyData.amount?.takeWhile { !it.isWhitespace() }
- view.tv_currency_symbol.text = wallet.currencyData.amount?.takeLastWhile { !it.isWhitespace() }
- view.tv_amount_fiat.text = wallet.currencyData.fiatAmountFormatted
- view.tv_exchange_rate.text = wallet.fiatRateString
- view.card_wallet.setOnClickListener {
+ fun bind(wallet: WalletData) = with(binding) {
+ tvCurrency.text = wallet.currencyData.currency
+ tvAmount.text = wallet.currencyData.amount?.takeWhile { !it.isWhitespace() }
+ tvCurrencySymbol.text = wallet.currencyData.amount?.takeLastWhile { !it.isWhitespace() }
+ tvAmountFiat.text = wallet.currencyData.fiatAmountFormatted
+ tvExchangeRate.text = wallet.fiatRateString
+ cardWallet.setOnClickListener {
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
}
val blockchain = wallet.currency.blockchain
val token = (wallet.currency as? Currency.Token)?.token
Picasso.get().loadCurrenciesIcon(
- imageView = view.iv_currency,
- textView = view.tv_token_letter,
+ imageView = ivCurrency,
+ textView = tvTokenLetter,
token = token, blockchain = blockchain,
)
@@ -77,16 +78,16 @@ class WalletAdapter
BalanceStatus.Loading -> {
hideWarning()
if (wallet.currencyData.amount == null) {
- view.tv_exchange_rate.text = view.context.getText(R.string.wallet_balance_loading)
+ tvExchangeRate.text = root.getString(R.string.wallet_balance_loading)
}
}
BalanceStatus.TransactionInProgress ->
- showWarning(view.context.getString(R.string.wallet_balance_tx_in_progress))
+ showWarning(root.getString(R.string.wallet_balance_tx_in_progress))
BalanceStatus.Unreachable ->
- showWarning(view.context.getString(R.string.wallet_balance_blockchain_unreachable))
+ showWarning(root.getString(R.string.wallet_balance_blockchain_unreachable))
BalanceStatus.NoAccount ->
- showWarning(view.context.getString(R.string.wallet_error_no_account))
+ showWarning(root.getString(R.string.wallet_error_no_account))
else -> {
}
}
@@ -94,7 +95,7 @@ class WalletAdapter
private fun showWarning(message: String) {
toggleWarning(true)
- view.tv_status_error_message.text = message
+ binding.tvStatusErrorMessage.text = message
}
private fun hideWarning() {
@@ -102,8 +103,8 @@ class WalletAdapter
}
private fun toggleWarning(show: Boolean) {
- view.tv_exchange_rate.show(!show)
- view.tv_status_error_message.show(show)
+ binding.tvExchangeRate.show(!show)
+ binding.tvStatusErrorMessage.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
index 22c2d56cf3..afcba036da 100644
--- 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
@@ -10,25 +10,23 @@ 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.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.extensions.*
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 com.tangem.wallet.databinding.LayoutWarningBinding
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)
+ val binding = LayoutWarningBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
+ return WarningMessageVH(binding)
}
override fun onBindViewHolder(holder: WarningMessageVH, position: Int) {
@@ -44,7 +42,7 @@ class WarningMessagesAdapter : ListAdapter(Dif
}
}
-class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
+class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(warning: WarningMessage) {
setBgColor(warning.priority)
@@ -52,12 +50,12 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
setupControlButtons(warning)
}
- private fun setText(warning: WarningMessage) {
+ private fun setText(warning: WarningMessage) = with(binding) {
fun getString(resId: Int?, default: String, formatArgs: String? = null) =
- if (resId == null) default else view.context.getString(resId, formatArgs)
+ if (resId == null) default else root.getString(resId, formatArgs)
- view.tv_title.text = getString(warning.titleResId, warning.title)
- view.tv_message.text = getString(
+ tvTitle.text = getString(warning.titleResId, warning.title)
+ tvMessage.text = getString(
resId = warning.messageResId, default = warning.message, formatArgs = warning.messageFormatArg
)
}
@@ -68,17 +66,17 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
WarningMessage.Priority.Warning -> R.color.warning_warning
WarningMessage.Priority.Critical -> R.color.warning_critical
}
- view.card_view.setCardBackgroundColor(view.context.resources.getColor(color))
+ binding.cardView.setCardBackgroundColor(binding.root.getColor(color))
}
private fun setupControlButtons(warning: WarningMessage) = when (warning.type) {
WarningMessage.Type.Permanent, WarningMessage.Type.TestCard -> {
- view.group_controls_temporary.hide()
- view.group_controls_rating.hide()
+ binding.groupControlsTemporary.hide()
+ binding.groupControlsRating.hide()
}
WarningMessage.Type.Temporary -> {
- view.group_controls_rating.hide()
- view.group_controls_temporary.show()
+ binding.groupControlsRating.hide()
+ binding.groupControlsTemporary.show()
val buttonAction =
when (warning.titleResId) {
R.string.warning_important_security_info -> {
@@ -92,29 +90,29 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
}
}
}
- val buttonTitle = view.getString(
+ val buttonTitle = binding.root.getString(
warning.buttonTextId ?: R.string.how_to_got_it_button
)
- view.btn_got_it.setOnClickListener (buttonAction)
- view.btn_got_it.text = buttonTitle
+ binding.btnGotIt.setOnClickListener (buttonAction)
+ binding.btnGotIt.text = buttonTitle
}
WarningMessage.Type.AppRating -> {
- view.group_controls_temporary.hide()
- view.group_controls_rating.show()
+ binding.groupControlsTemporary.hide()
+ binding.groupControlsRating.show()
val analyticsHandler = store.state.globalState.analyticsHandlers
- view.btn_close.setOnClickListener {
+ binding.btnClose.setOnClickListener {
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS)
store.dispatch(GlobalAction.HideWarningMessage(warning))
store.dispatch(WalletAction.Warnings.AppRating.RemindLater)
}
- view.btn_can_be_better.setOnClickListener {
+ binding.btnCanBeBetter.setOnClickListener {
analyticsHandler?.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
+ binding.btnReallyCool.setOnClickListener {
+ val activity = binding.root.context.getActivity() ?: return@setOnClickListener
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendDialog.kt
index 0175d55b59..5eea55091b 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendDialog.kt
@@ -1,8 +1,9 @@
package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
+import android.content.DialogInterface
+import android.os.Bundle
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.recyclerview.widget.*
@@ -12,14 +13,22 @@ import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.dialog_wallet_send.*
-import kotlinx.android.synthetic.main.item_wallet_amount_to_send.view.*
-
+import com.tangem.wallet.databinding.DialogWalletSendBinding
+import com.tangem.wallet.databinding.ItemWalletAmountToSendBinding
class AmountToSendDialog(context: Context) : BottomSheetDialog(context) {
- init {
- this.setContentView(R.layout.dialog_wallet_send)
+ var binding: DialogWalletSendBinding? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DialogWalletSendBinding.inflate(LayoutInflater.from(context))
+ setContentView(binding!!.root)
+ }
+
+ override fun setOnCancelListener(listener: DialogInterface.OnCancelListener?) {
+ super.setOnCancelListener(listener)
+ binding = null
}
fun show(amounts: List?) {
@@ -27,15 +36,15 @@ class AmountToSendDialog(context: Context) : BottomSheetDialog(context) {
store.dispatch(WalletAction.Send.Cancel)
}
- rv_amounts_to_send.layoutManager = LinearLayoutManager(context)
+ binding!!.rvAmountsToSend.layoutManager = LinearLayoutManager(context)
val dividerItemDecoration = DividerItemDecoration(
- ContextThemeWrapper(rv_amounts_to_send.context, R.style.AppTheme),
+ ContextThemeWrapper(binding!!.root.context, R.style.AppTheme),
DividerItemDecoration.VERTICAL
)
- rv_amounts_to_send.addItemDecoration(dividerItemDecoration)
+ binding!!.rvAmountsToSend.addItemDecoration(dividerItemDecoration)
val viewAdapter = ChooseAmountAdapter()
- rv_amounts_to_send.adapter = viewAdapter
+ binding!!.rvAmountsToSend.adapter = viewAdapter
viewAdapter.submitList(amounts)
@@ -48,9 +57,10 @@ class ChooseAmountAdapter
: ListAdapter(DiffUtilCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AmountViewHolder {
- val layout = LayoutInflater.from(parent.context)
- .inflate(R.layout.item_wallet_amount_to_send, parent, false)
- return AmountViewHolder(layout)
+ val binding = ItemWalletAmountToSendBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
+ return AmountViewHolder(binding)
}
override fun onBindViewHolder(holder: AmountViewHolder, position: Int) {
@@ -67,13 +77,13 @@ class ChooseAmountAdapter
) = oldItem == newItem
}
- class AmountViewHolder(val view: View) :
- RecyclerView.ViewHolder(view) {
+ class AmountViewHolder(val binding: ItemWalletAmountToSendBinding) :
+ RecyclerView.ViewHolder(binding.root) {
- fun bind(amount: Amount) {
- view.tv_currency_symbol.text = amount.currencySymbol
- view.tv_amount.text = amount.value?.toFormattedString(amount.decimals)
- view.setOnClickListener {
+ fun bind(amount: Amount) = with(binding) {
+ tvCurrencySymbol.text = amount.currencySymbol
+ tvAmount.text = amount.value?.toFormattedString(amount.decimals)
+ root.setOnClickListener {
store.dispatch(WalletAction.Send.Cancel)
store.dispatch(WalletAction.Send(amount))
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionDialog.kt
index 95ff1f65ea..93baaf29a3 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionDialog.kt
@@ -1,30 +1,36 @@
package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
-import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.dialog_wallet_trade.*
+import com.tangem.wallet.databinding.DialogWalletTradeBinding
class ChooseTradeActionDialog(context: Context) : BottomSheetDialog(context) {
- init {
- this.setContentView(R.layout.dialog_wallet_trade)
+ var binding: DialogWalletTradeBinding? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DialogWalletTradeBinding.inflate(LayoutInflater.from(context))
+ setContentView(binding!!.root)
}
override fun show() {
super.show()
this.setOnDismissListener {
+ binding = null
store.dispatch(WalletAction.HideDialog)
}
- dialog_btn_buy.setOnClickListener {
+ binding!!.dialogBtnBuy.setOnClickListener {
store.dispatch(WalletAction.TradeCryptoAction.Buy)
dismiss()
}
- dialog_btn_sell.setOnClickListener {
+ binding!!.dialogBtnSell.setOnClickListener {
store.dispatch(WalletAction.TradeCryptoAction.Sell)
dismiss()
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt
new file mode 100644
index 0000000000..3fc5187741
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt
@@ -0,0 +1,41 @@
+package com.tangem.tap.features.wallet.ui.dialogs
+
+import android.content.Context
+import androidx.appcompat.app.AlertDialog
+import com.tangem.tap.common.extensions.dispatchDialogHide
+import com.tangem.tap.common.redux.AppDialog
+import com.tangem.tap.store
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+class SimpleOkDialog {
+
+ companion object {
+ fun create(dialog: AppDialog.SimpleOkDialog, context: Context): AlertDialog {
+ return AlertDialog.Builder(context).apply {
+ setTitle(dialog.header)
+ setMessage(dialog.message)
+ setPositiveButton(R.string.common_ok) { _, _ -> }
+ setOnDismissListener {
+ store.dispatchDialogHide()
+ dialog.onOk?.invoke()
+ }
+ }.create()
+ }
+
+ fun create(dialog: AppDialog.SimpleOkDialogRes, context: Context): AlertDialog {
+ return AlertDialog.Builder(context).apply {
+ setTitle(context.getString(dialog.headerId))
+ setMessage(dialog.messageId)
+ setPositiveButton(R.string.common_ok) { _, _ -> }
+ setOnDismissListener {
+ store.dispatchDialogHide()
+ dialog.onOk?.invoke()
+ }
+ }.create()
+ }
+ }
+
+}
\ 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
index 8eeef0afdc..7b2e9ba9ec 100644
--- 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
@@ -18,64 +18,64 @@ import com.tangem.tap.features.wallet.ui.adapters.WalletAdapter
import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog
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.*
+import com.tangem.wallet.databinding.FragmentWalletBinding
class MultiWalletView : WalletView {
private var fragment: WalletFragment? = null
+ private var binding: FragmentWalletBinding? = null
private var dialog: Dialog? = null
private lateinit var walletsAdapter: WalletAdapter
- override fun changeWalletView(fragment: WalletFragment) {
- setFragment(fragment)
+ override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
+ setFragment(fragment, binding)
onViewCreated()
- showMultiWalletView(fragment)
- setupButtons(fragment)
+ showMultiWalletView(binding)
+ setupButtons(binding)
}
- private fun showMultiWalletView(fragment: WalletFragment) = with(fragment) {
- tv_twin_card_number.hide()
- rv_pending_transaction.hide()
- l_card_balance.hide()
- l_address.hide()
- l_buttons_short.hide()
- l_buttons_long.hide()
- btn_scan_multiwallet?.show()
- rv_multiwallet.show()
- btn_add_token.show()
- setupWalletCardNumber(fragment)
+ private fun showMultiWalletView(binding: FragmentWalletBinding) = with(binding) {
+ tvTwinCardNumber.hide()
+ rvPendingTransaction.hide()
+ lCardBalance.root.hide()
+ lAddress.root.hide()
+ lButtonsShort.root.hide()
+ lButtonsLong.root.hide()
+ btnScanMultiwallet.show()
+ rvMultiwallet.show()
+ btnAddToken.show()
+ setupWalletCardNumber(binding)
}
- private fun setupWalletCardNumber(fragment: WalletFragment) = with(fragment) {
+ private fun setupWalletCardNumber(binding: FragmentWalletBinding) = with(binding) {
val card = store.state.globalState.scanResponse?.card
if (card?.backupStatus is Card.BackupStatus.Active) {
val cardCount = (card.backupStatus as Card.BackupStatus.Active).cardCount + 1
- tv_twin_card_number.show()
- tv_twin_card_number.text =
- getString(R.string.wallet_twins_chip_format, 1, cardCount)
+ tvTwinCardNumber.show()
+ tvTwinCardNumber.text =
+ fragment?.getString(R.string.wallet_twins_chip_format, 1, cardCount)
} else {
- tv_twin_card_number.hide()
+ tvTwinCardNumber.hide()
}
}
- private fun setupButtons(fragment: WalletFragment) = with(fragment) {
- btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) }
+ private fun setupButtons(binding: FragmentWalletBinding) = with(binding) {
+ btnScanMultiwallet.setOnClickListener { store.dispatch(WalletAction.Scan) }
}
- override fun setFragment(fragment: WalletFragment) {
+ override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
+ this.binding = binding
}
override fun removeFragment() {
this.fragment = null
+ this.binding = null
}
override fun onViewCreated() {
@@ -86,37 +86,42 @@ class MultiWalletView : WalletView {
val fragment = fragment ?: return
walletsAdapter = WalletAdapter()
walletsAdapter.setHasStableIds(true)
- fragment.rv_multiwallet.layoutManager = LinearLayoutManager(fragment.requireContext())
- fragment.rv_multiwallet.adapter = walletsAdapter
+ binding?.rvMultiwallet?.layoutManager = LinearLayoutManager(fragment.requireContext())
+ binding?.rvMultiwallet?.adapter = walletsAdapter
}
override fun onNewState(state: WalletState) {
val fragment = fragment ?: return
+ val binding = binding ?: return
walletsAdapter.submitList(state.wallets, state.primaryBlockchain, state.primaryToken)
- fragment.btn_add_token?.setOnClickListener {
+ binding.btnAddToken.setOnClickListener {
store.dispatch(TokensAction.LoadCurrencies)
store.dispatch(TokensAction.SetAddedCurrencies(state.wallets))
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
}
- handleErrorStates(state, fragment)
+ handleErrorStates(state = state, binding = binding, fragment = fragment)
handleDialogs(state.walletDialog)
}
- private fun handleErrorStates(state: WalletState, fragment: WalletFragment) {
+ private fun handleErrorStates(
+ state: WalletState,
+ binding: FragmentWalletBinding,
+ fragment: WalletFragment
+ ) {
when (state.primaryWallet?.currencyData?.status) {
BalanceStatus.EmptyCard -> {
showErrorState(
- fragment,
+ binding,
fragment.getText(R.string.wallet_error_empty_card),
fragment.getString(R.string.wallet_error_empty_card_subtitle)
)
- configureButtonsForEmptyWalletState(fragment)
+ configureButtonsForEmptyWalletState(binding)
}
BalanceStatus.UnknownBlockchain -> {
showErrorState(
- fragment,
+ binding,
fragment.getText(R.string.wallet_error_unsupported_blockchain),
fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
)
@@ -125,24 +130,28 @@ class MultiWalletView : WalletView {
}
private fun showErrorState(
- fragment: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence,
- ) = with(fragment) {
- l_card_balance.show()
- l_balance.hide()
- l_balance_error.show()
- rv_multiwallet.hide()
- btn_add_token.hide()
- tv_error_title.text = errorTitle
- tv_error_descriptions.text = errorDescription
+ binding: FragmentWalletBinding, errorTitle: CharSequence, errorDescription: CharSequence,
+ ) = with(binding) {
+ lCardBalance.root.show()
+ with(lCardBalance) {
+ lBalance.root.hide()
+ lBalanceError.root.show()
+ rvMultiwallet.show()
+ btnAddToken.hide()
+ lBalanceError.tvErrorTitle.text = errorTitle
+ lBalanceError.tvErrorDescriptions.text = errorDescription
+ }
}
- private fun configureButtonsForEmptyWalletState(fragment: WalletFragment) = with(fragment) {
- btn_scan_multiwallet.hide()
- l_buttons_long.show()
- btn_scan_long.setOnClickListener { store.dispatch(WalletAction.Scan) }
- btn_confirm_long.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
- btn_confirm_long.text = fragment.getText(R.string.wallet_button_create_wallet)
- }
+ private fun configureButtonsForEmptyWalletState(binding: FragmentWalletBinding) =
+ with(binding) {
+ btnScanMultiwallet.hide()
+ lButtonsLong.root.show()
+ lButtonsLong.btnScanLong.setOnClickListener { store.dispatch(WalletAction.Scan) }
+ lButtonsLong.btnConfirmLong.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
+ lButtonsLong.btnConfirmLong.text =
+ fragment?.getText(R.string.wallet_button_create_wallet)
+ }
private fun handleDialogs(walletDialog: StateDialog?) {
val fragment = fragment ?: return
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
index d8ab4644df..224f717a2a 100644
--- 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
@@ -5,7 +5,10 @@ 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.common.extensions.beginDelayedTransition
+import com.tangem.tap.common.extensions.fitChipsByGroupWidth
+import com.tangem.tap.common.extensions.hide
+import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
@@ -15,42 +18,43 @@ 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.*
+import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
+import com.tangem.tap.features.wallet.ui.dialogs.ChooseTradeActionDialog
+import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog
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.*
+import com.tangem.wallet.databinding.FragmentWalletBinding
class SingleWalletView : WalletView {
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
private var fragment: WalletFragment? = null
+ private var binding: FragmentWalletBinding? = null
private var dialog: Dialog? = null
- override fun setFragment(fragment: WalletFragment) {
+ override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
+ this.binding = binding
}
override fun removeFragment() {
fragment = null
+ binding = null
}
- override fun changeWalletView(fragment: WalletFragment) {
- setFragment(fragment)
+ override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
+ setFragment(fragment, binding)
onViewCreated()
- showSingleWalletView(fragment)
+ showSingleWalletView(binding)
}
- private fun showSingleWalletView(fragment: WalletFragment) = with(fragment) {
- rv_multiwallet.hide()
- btn_add_token.hide()
- btn_scan_multiwallet?.hide()
- rv_pending_transaction.hide()
- l_card_balance.show()
- l_address.show()
+ private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
+ rvMultiwallet.hide()
+ btnAddToken.hide()
+ btnScanMultiwallet.hide()
+ rvPendingTransaction.hide()
+ lCardBalance.root.show()
+ lAddress.root.show()
}
override fun onViewCreated() {
@@ -61,17 +65,19 @@ class SingleWalletView : WalletView {
private fun setupTransactionsRecyclerView() {
val fragment = fragment ?: return
pendingTransactionAdapter = PendingTransactionsAdapter()
- fragment.rv_pending_transaction.layoutManager = LinearLayoutManager(fragment.requireContext())
- fragment.rv_pending_transaction.adapter = pendingTransactionAdapter
+ binding?.rvPendingTransaction?.layoutManager =
+ LinearLayoutManager(fragment.requireContext())
+ binding?.rvPendingTransaction?.adapter = pendingTransactionAdapter
}
override fun onNewState(state: WalletState) {
val fragment = fragment ?: return
+ val binding = binding ?: return
state.primaryWallet ?: return
- setupTwinCards(state.twinCardsState, fragment)
- setupButtons(state.primaryWallet, state.isTangemTwins, fragment)
- setupAddressCard(state.primaryWallet, fragment)
+ setupTwinCards(state.twinCardsState, binding)
+ setupButtons(state.primaryWallet, state.isTangemTwins, binding)
+ setupAddressCard(state.primaryWallet, binding)
showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
setupBalance(state, state.primaryWallet)
handleDialogs(state.walletDialog, fragment)
@@ -79,75 +85,81 @@ class SingleWalletView : WalletView {
private fun showPendingTransactionsIfPresent(pendingTransactions: List) {
pendingTransactionAdapter.submitList(pendingTransactions)
- fragment?.rv_pending_transaction?.show(pendingTransactions.isNotEmpty())
+ binding?.rvPendingTransaction?.show(pendingTransactions.isNotEmpty())
}
private fun setupBalance(state: WalletState, primaryWallet: WalletData) {
- fragment?.apply {
- this.l_balance.show()
- BalanceWidget(this, primaryWallet.currencyData, state.isTangemTwins).setup()
+ val fragment = fragment ?: return
+ binding?.apply {
+ lCardBalance.lBalance.root.show()
+ BalanceWidget(
+ binding = this.lCardBalance,
+ fragment = fragment,
+ data = primaryWallet.currencyData,
+ isTwinCard = state.isTangemTwins
+ ).setup()
}
}
private fun setupTwinCards(
- twinCardsState: TwinCardsState?, fragment: WalletFragment
- ) = with(fragment) {
+ twinCardsState: TwinCardsState?, binding: FragmentWalletBinding
+ ) = with(binding) {
twinCardsState?.cardNumber?.let { cardNumber ->
- tv_twin_card_number.show()
+ tvTwinCardNumber.show()
val number = when (cardNumber) {
TwinCardNumber.First -> 1
TwinCardNumber.Second -> 2
}
- tv_twin_card_number.text =
- this.getString(R.string.wallet_twins_chip_format, number, 2)
+ tvTwinCardNumber.text =
+ fragment?.getString(R.string.wallet_twins_chip_format, number, 2)
}
if (twinCardsState?.cardNumber == null) {
- tv_twin_card_number.hide()
+ tvTwinCardNumber.hide()
}
}
private fun setupButtons(
- state: WalletData, isTwinsWallet: Boolean, fragment: WalletFragment
- ) = with(fragment){
+ state: WalletData, isTwinsWallet: Boolean, binding: FragmentWalletBinding
+ ) = with(binding) {
- setupButtonsType(state, fragment)
+ setupButtonsType(state, binding)
val btnConfirm = if (state.tradeCryptoState.sellingAllowed ||
state.tradeCryptoState.buyingAllowed
) {
- btn_confirm_short
+ lButtonsShort.btnConfirm
} else {
- btn_confirm_long
+ lButtonsLong.btnConfirmLong
}
val btnScan = if (state.tradeCryptoState.sellingAllowed ||
state.tradeCryptoState.buyingAllowed
) {
- btn_scan_short
+ lButtonsShort.btnScan
} else {
- btn_scan_long
+ lButtonsLong.btnScanLong
}
- setupConfirmButton(state, btnConfirm, fragment, isTwinsWallet)
+ setupConfirmButton(state, btnConfirm, isTwinsWallet)
btnScan.setOnClickListener {
store.dispatch(WalletAction.Scan)
}
- btn_copy.setOnClickListener {
+ lAddress.btnCopy.setOnClickListener {
state.walletAddresses?.selectedAddress?.address?.let { addressString ->
- store.dispatch(WalletAction.CopyAddress(addressString, fragment.requireContext()))
+ store.dispatch(WalletAction.CopyAddress(addressString, fragment!!.requireContext()))
}
}
- btn_show_qr.setOnClickListener {
+ lAddress.btnShowQr.setOnClickListener {
store.dispatch(WalletAction.ShowDialog.QrCode)
}
- setupTradeButton(fragment, state.tradeCryptoState)
+ setupTradeButton(binding, state.tradeCryptoState)
}
- private fun setupTradeButton(fragment: WalletFragment, tradeCryptoState: TradeCryptoState) {
+ private fun setupTradeButton(binding: FragmentWalletBinding, tradeCryptoState: TradeCryptoState) {
val allowedToBuy = tradeCryptoState.buyingAllowed
val allowedToSell = tradeCryptoState.sellingAllowed
val action = when {
@@ -160,35 +172,35 @@ class SingleWalletView : WalletView {
allowedToBuy && !allowedToSell -> R.string.wallet_button_buy
!allowedToBuy && allowedToSell -> R.string.wallet_button_sell
allowedToBuy && allowedToSell -> R.string.wallet_button_trade
- else -> R.string.wallet_button_trade
+ else -> R.string.wallet_button_trade
}
val icon = when {
- allowedToBuy && !allowedToSell -> R.drawable.ic_arrow_up_short_btn
- !allowedToBuy && allowedToSell -> R.drawable.ic_arrow_down_short_button
- allowedToBuy && allowedToSell -> R.drawable.ic_arrows_up_down_short_btn
+ allowedToBuy && !allowedToSell -> R.drawable.ic_arrow_up
+ !allowedToBuy && allowedToSell -> R.drawable.ic_arrow_down
+ allowedToBuy && allowedToSell -> R.drawable.ic_arrows_up_down
else -> null
}
- with(fragment) {
- btn_trade.text = getText(text)
- btn_trade.setCompoundDrawablesWithIntrinsicBounds(0, icon ?: 0, 0, 0)
- btn_trade.setOnClickListener { if (action != null) store.dispatch(action) }
+ with(binding) {
+ lButtonsShort.btnTrade.text = fragment?.getText(text)
+ icon?.let { lButtonsShort.btnTrade.setIconResource(it) }
+ lButtonsShort.btnTrade.setOnClickListener { if (action != null) store.dispatch(action) }
}
}
- private fun setupButtonsType(state: WalletData, fragment: WalletFragment) = with(fragment) {
+ private fun setupButtonsType(state: WalletData, binding: FragmentWalletBinding) = with(binding) {
if (state.tradeCryptoState.sellingAllowed ||
state.tradeCryptoState.buyingAllowed
) {
- l_buttons_long.hide()
- l_buttons_short.show()
+ lButtonsLong.root.hide()
+ lButtonsShort.root.show()
} else {
- l_buttons_long.show()
- l_buttons_short.hide()
+ lButtonsLong.root.show()
+ lButtonsShort.root.hide()
}
}
private fun setupConfirmButton(
- state: WalletData, btnConfirm: Button, fragment: WalletFragment, isTwinsWallet: Boolean,
+ state: WalletData, btnConfirm: Button, isTwinsWallet: Boolean,
) {
val buttonTitle = when (state.mainButton) {
is WalletMainButton.SendButton -> R.string.wallet_button_send
@@ -200,7 +212,7 @@ class SingleWalletView : WalletView {
}
}
}
- btnConfirm.text = fragment.getString(buttonTitle)
+ btnConfirm.text = fragment?.getString(buttonTitle)
btnConfirm.isEnabled = state.mainButton.enabled
btnConfirm.setOnClickListener {
when (state.mainButton) {
@@ -211,33 +223,38 @@ class SingleWalletView : WalletView {
}
- private fun setupAddressCard(state: WalletData, fragment: WalletFragment) = with(fragment) {
+ private fun setupAddressCard(state: WalletData, binding: FragmentWalletBinding) = with(binding.lAddress) {
if (state.walletAddresses != null && state.currency is Currency.Blockchain) {
- l_address?.show()
+ binding.lAddress.root.show()
if (state.shouldShowMultipleAddress()) {
- (l_address as? ViewGroup)?.beginDelayedTransition()
- chip_group_address_type.show()
- chip_group_address_type.fitChipsByGroupWidth()
+ (binding.lAddress.root as? ViewGroup)?.beginDelayedTransition()
+ chipGroupAddressType.show()
+ chipGroupAddressType.fitChipsByGroupWidth()
- val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
- if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
+ val checkedId =
+ MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
+ if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
- chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
+ chipGroupAddressType.setOnCheckedChangeListener { group, checkedId ->
if (checkedId == -1) return@setOnCheckedChangeListener
- val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain)
+ val type =
+ MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain)
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
}
} else {
- chip_group_address_type.hide()
+ chipGroupAddressType.hide()
}
- tv_address.text = state.walletAddresses.selectedAddress.address
- tv_explore?.setOnClickListener {
- store.dispatch(WalletAction.ExploreAddress(
+ tvAddress.text = state.walletAddresses.selectedAddress.address
+ tvExplore.setOnClickListener {
+ store.dispatch(
+ WalletAction.ExploreAddress(
state.walletAddresses.selectedAddress.exploreUrl,
- fragment.requireContext()))
+ fragment!!.requireContext()
+ )
+ )
}
} else {
- l_address?.hide()
+ binding.lAddress.root.hide()
}
}
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
index ab49865c02..1d9ea0eac0 100644
--- 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
@@ -2,14 +2,15 @@ package com.tangem.tap.features.wallet.ui.wallet
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.WalletFragment
+import com.tangem.wallet.databinding.FragmentWalletBinding
interface WalletView {
- fun setFragment(fragment: WalletFragment)
+ fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding)
fun removeFragment()
- fun changeWalletView(fragment: WalletFragment)
+ fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)
fun onViewCreated()
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt
new file mode 100644
index 0000000000..a6e9c55cba
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt
@@ -0,0 +1,136 @@
+package com.tangem.tap.network.exchangeServices
+
+import com.tangem.Message
+import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.extensions.Result
+import com.tangem.tap.common.extensions.safeUpdate
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.domain.TangemSigner
+import com.tangem.tap.store
+import com.tangem.tap.tangemSdk
+import java.math.BigDecimal
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface ExchangeService {
+ suspend fun isBuyAllowed(): Boolean
+ suspend fun availableToBuy(): List
+ suspend fun isSellAllowed(): Boolean
+ suspend fun availableToSell(): List
+}
+
+interface ExchangeUrlBuilder {
+ fun getUrl(
+ action: CurrencyExchangeManager.Action,
+ blockchain: Blockchain,
+ cryptoCurrencyName: CryptoCurrencyName,
+ fiatCurrency: String,
+ walletAddress: String,
+ ): String?
+
+ fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String?
+
+ companion object {
+ const val SCHEME = "https"
+ const val URL_SELL = "sell.moonpay.com"
+ const val SUCCESS_URL = "tangem://success.tangem.com"
+ }
+}
+
+class CurrencyExchangeManager(
+ private val onramperService: ExchangeService,
+ private val moonPayService: ExchangeService,
+) : ExchangeService, ExchangeUrlBuilder {
+
+ var status: CurrencyExchangeStatus? = null
+ private set
+
+ suspend fun getStatus(): CurrencyExchangeStatus {
+ val isBuyAllowed = isBuyAllowed()
+ val isSellAllowed = isSellAllowed()
+ val availableToBuy = availableToBuy()
+ val availableToSell = availableToSell()
+ status = CurrencyExchangeStatus(
+ isBuyAllowed,
+ isSellAllowed,
+ availableToBuy,
+ availableToSell,
+ )
+ return status!!
+ }
+
+ override suspend fun isBuyAllowed(): Boolean = onramperService.isBuyAllowed()
+ override suspend fun availableToBuy(): List = onramperService.availableToBuy()
+ override suspend fun isSellAllowed(): Boolean = moonPayService.isSellAllowed()
+ override suspend fun availableToSell(): List = moonPayService.availableToSell()
+
+ override fun getUrl(
+ action: Action,
+ blockchain: Blockchain,
+ cryptoCurrencyName: CryptoCurrencyName,
+ fiatCurrency: String,
+ walletAddress: String,
+ ): String? {
+ if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl()
+
+ val urlBuilder = getExchangeUrlBuilder(action)
+ return urlBuilder.getUrl(action, blockchain, cryptoCurrencyName, fiatCurrency, walletAddress)
+ }
+
+ override fun getSellCryptoReceiptUrl(action: Action, transactionId: String): String? {
+ val urlBuilder = getExchangeUrlBuilder(action)
+ return urlBuilder.getSellCryptoReceiptUrl(action, transactionId)
+ }
+
+ private fun getExchangeUrlBuilder(action: Action): ExchangeUrlBuilder {
+ return when (action) {
+ Action.Buy -> onramperService
+ Action.Sell -> moonPayService
+ } as ExchangeUrlBuilder
+ }
+
+ enum class Action { Buy, Sell }
+}
+
+data class CurrencyExchangeStatus(
+ val isBuyAllowed: Boolean,
+ val isSellAllowed: Boolean,
+ val availableToBuy: List,
+ val availableToSell: List,
+)
+
+suspend fun CurrencyExchangeManager.buyErc20Tokens(walletManager: EthereumWalletManager, token: Token) {
+ walletManager.safeUpdate()
+
+ val amountToSend = Amount(walletManager.wallet.blockchain)
+ val destinationAddress = token.contractAddress
+
+ val feeResult = walletManager.getFee(amountToSend,
+ destinationAddress) as? Result.Success ?: return
+ val fee = feeResult.data[0]
+
+ if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) {
+ return
+ }
+
+ val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress)
+
+ val signer = TangemSigner(
+ tangemSdk = tangemSdk, Message()
+ ) { signResponse ->
+ store.dispatch(
+ GlobalAction.UpdateWalletSignedHashes(
+ walletSignedHashes = signResponse.totalSignedHashes,
+ walletPublicKey = walletManager.wallet.publicKey.seedKey,
+ remainingSignatures = signResponse.remainingSignatures
+ )
+ )
+ }
+ walletManager.send(transaction, signer)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt
new file mode 100644
index 0000000000..37dbe6185a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayApi.kt
@@ -0,0 +1,50 @@
+package com.tangem.tap.network.exchangeServices.moonpay
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface MoonPayApi {
+
+ @GET(MOOONPAY_IP_ADDRESS_REQUEST_URL)
+ suspend fun getUserStatus(
+ @Query("apiKey") moonPayApiKey: String,
+ ): MoonPayUserStatus
+
+ @GET(MOOONPAY_CURRENCIES_REQUEST_URL)
+ suspend fun getCurrencies(
+ @Query("apiKey") moonPayApiKey: String,
+ ): List
+
+
+ companion object {
+ const val MOOONPAY_BASE_URL = "https://api.moonpay.com/"
+ const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "v4/ip_address/"
+ const val MOOONPAY_CURRENCIES_REQUEST_URL = "v3/currencies/"
+ }
+}
+
+
+@JsonClass(generateAdapter = true)
+data class MoonPayUserStatus(
+ val isBuyAllowed: Boolean,
+ val isSellAllowed: Boolean,
+ @Json(name = "isAllowed")
+ val isMoonpayAllowed: Boolean,
+ @Json(name = "alpha3")
+ val countryCode: String,
+ @Json(name = "state")
+ val stateCode: String,
+)
+
+@JsonClass(generateAdapter = true)
+data class MoonPayCurrencies(
+ val type: String,
+ val code: String,
+ val supportsLiveMode: Boolean = false,
+ val isSuspended: Boolean = true,
+ val isSupportedInUS: Boolean = false,
+ val isSellSupported: Boolean = false,
+ val notAllowedUSStates: List = emptyList(),
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
new file mode 100644
index 0000000000..f3ca64c4db
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
@@ -0,0 +1,142 @@
+package com.tangem.tap.network.exchangeServices.moonpay
+
+import android.net.Uri
+import android.util.Base64
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.common.services.Result
+import com.tangem.common.services.performRequest
+import com.tangem.tap.common.extensions.urlEncode
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.network.createRetrofitInstance
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
+import com.tangem.tap.network.exchangeServices.ExchangeService
+import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
+import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
+import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.URL_SELL
+import kotlinx.coroutines.coroutineScope
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+
+class MoonPayService(
+ private val apiKey: String,
+ private val secretKey: String,
+) : ExchangeService, ExchangeUrlBuilder {
+
+ private val api: MoonPayApi by lazy {
+ createRetrofitInstance(MoonPayApi.MOOONPAY_BASE_URL)
+ .create(MoonPayApi::class.java)
+ }
+
+ private var status: MoonPayStatus? = null
+
+ private suspend fun updateStatus() {
+ try {
+ coroutineScope {
+ val userStatusResult = performRequest { api.getUserStatus(apiKey) }
+ if (userStatusResult is Result.Failure) return@coroutineScope userStatusResult
+
+ val currenciesResult = performRequest { api.getCurrencies(apiKey) }
+ if (currenciesResult is Result.Failure) return@coroutineScope currenciesResult
+
+ val userStatus = (userStatusResult as Result.Success).data
+ val currencies = (currenciesResult as Result.Success).data
+
+// val currenciesToBuy = mutableListOf()
+ val currenciesToSell = mutableListOf()
+
+ currencies.forEach { currencyStatus ->
+ if (currencyStatus.type != "crypto" || currencyStatus.isSuspended ||
+ !currencyStatus.supportsLiveMode
+ ) {
+ return@forEach
+ }
+
+ if (userStatus.countryCode == "USA") {
+ if (!currencyStatus.isSupportedInUS) return@forEach
+ if (currencyStatus.notAllowedUSStates.contains(userStatus.stateCode)) return@forEach
+ }
+ val currencyCode = currencyStatus.code.uppercase()
+// currenciesToBuy.add(currencyCode)
+
+ if (currencyStatus.isSellSupported) currenciesToSell.add(currencyCode)
+ }
+// currenciesToBuy.sort()
+ currenciesToSell.sort()
+
+ status = MoonPayStatus(currenciesToSell, userStatus, currencies)
+ }
+ } catch (error: Error) {
+ status = null
+ Result.Failure(error)
+ }
+ }
+
+ override suspend fun isBuyAllowed(): Boolean = false
+
+ override suspend fun availableToBuy(): List = listOf()
+
+ override suspend fun isSellAllowed(): Boolean {
+ refreshStatus()
+ return status?.responseUserStatus?.isSellAllowed ?: false
+ }
+
+ override suspend fun availableToSell(): List {
+ refreshStatus()
+ return status?.availableToSell ?: emptyList()
+ }
+
+ private suspend fun refreshStatus() {
+ if (status == null) {
+ updateStatus()
+ }
+ }
+
+ override fun getUrl(
+ action: CurrencyExchangeManager.Action,
+ blockchain: Blockchain,
+ cryptoCurrencyName: CryptoCurrencyName,
+ fatCurrency: String,
+ walletAddress: String
+ ): String? {
+ if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException()
+
+ val uri = Uri.Builder()
+ .scheme(SCHEME)
+ .authority(URL_SELL)
+ .appendQueryParameter("apiKey", apiKey.urlEncode())
+ .appendQueryParameter("baseCurrencyCode", cryptoCurrencyName.urlEncode())
+ .appendQueryParameter("refundWalletAddress", walletAddress.urlEncode())
+ .appendQueryParameter("redirectURL", "tangem://sell-request.tangem.com".urlEncode())
+
+ val originalQuery = uri.build().toString()
+ val signature = createSignature(originalQuery)
+ uri.appendQueryParameter("signature", signature.urlEncode())
+
+ val url = uri.build().toString()
+ return url
+ }
+
+ override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? {
+ val url = Uri.Builder()
+ .scheme(SCHEME)
+ .authority(URL_SELL)
+ .appendPath("transaction_receipt")
+ .appendQueryParameter("transactionId", transactionId).build().toString()
+ return url
+
+ }
+
+ private fun createSignature(data: String): String {
+ val sha256Hmac = Mac.getInstance("HmacSHA256")
+ val secretKey = SecretKeySpec(secretKey.toByteArray(), "HmacSHA256")
+ sha256Hmac.init(secretKey)
+ val sha256encoded = sha256Hmac.doFinal(data.toByteArray())
+ return Base64.encodeToString(sha256encoded, Base64.NO_WRAP)
+ }
+}
+
+private data class MoonPayStatus(
+ val availableToSell: List,
+ val responseUserStatus: MoonPayUserStatus,
+ val responseCurrencies: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt
new file mode 100644
index 0000000000..01fec5d3d9
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt
@@ -0,0 +1,94 @@
+package com.tangem.tap.network.exchangeServices.onramper
+
+import com.squareup.moshi.JsonClass
+import okhttp3.Interceptor
+import okhttp3.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+
+interface OnramperApi {
+ @GET("gateways")
+ suspend fun gateways(): GatewaysResponse
+
+ @GET("rate/{fromCurrency}/{toCurrency}/{paymentMethod}/{amount}")
+ suspend fun rate(
+ @Path("fromCurrency") fromCurrency: String,
+ @Path("toCurrency") toCurrency: String,
+ @Path("paymentMethod") paymentMethod: String,
+ @Path("amount") amount: Int,
+ ): RateResponse
+
+ companion object {
+ val BASE_URL = "https://onramper.tech/"
+ }
+}
+
+class AddKeyToHeaderInterceptor(
+ private val key: String
+) : Interceptor {
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val request = chain.request().newBuilder().addHeader("Authorization", "Basic $key").build()
+ return chain.proceed(request)
+ }
+}
+
+@JsonClass(generateAdapter = true)
+data class GatewaysResponse(
+ val gateways: List
+)
+
+@JsonClass(generateAdapter = true)
+data class OnramperGateway(
+ val identifier: String,
+ val paymentMethods: List,
+ val fiatCurrencies: List,
+ val cryptoCurrencies: List
+)
+
+@JsonClass(generateAdapter = true)
+data class OnramperCurrency(
+ val id: String,
+ val code: String,
+ val precision: Int
+)
+
+@JsonClass(generateAdapter = true)
+data class RateResponse(
+ val identifier: String,
+ val duration: OnramperDuration,
+ val available: Boolean,
+ val error: OnramperError? = null,
+ val rate: Double? = null,
+ val fees: Double? = null,
+ val requiredKYC: List? = null,
+ val receivedCrypto: Double? = null,
+ val nextStep: OnramperNextStep? = null,
+)
+
+@JsonClass(generateAdapter = true)
+data class OnramperNextStep(
+ val type: String,
+ val url: String,
+ val message: String,
+ val extraData: List
+)
+
+@JsonClass(generateAdapter = true)
+data class OnramperExtraData(
+ val type: String,
+ val name: String,
+ val humanName: String,
+)
+
+@JsonClass(generateAdapter = true)
+data class OnramperDuration(
+ val seconds: Long,
+ val message: String
+)
+
+@JsonClass(generateAdapter = true)
+data class OnramperError(
+ val type: String,
+ val message: String,
+ val limit: Double
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt
new file mode 100644
index 0000000000..a2534b966d
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt
@@ -0,0 +1,111 @@
+package com.tangem.tap.network.exchangeServices.onramper
+
+import android.net.Uri
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.common.services.Result
+import com.tangem.common.services.performRequest
+import com.tangem.tap.common.extensions.urlEncode
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.network.createRetrofitInstance
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
+import com.tangem.tap.network.exchangeServices.ExchangeService
+import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
+import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
+import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SUCCESS_URL
+import kotlinx.coroutines.coroutineScope
+import java.util.*
+
+/**
+[REDACTED_AUTHOR]
+ */
+class OnramperService(
+ val apiKey: String
+) : ExchangeService, ExchangeUrlBuilder {
+
+ private val api: OnramperApi by lazy {
+ createRetrofitInstance(OnramperApi.BASE_URL, listOf(AddKeyToHeaderInterceptor(apiKey)))
+ .create(OnramperApi::class.java)
+ }
+
+ private var status: OnramperStatus? = null
+
+ private suspend fun updateStatus() {
+ try {
+ coroutineScope {
+ val result = performRequest { api.gateways() }
+ if (result is Result.Failure) return@coroutineScope result
+
+ val response = (result as Result.Success).data
+ val currenciesToBuy = extractCurrenciesToBuy(response).sorted()
+ val status = OnramperStatus(currenciesToBuy, response)
+ this@OnramperService.status = status
+ }
+ } catch (error: Error) {
+ status = null
+ Result.Failure(error)
+ }
+ }
+
+ private fun extractCurrenciesToBuy(response: GatewaysResponse): List {
+ return response.gateways.map { gateway ->
+ gateway.cryptoCurrencies.map { currency -> currency.code }
+ }.flatten().toMutableSet().toList()
+ }
+
+ override suspend fun isBuyAllowed(): Boolean {
+ refreshStatus()
+ return status != null
+ }
+
+ override suspend fun availableToBuy(): List {
+ refreshStatus()
+ return status?.availableToBuy ?: emptyList()
+ }
+
+ private suspend fun refreshStatus() {
+ if (status == null) {
+ updateStatus()
+ }
+ }
+
+ override suspend fun isSellAllowed(): Boolean = false
+
+ override suspend fun availableToSell(): List = listOf()
+
+ override fun getUrl(
+ action: CurrencyExchangeManager.Action,
+ blockchain: Blockchain,
+ cryptoCurrencyName: CryptoCurrencyName,
+ fiatCurrency: String,
+ walletAddress: String,
+ ): String? {
+ var languageCode = Locale.getDefault().language
+ if (languageCode.isEmpty()) languageCode = "en"
+
+ val builder = Uri.Builder()
+ .scheme(SCHEME)
+ .authority("widget.onramper.com")
+ .appendQueryParameter("apiKey", this.apiKey.urlEncode())
+ .appendQueryParameter("defaultCrypto", cryptoCurrencyName)
+ .appendQueryParameter("wallets", "${blockchain.currency}:$walletAddress".urlEncode())
+ .appendQueryParameter("redirectURL", SUCCESS_URL)
+ .appendQueryParameter("defaultFiat", fiatCurrency)
+ .appendQueryParameter("language", languageCode)
+
+ status?.apply {
+ val gateways = responseGateways.gateways.joinToString(",") { it.identifier }.urlEncode()
+ builder.appendQueryParameter("onlyGateways", gateways)
+
+ }
+
+ val url = builder.build().toString()
+ return url
+ }
+
+ override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null
+}
+
+private data class OnramperStatus(
+ val availableToBuy: List,
+ val responseGateways: GatewaysResponse
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt
deleted file mode 100644
index 5d1bfbb5c2..0000000000
--- a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayApi.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.tangem.tap.network.moonpay
-
-import retrofit2.http.GET
-import retrofit2.http.Query
-
-interface MoonpayApi {
- @GET(MOOONPAY_IP_ADDRESS_REQUEST_URL)
- suspend fun getUserStatus(
- @Query("apiKey") moonpayApiKey: String,
- ): MoonPayUserStatus
-
- @GET(MOOONPAY_CURRENCIES_REQUEST_URL)
- suspend fun getCurrencies(
- @Query("apiKey") moonpayApiKey: String,
- ): List
-
-
- companion object {
- const val MOOONPAY_BASE_URL = "https://api.moonpay.com/"
- const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "v4/ip_address/"
- const val MOOONPAY_CURRENCIES_REQUEST_URL = "v3/currencies/"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt b/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt
deleted file mode 100644
index 3e9696700d..0000000000
--- a/app/src/main/java/com/tangem/tap/network/moonpay/MoonpayService.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.tangem.tap.network.moonpay
-
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-import com.tangem.common.services.Result
-import com.tangem.common.services.performRequest
-import com.tangem.tap.network.createRetrofitInstance
-import kotlinx.coroutines.coroutineScope
-
-class MoonpayService {
-
- private val moonpayApi: MoonpayApi by lazy {
- createRetrofitInstance(MoonpayApi.MOOONPAY_BASE_URL)
- .create(MoonpayApi::class.java)
- }
-
- suspend fun getMoonpayStatus(moonpayApiKey: String): Result {
- return try {
- coroutineScope {
- val userStatusResult = performRequest { moonpayApi.getUserStatus(moonpayApiKey) }
- if (userStatusResult is Result.Failure) return@coroutineScope userStatusResult
-
- val currenciesResult = performRequest { moonpayApi.getCurrencies(moonpayApiKey) }
- if (currenciesResult is Result.Failure) return@coroutineScope currenciesResult
-
- val userStatus = (userStatusResult as Result.Success).data
- val currencies = (currenciesResult as Result.Success).data
-
- val currenciesToBuy = mutableListOf()
- val currenciesToSell = mutableListOf()
-
- currencies.forEach { currencyStatus ->
- if (currencyStatus.type != "crypto" || currencyStatus.isSuspended ||
- !currencyStatus.supportsLiveMode
- ) {
- return@forEach
- }
-
- if (userStatus.countryCode == "USA") {
- if (!currencyStatus.isSupportedInUS) return@forEach
- if (currencyStatus.notAllowedUSStates.contains(userStatus.stateCode)) return@forEach
- }
- val currencyCode = currencyStatus.code.uppercase()
- currenciesToBuy.add(currencyCode)
-
- if (currencyStatus.isSellSupported) currenciesToSell.add(currencyCode)
- }
-
- Result.Success(MoonpayStatus(
- isBuyAllowed = userStatus.isBuyAllowed,
- isSellAllowed = userStatus.isSellAllowed,
- availableToBuy = if (userStatus.isBuyAllowed) currenciesToBuy else emptyList(),
- availableToSell = if (userStatus.isSellAllowed) currenciesToSell else emptyList()
- ))
- }
- } catch (error: Error) {
- Result.Failure(error)
- }
- }
-}
-
-data class MoonpayStatus(
- val isBuyAllowed: Boolean,
- val isSellAllowed: Boolean,
- val availableToBuy: List,
- val availableToSell: List,
-)
-
-@JsonClass(generateAdapter = true)
-data class MoonPayUserStatus(
- val isBuyAllowed: Boolean,
- val isSellAllowed: Boolean,
- @Json(name = "isAllowed")
- val isMoonpayAllowed: Boolean,
- @Json(name = "alpha3")
- val countryCode: String,
- @Json(name = "state")
- val stateCode: String,
-)
-
-@JsonClass(generateAdapter = true)
-data class MoonpayCurrencies(
- val type: String,
- val code: String,
- val supportsLiveMode: Boolean = false,
- val isSuspended: Boolean = true,
- val isSupportedInUS: Boolean = false,
- val isSellSupported: Boolean = false,
- val notAllowedUSStates: List = emptyList(),
-)
\ No newline at end of file
diff --git a/app/src/main/res/color/selector_chip_shop.xml b/app/src/main/res/color/selector_chip_shop.xml
new file mode 100644
index 0000000000..052f962892
--- /dev/null
+++ b/app/src/main/res/color/selector_chip_shop.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/selector_chip_shop_text.xml b/app/src/main/res/color/selector_chip_shop_text.xml
new file mode 100644
index 0000000000..5d5afc33ff
--- /dev/null
+++ b/app/src/main/res/color/selector_chip_shop_text.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-de/buy_with_googlepay_button_content.xml b/app/src/main/res/drawable-de/buy_with_googlepay_button_content.xml
new file mode 100755
index 0000000000..e1e2577110
--- /dev/null
+++ b/app/src/main/res/drawable-de/buy_with_googlepay_button_content.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-fr/buy_with_googlepay_button_content.xml b/app/src/main/res/drawable-fr/buy_with_googlepay_button_content.xml
new file mode 100755
index 0000000000..4be47da569
--- /dev/null
+++ b/app/src/main/res/drawable-fr/buy_with_googlepay_button_content.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-hdpi/card_placeholder_new.png b/app/src/main/res/drawable-hdpi/card_placeholder_new.png
deleted file mode 100644
index 4c9cff9062..0000000000
Binary files a/app/src/main/res/drawable-hdpi/card_placeholder_new.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/card_placeholder_wallet.9.png b/app/src/main/res/drawable-hdpi/card_placeholder_wallet.9.png
new file mode 100644
index 0000000000..97ea9bc260
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/card_placeholder_wallet.9.png differ
diff --git a/app/src/main/res/drawable-hdpi/floating_cards.png b/app/src/main/res/drawable-hdpi/floating_cards.png
new file mode 100644
index 0000000000..6f8eba908f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/floating_cards.png differ
diff --git a/app/src/main/res/drawable-hdpi/meet_tangem.png b/app/src/main/res/drawable-hdpi/meet_tangem.png
new file mode 100644
index 0000000000..25f2d5a3ba
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/meet_tangem.png differ
diff --git a/app/src/main/res/drawable-hdpi/revolutionary_wallet.png b/app/src/main/res/drawable-hdpi/revolutionary_wallet.png
new file mode 100644
index 0000000000..f4f384a54d
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/revolutionary_wallet.png differ
diff --git a/app/src/main/res/drawable-hdpi/thousands_of_currencies.png b/app/src/main/res/drawable-hdpi/thousands_of_currencies.png
new file mode 100644
index 0000000000..af3bf1ca51
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/thousands_of_currencies.png differ
diff --git a/app/src/main/res/drawable-hdpi/wallet_for_everyone.png b/app/src/main/res/drawable-hdpi/wallet_for_everyone.png
new file mode 100644
index 0000000000..6eec2f53dd
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/wallet_for_everyone.png differ
diff --git a/app/src/main/res/drawable-it/buy_with_googlepay_button_content.xml b/app/src/main/res/drawable-it/buy_with_googlepay_button_content.xml
new file mode 100755
index 0000000000..477793d6e5
--- /dev/null
+++ b/app/src/main/res/drawable-it/buy_with_googlepay_button_content.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-ldpi/card_placeholder_new.png b/app/src/main/res/drawable-ldpi/card_placeholder_new.png
deleted file mode 100644
index e9986ae27f..0000000000
Binary files a/app/src/main/res/drawable-ldpi/card_placeholder_new.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/card_placeholder_new.png b/app/src/main/res/drawable-mdpi/card_placeholder_new.png
deleted file mode 100644
index b15e1cf3bb..0000000000
Binary files a/app/src/main/res/drawable-mdpi/card_placeholder_new.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/card_placeholder_wallet.9.png b/app/src/main/res/drawable-mdpi/card_placeholder_wallet.9.png
new file mode 100644
index 0000000000..8a45ec562c
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/card_placeholder_wallet.9.png differ
diff --git a/app/src/main/res/drawable-mdpi/floating_cards.png b/app/src/main/res/drawable-mdpi/floating_cards.png
new file mode 100644
index 0000000000..9ff5cb944a
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/floating_cards.png differ
diff --git a/app/src/main/res/drawable-mdpi/meet_tangem.png b/app/src/main/res/drawable-mdpi/meet_tangem.png
new file mode 100644
index 0000000000..ef04e35830
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/meet_tangem.png differ
diff --git a/app/src/main/res/drawable-mdpi/revolutionary_wallet.png b/app/src/main/res/drawable-mdpi/revolutionary_wallet.png
new file mode 100644
index 0000000000..4dba3bffa4
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/revolutionary_wallet.png differ
diff --git a/app/src/main/res/drawable-mdpi/thousands_of_currencies.png b/app/src/main/res/drawable-mdpi/thousands_of_currencies.png
new file mode 100644
index 0000000000..4f448c7df8
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/thousands_of_currencies.png differ
diff --git a/app/src/main/res/drawable-mdpi/wallet_for_everyone.png b/app/src/main/res/drawable-mdpi/wallet_for_everyone.png
new file mode 100644
index 0000000000..9ec8c92210
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/wallet_for_everyone.png differ
diff --git a/app/src/main/res/drawable-mdpi/web_3.png b/app/src/main/res/drawable-mdpi/web_3.png
new file mode 100644
index 0000000000..df3245ff41
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/web_3.png differ
diff --git a/app/src/main/res/drawable-ru/buy_with_googlepay_button_content.xml b/app/src/main/res/drawable-ru/buy_with_googlepay_button_content.xml
new file mode 100755
index 0000000000..a624bdabf1
--- /dev/null
+++ b/app/src/main/res/drawable-ru/buy_with_googlepay_button_content.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-xhdpi/card_placeholder_new.png b/app/src/main/res/drawable-xhdpi/card_placeholder_new.png
deleted file mode 100644
index a9f3c750e0..0000000000
Binary files a/app/src/main/res/drawable-xhdpi/card_placeholder_new.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/card_placeholder_wallet.9.png b/app/src/main/res/drawable-xhdpi/card_placeholder_wallet.9.png
new file mode 100644
index 0000000000..ee8fdfd5f2
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/card_placeholder_wallet.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/floating_cards.png b/app/src/main/res/drawable-xhdpi/floating_cards.png
new file mode 100644
index 0000000000..3445299a6f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/floating_cards.png differ
diff --git a/app/src/main/res/drawable-xhdpi/googlepay_button_background_image.9.png b/app/src/main/res/drawable-xhdpi/googlepay_button_background_image.9.png
new file mode 100755
index 0000000000..40f767d533
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/googlepay_button_background_image.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/googlepay_button_no_shadow_background_image.9.png b/app/src/main/res/drawable-xhdpi/googlepay_button_no_shadow_background_image.9.png
new file mode 100755
index 0000000000..969cc52a94
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/googlepay_button_no_shadow_background_image.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/meet_tangem.png b/app/src/main/res/drawable-xhdpi/meet_tangem.png
new file mode 100644
index 0000000000..958f83e0a6
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/meet_tangem.png differ
diff --git a/app/src/main/res/drawable-xhdpi/revolutionary_wallet.png b/app/src/main/res/drawable-xhdpi/revolutionary_wallet.png
new file mode 100644
index 0000000000..e72d2ff281
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/revolutionary_wallet.png differ
diff --git a/app/src/main/res/drawable-xhdpi/thousands_of_currencies.png b/app/src/main/res/drawable-xhdpi/thousands_of_currencies.png
new file mode 100644
index 0000000000..cd226e8bf3
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/thousands_of_currencies.png differ
diff --git a/app/src/main/res/drawable-xhdpi/wallet_for_everyone.png b/app/src/main/res/drawable-xhdpi/wallet_for_everyone.png
new file mode 100644
index 0000000000..773a3bb463
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/wallet_for_everyone.png differ
diff --git a/app/src/main/res/drawable-xhdpi/web_3.png b/app/src/main/res/drawable-xhdpi/web_3.png
new file mode 100644
index 0000000000..8af4c9c432
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/web_3.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/card_placeholder_new.png b/app/src/main/res/drawable-xxhdpi/card_placeholder_new.png
deleted file mode 100644
index 0caf7a0d35..0000000000
Binary files a/app/src/main/res/drawable-xxhdpi/card_placeholder_new.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/card_placeholder_wallet.9.png b/app/src/main/res/drawable-xxhdpi/card_placeholder_wallet.9.png
new file mode 100644
index 0000000000..7483c7232b
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/card_placeholder_wallet.9.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/floating_cards.png b/app/src/main/res/drawable-xxhdpi/floating_cards.png
new file mode 100644
index 0000000000..000db85171
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/floating_cards.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/googlepay_button_background_image.9.png b/app/src/main/res/drawable-xxhdpi/googlepay_button_background_image.9.png
new file mode 100755
index 0000000000..91035e2a41
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/googlepay_button_background_image.9.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/googlepay_button_no_shadow_background_image.9.png b/app/src/main/res/drawable-xxhdpi/googlepay_button_no_shadow_background_image.9.png
new file mode 100755
index 0000000000..dda66dfd0d
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/googlepay_button_no_shadow_background_image.9.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/meet_tangem.png b/app/src/main/res/drawable-xxhdpi/meet_tangem.png
new file mode 100644
index 0000000000..4d5144346d
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/meet_tangem.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/revolutionary_wallet.png b/app/src/main/res/drawable-xxhdpi/revolutionary_wallet.png
new file mode 100644
index 0000000000..83a8bc7963
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/revolutionary_wallet.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/thousands_of_currencies.png b/app/src/main/res/drawable-xxhdpi/thousands_of_currencies.png
new file mode 100644
index 0000000000..572bb12ae8
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/thousands_of_currencies.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/wallet_for_everyone.png b/app/src/main/res/drawable-xxhdpi/wallet_for_everyone.png
new file mode 100644
index 0000000000..18b099256b
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/wallet_for_everyone.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/web_3.png b/app/src/main/res/drawable-xxhdpi/web_3.png
new file mode 100644
index 0000000000..8aa6f0b237
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/web_3.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/card_placeholder_new.png b/app/src/main/res/drawable-xxxhdpi/card_placeholder_new.png
deleted file mode 100644
index 56a6e580f0..0000000000
Binary files a/app/src/main/res/drawable-xxxhdpi/card_placeholder_new.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxxhdpi/card_placeholder_wallet.9.png b/app/src/main/res/drawable-xxxhdpi/card_placeholder_wallet.9.png
new file mode 100644
index 0000000000..3fb1bc1422
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/card_placeholder_wallet.9.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/floating_cards.png b/app/src/main/res/drawable-xxxhdpi/floating_cards.png
new file mode 100644
index 0000000000..81dd5e3119
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/floating_cards.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/googlepay_button_background_image.9.png b/app/src/main/res/drawable-xxxhdpi/googlepay_button_background_image.9.png
new file mode 100755
index 0000000000..ab905289bc
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/googlepay_button_background_image.9.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/googlepay_button_no_shadow_background_image.9.png b/app/src/main/res/drawable-xxxhdpi/googlepay_button_no_shadow_background_image.9.png
new file mode 100755
index 0000000000..e67fdaf516
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/googlepay_button_no_shadow_background_image.9.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/meet_tangem.png b/app/src/main/res/drawable-xxxhdpi/meet_tangem.png
new file mode 100644
index 0000000000..a84bb7b804
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/meet_tangem.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/revolutionary_wallet.png b/app/src/main/res/drawable-xxxhdpi/revolutionary_wallet.png
new file mode 100644
index 0000000000..bc0d94be7f
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/revolutionary_wallet.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/thousands_of_currencies.png b/app/src/main/res/drawable-xxxhdpi/thousands_of_currencies.png
new file mode 100644
index 0000000000..5e2406e2c7
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/thousands_of_currencies.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/wallet_for_everyone.png b/app/src/main/res/drawable-xxxhdpi/wallet_for_everyone.png
new file mode 100644
index 0000000000..1d99d407ab
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/wallet_for_everyone.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/web_3.png b/app/src/main/res/drawable-xxxhdpi/web_3.png
new file mode 100644
index 0000000000..988c52d448
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/web_3.png differ
diff --git a/app/src/main/res/drawable/buy_with_googlepay_button_content.xml b/app/src/main/res/drawable/buy_with_googlepay_button_content.xml
new file mode 100755
index 0000000000..a893cd0cac
--- /dev/null
+++ b/app/src/main/res/drawable/buy_with_googlepay_button_content.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/googlepay_button_background.xml b/app/src/main/res/drawable/googlepay_button_background.xml
new file mode 100755
index 0000000000..b81005b06f
--- /dev/null
+++ b/app/src/main/res/drawable/googlepay_button_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/googlepay_button_content.xml b/app/src/main/res/drawable/googlepay_button_content.xml
new file mode 100755
index 0000000000..b2cdb1d52b
--- /dev/null
+++ b/app/src/main/res/drawable/googlepay_button_content.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/googlepay_button_no_shadow_background.xml b/app/src/main/res/drawable/googlepay_button_no_shadow_background.xml
new file mode 100755
index 0000000000..9543c4bcb6
--- /dev/null
+++ b/app/src/main/res/drawable/googlepay_button_no_shadow_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/googlepay_button_overlay.xml b/app/src/main/res/drawable/googlepay_button_overlay.xml
new file mode 100755
index 0000000000..70c7a92271
--- /dev/null
+++ b/app/src/main/res/drawable/googlepay_button_overlay.xml
@@ -0,0 +1,12 @@
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_clear_24.xml b/app/src/main/res/drawable/ic_clear_24.xml
new file mode 100644
index 0000000000..cd2a842a1f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_clear_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_overlay.xml b/app/src/main/res/drawable/ic_overlay.xml
new file mode 100644
index 0000000000..586397f855
--- /dev/null
+++ b/app/src/main/res/drawable/ic_overlay.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_promo_code.xml b/app/src/main/res/drawable/ic_promo_code.xml
new file mode 100644
index 0000000000..149d59df21
--- /dev/null
+++ b/app/src/main/res/drawable/ic_promo_code.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_shipping.xml b/app/src/main/res/drawable/ic_shipping.xml
new file mode 100644
index 0000000000..b36ffe1bed
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shipping.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_line.xml b/app/src/main/res/drawable/shape_line.xml
new file mode 100644
index 0000000000..cf7b6cfb0f
--- /dev/null
+++ b/app/src/main/res/drawable/shape_line.xml
@@ -0,0 +1,8 @@
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_rectangle_rounded_4.xml b/app/src/main/res/drawable/shape_rectangle_rounded_4.xml
new file mode 100644
index 0000000000..b7d03c2907
--- /dev/null
+++ b/app/src/main/res/drawable/shape_rectangle_rounded_4.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/buy_with_googlepay_button.xml b/app/src/main/res/layout/buy_with_googlepay_button.xml
new file mode 100755
index 0000000000..2553181fd8
--- /dev/null
+++ b/app/src/main/res/layout/buy_with_googlepay_button.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_onboarding_address_info.xml b/app/src/main/res/layout/dialog_onboarding_address_info.xml
index 55617e452f..ca04440067 100644
--- a/app/src/main/res/layout/dialog_onboarding_address_info.xml
+++ b/app/src/main/res/layout/dialog_onboarding_address_info.xml
@@ -24,7 +24,7 @@
app:layout_constraintTop_toBottomOf="@+id/pseudo_toolbar" />
+ app:title="@string/add_tokens_title" />
@@ -51,7 +51,7 @@
android:layout_marginBottom="16dp"
android:enabled="false"
android:gravity="center"
- android:text="Save changes"
+ android:text="@string/common_save_changes"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml
index b6475a8ac3..58ac038846 100644
--- a/app/src/main/res/layout/fragment_details.xml
+++ b/app/src/main/res/layout/fragment_details.xml
@@ -133,32 +133,39 @@
app:layout_constraintTop_toBottomOf="@id/tv_issuer"
tools:text="48 hashes" />
-
-
-
+ app:layout_constraintTop_toBottomOf="@+id/tv_signed_hashes_title">
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/ll_manage_security" />
+ app:title="@string/details_manage_security_title" />
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_onboarding_main.xml b/app/src/main/res/layout/fragment_onboarding_main.xml
index 6d567ce91b..82b94a5def 100644
--- a/app/src/main/res/layout/fragment_onboarding_main.xml
+++ b/app/src/main/res/layout/fragment_onboarding_main.xml
@@ -33,11 +33,12 @@
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
-
+ android:layout_weight="@dimen/home_bottom_weight" />
diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml
index 5df3b30845..904ab50059 100644
--- a/app/src/main/res/layout/fragment_onboarding_wallet.xml
+++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml
@@ -23,9 +23,9 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
+ app:menu="@menu/shop"
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
- app:title="@string/onboarding_getting_started"
- app:menu="@menu/shop"/>
+ app:title="@string/onboarding_getting_started" />
@@ -33,6 +33,7 @@
android:id="@+id/onboarding_main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:clipChildren="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
@@ -51,28 +51,25 @@
android:id="@+id/imv_card_background"
android:layout_width="236dp"
android:layout_height="236dp"
- android:layout_gravity="center"
android:adjustViewBounds="true"
android:background="@drawable/shape_circle"
android:backgroundTint="@color/lightGray0"
android:elevation="0dp"
android:scaleType="fitCenter"
- app:layout_constraintStart_toStartOf="@id/fl_cards_container"
- app:layout_constraintEnd_toEndOf="@id/fl_cards_container"
- app:layout_constraintTop_toTopOf="@id/fl_cards_container"
app:layout_constraintBottom_toBottomOf="@id/fl_cards_container"
- android:layout_marginBottom="16dp"
- />
+ app:layout_constraintEnd_toEndOf="@id/fl_cards_container"
+ app:layout_constraintStart_toStartOf="@id/fl_cards_container"
+ app:layout_constraintTop_toTopOf="@id/fl_cards_container" />
@@ -81,36 +78,33 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
- android:src="@drawable/card_placeholder_new"
- android:visibility="gone"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:scaleType="fitCenter"
-
- />
+ android:src="@drawable/card_placeholder_black"
+ android:visibility="gone" />
+ android:src="@drawable/card_placeholder_black"
+ android:visibility="gone" />
+ android:src="@drawable/card_placeholder_black"
+ android:transitionName="imv_front_card" />
@@ -119,12 +113,12 @@
android:layout_width="142dp"
android:layout_height="142dp"
android:src="@drawable/ic_activation_success"
+ android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/guideline2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.495"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/pb_state"
- android:visibility="gone"/>
+ app:layout_constraintTop_toBottomOf="@id/pb_state" />
+ app:layout_constraintTop_toTopOf="@+id/guideline2"
+ tools:text="Добавлена одна резервная карта" />
+ app:layout_constraintTop_toBottomOf="@+id/tv_header"
+ tools:text="Body" />
+ app:layout_constraintTop_toTopOf="@+id/guideline2" />
+ app:tabPaddingStart="8dp" />
+ app:layout_constraintBottom_toBottomOf="parent" />
diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml
index 78131a2e15..1026392257 100644
--- a/app/src/main/res/layout/fragment_send.xml
+++ b/app/src/main/res/layout/fragment_send.xml
@@ -39,12 +39,14 @@
android:orientation="vertical">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
index 05af6fcb26..35b4a700af 100644
--- a/app/src/main/res/layout/item_currency_wallet.xml
+++ b/app/src/main/res/layout/item_currency_wallet.xml
@@ -20,7 +20,7 @@
android:layout_height="wrap_content"
android:minHeight="76dp">
-
+ app:layout_constraintTop_toTopOf="@id/guideline"
+ tools:text="@string/lorem_ipsum" />
diff --git a/app/src/main/res/layout/item_popular_token.xml b/app/src/main/res/layout/item_popular_token.xml
index 3f03f0279f..c3904baa73 100644
--- a/app/src/main/res/layout/item_popular_token.xml
+++ b/app/src/main/res/layout/item_popular_token.xml
@@ -8,7 +8,7 @@
android:background="@android:color/white"
android:minHeight="60dp">
-
-
+
-
+
-
+
diff --git a/app/src/main/res/layout/layout_backup_access_code_info.xml b/app/src/main/res/layout/layout_backup_access_code_info.xml
index ac5a98171f..3ef7ba57fb 100644
--- a/app/src/main/res/layout/layout_backup_access_code_info.xml
+++ b/app/src/main/res/layout/layout_backup_access_code_info.xml
@@ -8,18 +8,18 @@
android:id="@+id/iv_access_code_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="25dp"
+ android:layout_marginTop="@dimen/access_code_info_protect_margin_top"
android:src="@drawable/ic_access_code"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
\ 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
index 56bf9b2d69..29a44311c8 100644
--- a/app/src/main/res/layout/layout_balance_wallet_details.xml
+++ b/app/src/main/res/layout/layout_balance_wallet_details.xml
@@ -4,8 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="24dp">
+ android:layout_height="120dp">
-
+ tools:src="@drawable/shape_circle" />
+ tools:text="J" />
+ app:layout_constraintTop_toBottomOf="@id/tv_fiat_amount"
+ tools:text="@string/wallet_balance_verified" />
-
-
+ app:layout_constraintTop_toBottomOf="@id/tv_fiat_amount"
+ tools:text="@string/lorem_ipsum" />
-
-
-
+ app:constraint_referenced_ids="tv_amount, tv_fiat_amount, tv_status" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_onboarding_buttons_common.xml b/app/src/main/res/layout/layout_onboarding_buttons_common.xml
index ff0472d121..e99f7dfe4a 100644
--- a/app/src/main/res/layout/layout_onboarding_buttons_common.xml
+++ b/app/src/main/res/layout/layout_onboarding_buttons_common.xml
@@ -1,33 +1,33 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ app:layout_constraintStart_toStartOf="parent"
+ tools:text="Main Action" />
+ app:layout_constraintStart_toStartOf="parent"
+ tools:text="Alternative action" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_onboarding_container_bottom.xml b/app/src/main/res/layout/layout_onboarding_container_bottom.xml
index d5e9dbcc4a..08c351d6fb 100644
--- a/app/src/main/res/layout/layout_onboarding_container_bottom.xml
+++ b/app/src/main/res/layout/layout_onboarding_container_bottom.xml
@@ -10,14 +10,14 @@
android:id="@+id/linearLayoutCompat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="8dp"
+ android:layout_marginBottom="@dimen/home_bottom_llCompat_margin_bottom"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/fl_scan_card_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.1">
+ app:layout_constraintVertical_bias="@dimen/home_bottom_llCompat_constraint_vertical_bias">
-
+
@@ -29,8 +35,10 @@
android:elevation="1dp"
android:rotation="-3"
android:scaleType="fitXY"
+ android:scaleX="@dimen/home_top_front_card_scaleX"
+ android:scaleY="@dimen/home_top_front_card_scaleY"
android:src="@drawable/card_placeholder_black"
- android:translationY="@dimen/home_front_card_translationY"
+ android:translationY="@dimen/home_top_front_card_translationY"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/app/src/main/res/layout/layout_send_fee.xml b/app/src/main/res/layout/layout_send_fee.xml
index d1c30381f6..f54f8f8bfe 100644
--- a/app/src/main/res/layout/layout_send_fee.xml
+++ b/app/src/main/res/layout/layout_send_fee.xml
@@ -1,6 +1,7 @@
-
+ android:clipChildren="false">
-
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:translationY="-8dp"
+ app:selectionRequired="true"
+ app:singleLine="true"
+ app:singleSelection="true">
-
+
-
+
-
+
+
+
+
-
+ app:layout_constraintGuide_percent="0.40" />
+ tools:text="139mrsJgyWnJjkljlkhiuojlkljkjljljlkjlkfsdkdsjflsdkjflsdkjfffffffljlkjkljlkjlkjkljky9BV" />
-
-
-
-
+ app:iconEndPadding="0dp"
+ app:iconStartPadding="12dp"
+ app:layout_constraintEnd_toStartOf="@+id/btn_share"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/iv_qr_code" />
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toEndOf="@+id/btn_copy"
+ app:layout_constraintTop_toTopOf="@+id/btn_copy" />
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 d63cb4681a..fa2a403d60 100644
--- a/app/src/main/res/layout/layout_wallet_long_buttons.xml
+++ b/app/src/main/res/layout/layout_wallet_long_buttons.xml
@@ -27,7 +27,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintGuide_percent="0.37" />
+ app:layout_constraintGuide_percent="0.5" />
-
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/lp_onboarding_done_activation.xml b/app/src/main/res/layout/lp_onboarding_done_activation.xml
index 1ed62c57f9..70be6417e0 100644
--- a/app/src/main/res/layout/lp_onboarding_done_activation.xml
+++ b/app/src/main/res/layout/lp_onboarding_done_activation.xml
@@ -12,6 +12,7 @@
android:visibility="gone" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml
new file mode 100644
index 0000000000..fa5d2dc4b3
--- /dev/null
+++ b/app/src/main/res/values-de/strings_final.xml
@@ -0,0 +1,38 @@
+
+
+ Tangem Wallet
+ 3 cards
+ 2 cards
+ Shipping
+ Free
+ I have a promo code…
+ Total
+ Other payment methods
+ Buy now
+ Placing your order
+ Your order
+ Meet\nTangem
+ Buy
+ Store
+ Send
+ Pay
+ Exchange
+ Lend
+ Borrow
+ Stake
+ Revolutionary Hardware Wallet
+ Store your crypto assets secure while keeping private keys contained in your card
+ Ultra Secure Backup
+ Up to 3 physical cards to one wallet
+ Thousands of Currencies
+ A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card
+ DeFi Compatible
+ Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services
+ The Wallet for Everyone
+ Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.
+ Order card
+ Search tokens
+ Search tokens
+ You are currently running in Demo mode. All funds are not real.
+ This feature is disabled in Demo mode
+
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 29e248d8d7..9e6777a914 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -9,8 +9,8 @@
Annuler
J\'accepte
- L’accès à la caméra est refusé
- Vous n\'avez pas octroyé l’accès à votre caméra, veuillez modifier vos paramètres de confidentialité
+ L\'accès à la caméra est refusé
+ 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. \nScannez votre carte pour commencer.
@@ -29,7 +29,7 @@
Solde confirmé
Solde est en cours de téléchargement …
Blockchain est inaccessible
- Compte n’est pas créé
+ Compte n\'est pas créé
Téléchargez %s +%s pour créer un compte
Carte vide
Créez un portefeuille pour commencer à utiliser votre carte Tangem
@@ -37,7 +37,7 @@
Votre carte Tangem a été créée pour fonctionner avec une autre application. Regardez le nom et les instructions sur votre carte et installez l\'application pertinente
Transaction en cours…
Envoi\u0020
- jusqu’à %s
+ jusqu\'à %s
Réception \u0020
de %s
Explorer l\'adresse
@@ -80,7 +80,7 @@
Emetteur
Signé
%s hashes
- Monnaie de l’application
+ Monnaie de l\'application
Paramètres
Carte
Valider la carte
@@ -93,9 +93,9 @@
Ce mécanisme protège contre les attaques sans contact sur la carte. Il y a un délai entre la réception et l\'exécution de la commande. Après la première transaction signée, ce téléphone sera associé à la carte et les transactions seront signées immédiatement.
Mot de passe
Avant d\'exécuter une commande qui modifie l\'état de la carte, vous devrez entrer un mot de passe.
- Code d’accès
+ 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
+ Vous pouvez rencontrer des problèmes NFC avec certains iPhone 7/7 + lors de l\'extraction
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
@@ -105,7 +105,7 @@
Appuyez votre carte au téléphone comme indiqué ci-dessus
Touchez, pour annuler un portefeuille
Touchez, pour créer un portefeuille
- Touchez, pour modifier le code d’accès
+ Touchez, pour modifier le code d\'accès
Touchez, pour modifier le mot de passe
PayString incorrect
@@ -115,44 +115,44 @@ Avertissement
\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 :
+\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 :
-\n\na. Génération, stockage, transmission ou octroi d\’accès aux clés privées (confidentiels) de chiffrement des blockchain-portefeuilles, qui contiennent les avoirs numériques, y compris la crypto-monnaie.
-\n\nb. Génération, stockage, transmission ou octroi d\’accès aux clés confidentielles, aux mots de passe, phrases secrètes, phrases de restitution, qui pourront être utilisées pour la restitution ou pour le clonage des clés privées (confidentiels) de chiffrement des blockchain-portefeuilles, contenant les avoirs numériques, y compris la crypto-monnaie.
-\n\nc. Prestation des services de bourse, de commerce, d\’investissement du nom de Tangem AG.
+\n\na. Génération, stockage, transmission ou octroi d\'accès aux clés privées (confidentiels) de chiffrement des blockchain-portefeuilles, qui contiennent les avoirs numériques, y compris la crypto-monnaie.
+\n\nb. Génération, stockage, transmission ou octroi d\'accès aux clés confidentielles, aux mots de passe, phrases secrètes, phrases de restitution, qui pourront être utilisées pour la restitution ou pour le clonage des clés privées (confidentiels) de chiffrement des blockchain-portefeuilles, contenant les avoirs numériques, y compris la crypto-monnaie.
+\n\nc. Prestation des services de bourse, de commerce, d\'investissement du nom de Tangem AG.
-\n\n2. Risques, liés avec l\’utilisation du logiciel
+\n\n2. Risques, liés avec l\'utilisation du logiciel
-\n\nTangem n\’assume aucune responsabilité pour tous les dommages, préjudice ou prétentions, qui peuvent survenir comme résultat des évènements, se rapportant à une de cinq catégories suivantes :
+\n\nTangem n\'assume aucune responsabilité pour tous les dommages, préjudice ou prétentions, qui peuvent survenir comme résultat des évènements, se rapportant à une de cinq catégories suivantes :
-\n\na. Les erreurs, commises par l\’utilisateur de tout logiciel ou service liés avec la crypto-monnaie, par exemple, les mots de passe oubliés, les paiements envoyés aux adresses incorrectes, et élimination occasionnelle des blockchain-portefeuilles sur les Cartes.
-\n\nb. Les problèmes du Logiciel et/ou de tout progiciel ou service liés avec blockchain ou avec la crypto-monnaie, par exemple, les fichiers endommagés, les transaction créées d\’une manière incorrecte, les bibliothèques cryptographiques non sécurisées, les logiciels malveillants.
-\n\nc. Les défaillances techniques du hardware de l\’utilisateur, y compris les Cartes, de tout logiciel ou service liés avec la crypto-monnaie, par exemple, la perte des données à cause des périphériques défectives ou endommagées de stockage.
-\n\nd. Les problèmes de la sécurité, subis par l\’utilisateur de tout logiciel ou service liés avec la crypto-monnaie, par exemple, l\’accès non autorisé aux portefeuilles et/ou comptes des utilisateurs.
-\n\ne. Les actions ou l\’inaction des tierces personnes et/ou les évènements subis par les tierces personnes, par exemple, la faillite du prestataire de services, les attaques sur la sécurité informatique des prestataires de services et la fraude commise par des tierces personnes.
+\n\na. Les erreurs, commises par l\'utilisateur de tout logiciel ou service liés avec la crypto-monnaie, par exemple, les mots de passe oubliés, les paiements envoyés aux adresses incorrectes, et élimination occasionnelle des blockchain-portefeuilles sur les Cartes.
+\n\nb. Les problèmes du Logiciel et/ou de tout progiciel ou service liés avec blockchain ou avec la crypto-monnaie, par exemple, les fichiers endommagés, les transaction créées d\'une manière incorrecte, les bibliothèques cryptographiques non sécurisées, les logiciels malveillants.
+\n\nc. Les défaillances techniques du hardware de l\'utilisateur, y compris les Cartes, de tout logiciel ou service liés avec la crypto-monnaie, par exemple, la perte des données à cause des périphériques défectives ou endommagées de stockage.
+\n\nd. Les problèmes de la sécurité, subis par l\'utilisateur de tout logiciel ou service liés avec la crypto-monnaie, par exemple, l\'accès non autorisé aux portefeuilles et/ou comptes des utilisateurs.
+\n\ne. Les actions ou l\'inaction des tierces personnes et/ou les évènements subis par les tierces personnes, par exemple, la faillite du prestataire de services, les attaques sur la sécurité informatique des prestataires de services et la fraude commise par des tierces personnes.
-\n\n3. Risques commerciales et d\’investissement
+\n\n3. Risques commerciales et d\'investissement
-\n\nToute échange de crypto-monnaie ou d\’autres avoirs numériques implique les risques importants. Toute transaction liée avec la monnaie implique les risques, qui incluent, entre autres, la possibilité de modification des conditions économiques, qui peuvent influencer d\’une manière significative le prix ou la disponibilité de la monnaie. Les investissements dans les spéculations boursières avec les crypto-monnaies peuvent aussi être exposés aux montées et chutes en flèche lors des fluctuations des valeurs marchandes correspondants. C\'est pour cette raison, que lors de la spéculation sur tels marchés, il est conseillé d\’utiliser seulement le capital-risque.
+\n\nToute échange de crypto-monnaie ou d\'autres avoirs numériques implique les risques importants. Toute transaction liée avec la monnaie implique les risques, qui incluent, entre autres, la possibilité de modification des conditions économiques, qui peuvent influencer d\'une manière significative le prix ou la disponibilité de la monnaie. Les investissements dans les spéculations boursières avec les crypto-monnaies peuvent aussi être exposés aux montées et chutes en flèche lors des fluctuations des valeurs marchandes correspondants. C\'est pour cette raison, que lors de la spéculation sur tels marchés, il est conseillé d\'utiliser seulement le capital-risque.
\n\n4. Risques de commerce électronique
-\n\nAvant de passer aux transactions en utilisant le système électronique, Vous devez prendre connaissance, d\’une manière bien attentive, des règles et des dispositions stipulées par les bourses qui offrent ce système, et/ou les listes des titres de placement que vous voulez négocier. Le commerce en ligne possède le risque inaliénable lié avec la vitesse de réponse du système et avec le temps d\’accès qui peuvent varier en fonction des conditions de marché, du rendement du système et d\’autres facteurs. Avant de commencer les négoces, vous devez comprendre ces risques aussi que les risques supplémentaires.
+\n\nAvant de passer aux transactions en utilisant le système électronique, Vous devez prendre connaissance, d\'une manière bien attentive, des règles et des dispositions stipulées par les bourses qui offrent ce système, et/ou les listes des titres de placement que vous voulez négocier. Le commerce en ligne possède le risque inaliénable lié avec la vitesse de réponse du système et avec le temps d\'accès qui peuvent varier en fonction des conditions de marché, du rendement du système et d\'autres facteurs. Avant de commencer les négoces, vous devez comprendre ces risques aussi que les risques supplémentaires.
\n\n5. Respect des obligations fiscales
-\n\nLes utilisateurs du Logiciel assument toute la responsabilité pour définir, quels impôts sont appliqués à leurs transactions avec la crypto-monnaie, au cas où il y a de tels impôts. Les propriétaires et les participants à l\’élaboration du Logiciel n\’assument pas la responsabilité pour la définition des impôts qui sont appliqués aux transactions avec la crypto-monnaie.
+\n\nLes utilisateurs du Logiciel assument toute la responsabilité pour définir, quels impôts sont appliqués à leurs transactions avec la crypto-monnaie, au cas où il y a de tels impôts. Les propriétaires et les participants à l\'élaboration du Logiciel n\'assument pas la responsabilité pour la définition des impôts qui sont appliqués aux transactions avec la crypto-monnaie.
\n\n6. Absence de garanties
-\n\nLe logiciel est concédé sous les conditions « tel quel », sans aucune garantie à l\’égard du Logiciel et/ou de tout le contenu, données, matériaux et/ou services, présentes dans ce Logiciel.
+\n\nLe logiciel est concédé sous les conditions « tel quel », sans aucune garantie à l\'égard du Logiciel et/ou de tout le contenu, données, matériaux et/ou services, présentes dans ce Logiciel.
\n\n7. Restriction de la responsabilité
-\n\nSauf disposition contraire prévue par la législation, les propriétaires et les participants à l\’élaboration du Logiciel n\’assument pas la responsabilité pour aucun dommage, y compris l\’indisponibilité, la perte de profit ou la perte des données, qui surviennent comme résultat de l\’utilisation ou bien qui sont liés d\’une certaine manière avec l\’utilisation du Logiciel. Les propriétaires et les participants à l\’élaboration du Logiciel n\’assument pas la responsabilité pour les actions, les résolutions ou pour une autre conduite que vous manifestez ou pas par l\’intermédiaire du Logiciel.
+\n\nSauf disposition contraire prévue par la législation, les propriétaires et les participants à l\'élaboration du Logiciel n\'assument pas la responsabilité pour aucun dommage, y compris l\'indisponibilité, la perte de profit ou la perte des données, qui surviennent comme résultat de l\'utilisation ou bien qui sont liés d\'une certaine manière avec l\'utilisation du Logiciel. Les propriétaires et les participants à l\'élaboration du Logiciel n\'assument pas la responsabilité pour les actions, les résolutions ou pour une autre conduite que vous manifestez ou pas par l\'intermédiaire du Logiciel.
\n\n8. Dernière modification
@@ -171,14 +171,14 @@ Avertissement
PayString non pris en charge par la blockchain
PayString non enregistré
La demande de PayString a échoué
- L’adresse est la même que celle de votre portefeuille
+ L\'adresse est la même que celle de votre portefeuille
Solde insuffisant
Erreur interne de la blockchain
Commission non valide
Le montant minimal est de %ы
Le reste est trop petit
- L’adresse a été copiée avec succès
- Saisissez d’abord le PayString requis
+ L\'adresse a été copiée avec succès
+ Saisissez d\'abord le PayString requis
Posez pour scanner
Posez la carte
Votre solde sur ce portefeuille n\'est pas nul, ou vous avez des transactions non confirmées. Impossible de supprimer la fonction du portefeuille
diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml
new file mode 100644
index 0000000000..fa5d2dc4b3
--- /dev/null
+++ b/app/src/main/res/values-fr/strings_final.xml
@@ -0,0 +1,38 @@
+
+
+ Tangem Wallet
+ 3 cards
+ 2 cards
+ Shipping
+ Free
+ I have a promo code…
+ Total
+ Other payment methods
+ Buy now
+ Placing your order
+ Your order
+ Meet\nTangem
+ Buy
+ Store
+ Send
+ Pay
+ Exchange
+ Lend
+ Borrow
+ Stake
+ Revolutionary Hardware Wallet
+ Store your crypto assets secure while keeping private keys contained in your card
+ Ultra Secure Backup
+ Up to 3 physical cards to one wallet
+ Thousands of Currencies
+ A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card
+ DeFi Compatible
+ Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services
+ The Wallet for Everyone
+ Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.
+ Order card
+ Search tokens
+ Search tokens
+ You are currently running in Demo mode. All funds are not real.
+ This feature is disabled in Demo mode
+
\ No newline at end of file
diff --git a/app/src/main/res/values-h680dp/dimens.xml b/app/src/main/res/values-h680dp/dimens.xml
index b8c8d265ed..7a03c1b4cf 100644
--- a/app/src/main/res/values-h680dp/dimens.xml
+++ b/app/src/main/res/values-h680dp/dimens.xml
@@ -1,9 +1,38 @@
- -10dp
- 0dp
+ 1.0
+
+
+ - 1.1
+ -10dp
+ - 1
+ 1
+ 0dp
+ - 1
+ - 1
+
+
+ - 1
+ 16dp
+ - 0.4
>
+ 14dp
+ 64dp
+ 64dp
+ 90dp
+ 14dp
+
+
16dp
106dp
+ 230dp
67dp
+ - 0.56
+
+
+ 80dp
+ 24dp
+ 20dp
+ 32dp
+ 80dp
diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml
new file mode 100644
index 0000000000..fa5d2dc4b3
--- /dev/null
+++ b/app/src/main/res/values-it/strings_final.xml
@@ -0,0 +1,38 @@
+
+
+ Tangem Wallet
+ 3 cards
+ 2 cards
+ Shipping
+ Free
+ I have a promo code…
+ Total
+ Other payment methods
+ Buy now
+ Placing your order
+ Your order
+ Meet\nTangem
+ Buy
+ Store
+ Send
+ Pay
+ Exchange
+ Lend
+ Borrow
+ Stake
+ Revolutionary Hardware Wallet
+ Store your crypto assets secure while keeping private keys contained in your card
+ Ultra Secure Backup
+ Up to 3 physical cards to one wallet
+ Thousands of Currencies
+ A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card
+ DeFi Compatible
+ Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services
+ The Wallet for Everyone
+ Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.
+ Order card
+ Search tokens
+ Search tokens
+ You are currently running in Demo mode. All funds are not real.
+ This feature is disabled in Demo mode
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000000..c54c69a74d
--- /dev/null
+++ b/app/src/main/res/values-ru/strings.xml
@@ -0,0 +1,374 @@
+
+
+ Tangem
+ Нет
+ Сохранить изменения
+ Предупреждение
+ Повторить попытку
+ Готово
+ Отмена
+ Принять
+ %s
+ Доступ к камере запрещен
+ Вы не предоставили доступ к своей камере, измените настройки конфиденциальности
+ Настройки
+ Добро пожаловать в Tangem.\nУ Вас есть одна из наших карт?
+ Добро пожаловать обратно в Тангем. \nОтсканируйте карту, чтобы начать.
+ Магазин
+ Приготовьтесь приложить свою\nкарту к задней панели\nтелефона
+ Нажмите на
+ Сканировать карту
+ Да! Отсканировать.
+ Сканировать
+ Отправить
+ Создать кошелек
+ Tangem
+ Выбрите опцию
+ Токен
+ Токены
+ Подтвержденный баланс
+ Загрузка баланса…
+ Блокчейн недоступен
+ Аккаунт не создан
+ Загрузите %1$s+ %2$s для создания учетной записи
+ Карта пуста
+ Создайте кошелек, чтобы начать пользоваться картой Tangem
+ Эта карта не поддерживается
+ Ваша карта Tangem предназначена для работы с другим приложением. Пожалуйста, ознакомьтесь с названием и инструкциями на вашей карте и установите правильное приложение.
+ Транзакция в процессе…
+ до %s
+ от %s
+ Получение\u0020
+ Отправка\u0020
+ Посмотреть адрес
+ Создать PayString
+ %s кошелек
+ Создать PayString
+ Карта: %s
+ PayString имя
+ $payid.tangem.com
+ Создать
+ Ваша PayString — это уникальная для вас информация, такая как номер телефона, электронная почта или ABN
+ PayString успешно создан и скопирован в буфер обмена
+ Ошибка при создании PayString
+ Такой PayString уже существует. Попробуйте другой.
+ Адрес или PayString
+ Адрес
+ Отправить
+ Комиссия сети
+ Сумма
+ Комиссия
+ Всего
+ Максимальная сумма
+ Неверный адрес
+ Недопустимая сумма
+ Сумма превышает баланс
+ Общая сумма превышает баланс
+ Комиссия превышает баланс
+ Низкий
+ Нормальный
+ Приоритетный
+ Включить коммисию
+ %1$s %2$s будет отправлено
+ ≈ %1$s (включая комиссию: %2$s)
+ %1$s %2$s и %3$s %4$s будут отправлены
+ Баланс: %1$s %2$s
+ Транзакция успешно подписана и отправлена в блокчейн. Баланс кошелька будет обновлен через некоторое время.
+ Подробности
+ ID карты
+ Эмитент
+ Подписано
+ %s хэшей
+ Валюта приложения
+ Настройки
+ Карта
+ Подтвердить карту
+ Управление безопасностью
+ Удалить кошелек
+ Это действие необратимо. Если после удаления кошелька кто-то отправит на него средства, то Вы не сможете их вывести.
+ Если вы забудете код, Вы потеряете возможность использовать карту. Невозможно восстановить или изменить код, если вы его потеряете.
+ Управление безопасностью
+ Длительное удержание
+ Этот механизм защищает карту от бесконтактной атаки. Это обеспечит задержку между приемом и выполнением команды. После первой подписанной транзакции этот телефон будет привязан к карте, и транзакции будут подписаны без задержки.
+ Пароль
+ Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль
+ Код доступа
+ Перед сканированием карты вам нужно будет ввести правильный код доступа
+ У Вас могут возникнуть проблемы с NFC на некоторых iPhone 7/7+ во время извлечения
+ Эта карта уже пополнялась и подписывала транзакции в прошлом. Рассмотрите возможность немедленного снятия всех средств, если Вы получили эту карту из ненадежного источника.
+ Предупреждение
+ Эта карта не предназначена для работы с этим приложением
+ Карта, которую Вы отсканировали, является картой для разработчиков. Не принимайте ее в качестве оплаты.
+ Карты Tangem, выпущенные до сентября 2019 года, в настоящее время не могут быть извлечены с помощью iPhone. Мы усердно работаем с Apple, чтобы сделать это возможным в будущих версиях iOS.
+ Нажмите, чтобы подписать
+ Приложите карту к телефону, как показано выше
+ Приложите, чтобы удалить кошелек
+ Приложите, чтобы создать кошелек
+ Приложите, чтобы изменить код доступа
+ Приложите, чтобы изменить пароль
+ Неверный PayString
+ Условия использования
+ Не удалось получить комиссию
+ Целевая учетная запись не создана. Сумма для отправки должна быть %1$s %2$s + плата за создание или больше
+ Нет соединения с интернетом
+ Неизвестная ошибка
+ Ошибка проверки PayString
+ PayString не поддерживается блокчейном
+ PayString не зарегистрирован
+ Не удалось выполнить запрос PayString
+ Адрес совпадает с адресом кошелька
+ Недостаточный баланс
+ Внутренняя ошибка блокчейна
+ Неверная комиссия
+ Минимальная сумма: %s
+ Сдача слишком мала
+ Для создания учетной записи отправьте 1+ XLM на этот адрес
+ Адрес скопирован в буфер обмена
+ Сначала введите желаемую PayString
+ Приложите, чтобы отсканировать
+ Приложите карту
+ Ваш баланс на этом кошельке не равен нулю, или у Вас есть неподтвержденные транзакции
+ Это текущая активная опция
+ Чтобы не платить повышенную комиссию при следующем пополнении кошелька, уменьшите сумму на %s XTZ
+ Уменьшить на %s XTZ
+ Нет, отправить все
+ Ошибка сканирования карты. Попробуй снова.
+ Купить
+ Продать
+ Торговать
+ По умолчанию
+ Совместимость
+ Устаревший
+ Создать кошелек
+ Создать кошелек
+ Сгенерируйте ключи кошелька на обеих картах, чтобы начать использовать свои Twin карты
+ Tangem Twin
+ Один кошелек. Две карты.
+ Одна, которую Вы держите в руках, и вторая карта с номером #%s
+ Карта %1d из %2d
+ Пересоздать Twin кошелек
+ Условия использования карты
+ Tangem Twin
+ Это действие необратимо. У Вас не будет доступа к старому кошельку.
+ Приложите Twin карту #%s
+ Подготовка карты
+ Создание кошелька
+ Процедура создания кошелька состоит из трех шагов. Вы должны пройти его до конца, иначе вам придется начинать сначала.
+ Сканировать карту #%s
+ Вы уже начали процесс пересоздания кошелька. Если Вы прервете его, вы не сможете использовать свои карты, пока не запустите его снова и не завершите пересоздание кошелька.
+ Вы приложили ту же карту. Чтобы создать кошелек, вам нужно приложить другу Twin карту.
+ Адрес кошелька успешно создан
+ Начать
+ Назад
+ Добавить
+ Удалить
+ Поиск
+ Памятка
+ Тег назначения
+ Недопустимый тег назначения. Он не будет добавлен в транзакцию.
+ Недопустимый идентификатор памятки. Он не будет добавлен в транзакцию.
+ Приложение
+ Отправить отзыв
+ Успешно отправлено
+ Спасибо за ваш отзыв
+ Ваши предложения отправлены
+ Спасибо за ваш отзыв. Мы ответим как можно скорее.
+ Не удалось отправить электронное письмо
+ Причина: %s
+ Не могу отправить транзакцию
+ Причина: %s. Хотите отправить отзыв?
+ У Вас возникли трудности со сканированием вашей карты?
+ Пожалуйста, попробуйте приложить карту точно так, как показано на анимации, или обратитесь в поддержку
+ Отмена
+ Попробуйте снова
+ Обратиться в поддержку
+ Отправить отзыв
+ Очень круто!
+ Может быть лучше
+ Один вопрос
+ Вам нравится приложение Tangem?
+ Все поля обязательны к заполнению
+ Токенов пока нет
+ Добавленные токены
+ Удалить токен %s
+ Вы уверены, что хотите удалить этот токен?
+ Добавить токены
+ Добавить токен
+ Начните поиск
+ Управление токенами
+ Добавить пользовательский
+ Пользовательские токены
+ Популярные токены
+ Добавить пользовательский токен
+ Пожалуйста заполните все поля
+ Введенное число не является допустимым десятичным числом
+ Имя
+ Символ токена
+ Адрес контракта
+ Десятичные
+ бывший USD Coin
+ бывший USDC
+ Добавлен
+ Удалить токен
+ Значок токена
+ Блокчейн
+ Управление токенами
+ Блокчейны
+ Копировать
+ Поделиться
+ + Добавить токены
+ Пожалуйста, дождитесь завершения транзакции, чтобы иметь возможность отправить средства
+ Проверка подлинности не удалась
+ Эта карта может быть производственным образцом или подделкой
+ Важная информация о безопасности \u26A0
+ Эта карта ранее подписывала транзакции
+ Узнать подробнее
+ Я понял
+ На этой карте доступно только %s подписей. Вы должны вывести все свои средства.
+ Отключить
+ Подписать
+ Подписать и отправить
+ Отклонить
+ Это тестовая карта. Не принимайте ее в качестве оплаты. Она должна использоваться только в целях тестирования и разработки.
+ Вы хотите купить или продать криптовалюту?
+ К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений
+ Добро пожаловать в Tangem
+ Самый безопасный способ покупать, использовать и\n хранить криптовалюту
+ Получить новую карту
+ Активация карты
+ Как это работает?
+ Получить криптовалюту
+ Создать кошелек
+ Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек
+ Создать кошелек
+ Пополните свой кошелек
+ Чтобы начать, просто пополните карту на любую сумму
+ Чтобы начать, просто пополните кошелек более чем на %s %s
+ Купить криптовалюту
+ Показать адрес кошелька
+ Отсканируйте адрес, чтобы пополнить кошелек
+ Успешно!
+ Ваша криптокарта активирована и готова к использованию
+ Продолжить
+ Баланс
+ Отправляйте только %s (%s) на этот адрес. Отправка любой другой валюты приведет к ее безвозвратной потере.
+ Отправляйте только %s (%s) из сети %s на этот адрес. Отправка любой другой валюты приведет к ее безвозвратной потере.
+ Если процесс повторного создания кошелька каким-либо образом прервется, вам придется начать все сначала.
+ Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас.
+ Внутренняя ошибка: не удается создать менеджер кошельков
+ Блокчейн недоступен. Попробуй позже.
+ Хорошо, понял!
+ Успешно!
+ Начнем
+ Выполнить резервное копирование
+ Получить резервную карту
+ Оставить на потом
+ Завершить процесс резервного копирования
+ + Добавить резервную карту
+ Сканировать карту #%d
+ Сканировать основную карту
+ Добавить больше карт
+ Сделайте резервную копию вашего кошелька
+ Нет резервных карт
+ Подготовьте свою карту
+ Добавлена одна резервная карта
+ Добавлены две резервные карты
+ Резервная карта #%d
+ Вы можете привязать до трех карт к одному кошельку
+ Чтобы начать процесс резервного копирования, добавьте еще максимум 2 карты
+ Подготовьте основную карту с номером %s
+ Подготовьте основную карту
+ Вы можете добавить еще одну карту или завершить процесс резервного копирования
+ Добавлено максимальное количество карт
+ Если процесс резервного копирования будет прерван каким-либо образом, вам придется начать все сначала
+ Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить?
+ Создать код доступа
+ Введите код доступа повторно
+ Защитить
+ Персонализировать
+ Восстановить
+ Вам необходимо установить единый код доступа для защиты всех ваших кошельков
+ Позже Вы сможете установить индивидуальный код доступа для каждой карты
+ Код доступа можно восстановить с помощью привязанной карты, не храните все карты в одном месте
+ Выберите любое слово, фразу или номер в качестве кода доступа
+ Код доступа должен состоять не менее чем из 4 символов
+ Введенный код доступа не соответствует исходному коду доступа
+ Создать
+ Подтвердить
+ Вы создали 2 резервные карты, и теперь эти карты готовы к использованию
+ Создание резервной копии
+ Процесс резервного копирования частично завершен. Вы не можете выйти из него сейчас.
+ Резервный кошелек
+ Идентичные карты
+ Код доступа
+ Восстановление кода доступа
+ Вы можете сделать до двух резервных копий карты Tangem Wallet
+ Все резервные карты могут использоваться как полнофункциональные с одинаковыми ключами
+ Вы сможете установить код доступа для защиты своих кошельков
+ Код доступа можно восстановить с помощью одной из резервных карт
+ Ваша карта Tangem Wallet настроена и готова к использованию
+ Вы создали 1 резервную карту, и теперь эта карта готова к использованию
+ Основная карта
+ Я понимаю
+ Статус резервного копирования
+ Активный. Количество резервных карт: %d
+ Связано. Количество резервных карт: %d
+ Нет резервной копии
+ Создать резервную копию
+ У Вас есть прерванное резервное копирование. Хотите возобновить?
+ Да, возобновить
+ Отказаться
+ Это необратимое действие
+ Если сейчас отменить резервное копирование, то придется вернуть карты до заводских настроек, чтобы начать заново
+ Возобновить резервное копирование
+ Отказаться
+ Основная карта
+ Сканировать основную карту
+ Перейти к моему кошельку
+ Карта %1s из %2s
+ Отправка %s
+ Подготовьте резервную карту с номером %s
+ Подготовьте основную карту с номером %s
+ Сброс до заводских настроек
+ Это действие необратимо. Если после сброса карты кто-то отправит на нее средства, то Вы не сможете их вывести.
+ Код доступа доступен только для карт с резервной копией
+ Сеть Solana взымает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы использовать его бесплатно.
+ %1s (%2s)
+ Эта карта не является векселем на предъявителя. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец скрывает автономную подпись, что является проблемой безопасности.\n\nНе принимайте эту карту в качестве физического платежа от кого-то, кому Вы не доверяете.\n\nВо всех остальных отношениях - это совершенно безопасно.\n\nTangem — единственный аппаратный кошелек, предлагающий защиту от подсчета подписей.
+ WalletConnect
+ Запрос на создание транзакции для %2s\n%3s\n\nСумма: %4s\nКомиссия: %5s\nВсего: %6s\nБаланс: %7s
+ Запрос на запуск сеанса для карты с идентификатором %1s\nдля %2s\n\nURL: %3s
+ Сессии WalletConnect
+ Сеанс WalletConnect открыт с %s
+ Упс. Нет сессий.
+ Нет открытых сессий WalletConnect
+ Открытая сессия
+ Карта: %s
+ Коснитесь карты, чтобы привязать ее WalletConnect
+ Не удается отправить транзакцию. Недостаточно средств.
+ Просьба подписать сообщение\nкартой %s\n\n
+ Сообщение для %s:\n%s
+ Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код
+ Вставить из буфера обмена
+ Сканировать новый код
+ Эту карту нельзя использовать для установки сеанса WalletConnect.
+ Операция не может быть завершена
+ Операция не может быть завершена. \n\nВы уже установили сеанс WalletConnect с этими параметрами.
+ Не удалось установить сеанс WalletConnect: ошибка времени выполнения. Пожалуйста, повторите попытку позже.
+ Транзакция BNB успешно подписана и отправлена в DApp
+ DApp %s, запрашивает\nподпись транзакции BNB с\nкартой: %s\n\n%s
+ Сведения о транзакции:\nОт: %s\nКому: %s\nСумма: %s
+ Торговый ордер на %s\nЦена: %s\nСумма к получению: %s\nСумма к оплате: %s
+ Мои предложения
+ Не могу отсканировать карту
+ Не могу отправить транзакцию
+ Не могу протолкнуть транзакцию
+ Обращение в поддержку Tangem
+ Обращение в поддержку
+ Расскажите, каких функций Вам не хватает, и мы постараемся Вам помочь
+ Скажите, пожалуйста, какая у Вас карта?
+ Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая маленькая деталь может помочь.
+ Привет, команда поддержки,
+ Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая деталь может быть полезной.
+ Информация ниже не является обязательной. Вы можете стереть её, если хотите.
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml
new file mode 100644
index 0000000000..288761b97c
--- /dev/null
+++ b/app/src/main/res/values-ru/strings_final.xml
@@ -0,0 +1,38 @@
+
+
+ Tangem Wallet
+ 3 карты
+ 2 карты
+ Доставка
+ Бесплатно
+ У меня есть промо-код...
+ Итого
+ Другие способы оплаты
+ Купить сейчас
+ Оформление заказа
+ Ваш заказ
+ Встречайте\nTangem
+ Покупайте
+ Храните
+ Отправляйте
+ Расплачивайтесь
+ Обменивайте
+ Вкладывайте
+ Занимайте
+ Стейкайте
+ Революционный аппаратный кошелек
+ Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.
+ Все ключи в безопасности
+ До трех карт с одним кошельком
+ Тысячи криптовалют
+ Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте
+ Поддержка DeFi
+ Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах
+ Кошелек для каждого
+ Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону.
+ Купить
+ Поиск токенов
+ Поиск токенов
+ Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие.
+ Эта функция недоступна в демонстрационном режиме
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 93cbbcca62..3318e7609d 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -29,6 +29,11 @@
#F4F5F6
#DE000000
+ #060606
+ #686868
+ #F2F2F2
+ #090E13
+
#14181D
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 496ccebf23..ce2c35b5fb 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -35,10 +35,41 @@
32sp
16dp
- -30dp
- -20dp
- 8dp
- 70dp
+ - 0.85
+
+
+ - 0.9
+ -60dp
+ - 0.9
+ - 0.9
+ -40dp
+ - 0.9
+ - 0.9
+
+
+ - 1
+ 8dp
+ - 0.1
+ 10dp
+ 34dp
+ 34dp
+ 72dp
+ 12dp
+
+
+ 0dp
+ 50dp
+ 180dp
37dp
+ - 0.48
>
+
+
+ 40dp
+ 12dp
+ 8dp
+ 16dp
+ 38dp
+ 78dp
+ 78dp
diff --git a/app/src/main/res/values/googlepay_strings.xml b/app/src/main/res/values/googlepay_strings.xml
new file mode 100755
index 0000000000..81d01c2b74
--- /dev/null
+++ b/app/src/main/res/values/googlepay_strings.xml
@@ -0,0 +1,12 @@
+
+
+ Google Pay
+ Buy with Google Pay
+ Donate With Google Pay
+ Pay With Google Pay
+ Subscribe With Google Pay
+ Book With Google Pay
+ Checkout With Google Pay
+ Order With Google Pay
+ View In Google Pay
+
\ 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 a8830f8137..c3e70dee2f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -110,8 +110,8 @@
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 is not designed to work with this app
- 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.
+ 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.
Tap to sign
Tap the card to your phone as shown above
@@ -122,7 +122,7 @@
Invalid PayString
- Legal Disclaimer
+ Legal Disclaimer
\n
\n1. Tangem application (Software)
\n
@@ -144,7 +144,7 @@
\n
\nc) Technical failures in the hardware of the user, including Cards, of any cryptocurrency-related software or service, e.g., data loss due to a faulty or damaged storage device.
\n
- \nd) Security problems experienced by the user of any cryptocurrency-related software or service, e.g., unauthorized access to users’ wallets and/or accounts.
+ \nd) Security problems experienced by the user of any cryptocurrency-related software or service, e.g., unauthorized access to users\' wallets and/or accounts.
\n
\ne) Actions or inactions of third parties and/or events experienced by third parties, e.g., bankruptcy of service providers, information security attacks on service providers, and fraud conducted by third parties.
\n
diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml
new file mode 100644
index 0000000000..fa5d2dc4b3
--- /dev/null
+++ b/app/src/main/res/values/strings_final.xml
@@ -0,0 +1,38 @@
+
+
+ Tangem Wallet
+ 3 cards
+ 2 cards
+ Shipping
+ Free
+ I have a promo code…
+ Total
+ Other payment methods
+ Buy now
+ Placing your order
+ Your order
+ Meet\nTangem
+ Buy
+ Store
+ Send
+ Pay
+ Exchange
+ Lend
+ Borrow
+ Stake
+ Revolutionary Hardware Wallet
+ Store your crypto assets secure while keeping private keys contained in your card
+ Ultra Secure Backup
+ Up to 3 physical cards to one wallet
+ Thousands of Currencies
+ A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card
+ DeFi Compatible
+ Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services
+ The Wallet for Everyone
+ Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.
+ Order card
+ Search tokens
+ Search tokens
+ You are currently running in Demo mode. All funds are not real.
+ This feature is disabled in Demo mode
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml
index bfe549e0f7..40ba641e70 100644
--- a/app/src/main/res/values/strings_untranslated.xml
+++ b/app/src/main/res/values/strings_untranslated.xml
@@ -54,7 +54,7 @@
Thank your for your feedback. We will response as soon as possible
Failed to send email
Reason: %s
- Can’t send a transaction
+ 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.
@@ -98,11 +98,7 @@
Manage tokens
Blockchains
- Ethereum tokens
- Binance Smart Chain tokens
- Binance Chain tokens
- Avalanche C-Chain tokens
- Polygon tokens
+ %s tokens
Copy
Share
+ Add tokens
@@ -115,9 +111,9 @@
Important security information \u26A0
This card has signed transactions in the past
Learn more
- This card is not a bearer note. We can’t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.
- \n\nDo not accept this card as physical payment from someone you don’t trust.
- \n\nIt’s perfectly safe in all other respects.
+ This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.
+ \n\nDo not accept this card as physical payment from someone you don\'t trust.
+ \n\nIt\'s perfectly safe in all other respects.
\n\nTangem is the only hardware wallet to offer signature count protection.
I understand
@@ -162,7 +158,7 @@
Clipboard contain WalletConnect code. Use copied value or scan QR-code
Paste from clipboard
Scan new code
- This card can’t be used to establish WalletConnect session
+ This card can\'t be used to establish WalletConnect session
The operation couldn\'t be completed
The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters.
Failed to establish WalletConnect session: timeout error. Please, try again later.
@@ -184,14 +180,14 @@
Receive crypto
Create a wallet
- Let’s generate all the keys on your card and create a secure wallet
+ Let\'s generate all the keys on your card and create a secure wallet
Create wallet
Top up your wallet
To get started, simply top up the card with any amount
To get started, simply top up the wallet with more than %s %s.
Buy crypto
- Show the wallet’s address
+ Show the wallet\'s address
Scan the address to top up your wallet
Success!
@@ -201,8 +197,8 @@
Send only %s (%s) to this address. Sending any other currency will result in its irreversible loss.
Send only %s (%s) from %s network to this address. Sending any other currency will result in its irreversible loss.
- If the process of re-creating the wallet gets interrupted in any way, you’ll have to start over.
- The twinning process is partly complete. You can’t exit it now.
+ If the process of re-creating the wallet gets interrupted in any way, you\'ll have to start over.
+ The twinning process is partly complete. You can\'t exit it now.
Internal error: can\'t create wallet manager
@@ -235,9 +231,9 @@
Prepare the primary card
You can add one more card or finalize the backup process
Max number of cards added. Finalize the backup process
- If the process of backup gets interrupted in any way you’ll have to start over
+ If the process of backup gets interrupted in any way you\'ll have to start over
- You’ve added one backup card. When backup process is finished you can’t add more backup cards. If you have one more card, add it in backup, otherwise you can buy it in shop. Do you like to continue backup process?
+ You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it in backup, otherwise you can buy it in shop. Do you like to continue backup process?
Create Access Code
Re-enter your Access Code
@@ -246,7 +242,7 @@
Restore
You have to setup a single access code to protect all yor wallets.
You can set an individual access code on each card later.
- The access code can be restored with a linked card, don’t keep all cards at one place.
+ The access code can be restored with a linked card, don\'t keep all cards at one place.
Choose any word, phrase, or number you want as your access code.
Access code must be at least 4 characters long
Entered access code didn\'t match initial access code
@@ -255,7 +251,7 @@
Submit
You have created 2 backup cards, and now these cards are ready for use.
Creating a backup
- The backup process is partly complete. You can’t exit it now.
+ The backup process is partly complete. You can\'t exit it now.
Backup wallet
Identical cards
@@ -313,8 +309,22 @@ Price: %s\n
Amount to receive: %s\n
Amount to pay: %s
- Solana network charges a rent of %s every 2 days. Accounts that can’t afford the rent are purged from the network. Deposit your account with more than %s to use it for free.
+ Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free.
%1s (%2s)
+ Type here to search
+
+ My suggestions
+ Can\'t scan a card
+ Can\'t send a transaction
+ Can\'t push a transaction
+ Tangem feedback
+ Feedback
+ Tell us what functions you are missing, and we will try to help you.
+ Please tell us what card do you have?
+ Please tell us more about your issue. Every small detail can help.
+ Hi support team,
+ Please tell us more about your issue. Every small detail can help.
+ Following information is optional. You can erase it if you don’t want to share it.
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index ba05fd093a..10c1c9cb80 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -147,6 +147,15 @@
- adjustResize
+
+
diff --git a/build.gradle b/build.gradle
index 0ac373259b..23b9e4d0bc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,7 @@ buildscript {
classpath "com.android.tools.build:gradle:${versions.build_gradle}"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
classpath "com.github.dcendents:android-maven-gradle-plugin:2.1"
- classpath 'com.google.gms:google-services:4.3.8'
+ classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
}
}