diff --git a/app/build.gradle b/app/build.gradle index aa1b8e24b1..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 { @@ -38,7 +38,7 @@ android { } debug_beta { initWith release - debuggable true + debuggable false versionNameSuffix "-beta" applicationIdSuffix ".debug" buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"prod\"' @@ -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-132' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-132' + 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" @@ -138,6 +140,15 @@ dependencies { // 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 b38eacf816..4dfca87b23 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,6 +44,7 @@ android:value="true" /> (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/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 182461aa4c..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) @@ -165,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/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index f552e835f6..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,12 +7,13 @@ 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 /** @@ -20,6 +21,7 @@ import timber.log.Timber */ suspend fun WalletManager.safeUpdate(): Result = try { if (isDemoWallet()) { + delay(500) Result.Success(wallet) } else { update() @@ -44,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/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt index ecec5fb9f4..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,8 +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) : AppDialog() - data class SimpleOkDialogRes(val headerId: Int, val messageId: Int) : AppDialog() + 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/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index d428a9d197..3da94dc568 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -4,9 +4,11 @@ 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 @@ -197,6 +199,18 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { 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" 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 index da24d1337d..a33fb82b84 100644 --- 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 @@ -1,4 +1,4 @@ -package com.tangem.tap.common.shop +package com.tangem.tap.common.shop.shopify import android.app.Application import com.shopify.buy3.GraphCallResult @@ -7,7 +7,6 @@ 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.ShopifyShop 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 @@ -85,7 +84,7 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop when (result) { is GraphCallResult.Success -> { val checkout = result.response.data?.node as? Checkout - checkout == null + checkout?.order == null } is GraphCallResult.Failure -> false } 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 9e6fcd5de6..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,9 +1,12 @@ 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 @@ -15,12 +18,16 @@ 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 @@ -32,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) @@ -53,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) } @@ -91,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 { @@ -131,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()) } } @@ -158,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())) } @@ -167,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) } @@ -194,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()) } } @@ -222,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) { @@ -231,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) @@ -246,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 4324891cae..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 @@ -11,6 +11,7 @@ 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(), @@ -81,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, @@ -94,6 +96,7 @@ class ConfigManager( defaultConfig = defaultConfig.copy( coinMarketCapKey = values.coinMarketCapKey, moonPayApiKey = values.moonPayApiKey, + onramperApiKey = values.onramperApiKey, moonPayApiSecretKey = values.moonPayApiSecretKey, blockchainSdkConfig = BlockchainSdkConfig( blockchairApiKey = values.blockchairApiKey, 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 c82d448e7d..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 @@ -16,6 +16,7 @@ class FeatureModel( class ConfigValueModel( val coinMarketCapKey: String, val moonPayApiKey: String, + val onramperApiKey: String, val moonPayApiSecretKey: String, val blockchairApiKey: String?, val blockchairAuthorizationToken: String?, 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 663bbcbd6c..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 @@ -162,7 +162,7 @@ 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, 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 29ae9d45bc..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 @@ -145,7 +145,20 @@ private class CreateWalletTangemWallet : ProductCommandProcessor { primaryCard = result.data - deriveKeys(createWalletResponse, 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)) 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 114746a06f..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 @@ -15,6 +15,7 @@ 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) { @@ -122,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 { @@ -148,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, ) } @@ -169,10 +175,11 @@ class CurrenciesRepository(val context: Application) { return excludeUnsupportedBlockchains(blockchains) } + // 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 )) } } @@ -187,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 c5e74334a4..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/topup/TopUpManager.kt +++ /dev/null @@ -1,46 +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.extensions.safeUpdate -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.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/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/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index f80e04df6b..dad1a88a3b 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -1,8 +1,10 @@ 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 @@ -102,7 +104,45 @@ class DemoConfig { ?: 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( @@ -115,10 +155,7 @@ class DemoConfig { "AC79000000000004", // Wallet 4.46 ) - private val debugTestDemoCardIds = listOf( - "AB01000000045060", // Note BTC - "AB02000000045028", // Note ETH - "AC79000000000004", // Wallet 4.46 + private val debugTestDemoCardIds = listOf( ) } @@ -127,13 +164,26 @@ class DemoTransactionSender( private val sender: TransactionSender = walletManager as TransactionSender ) : TransactionSender { - override suspend fun getFee(amount: Amount, destination: String): Result> = - sender.getFee(amount, destination) + 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 SimpleResult.Failure(Exception(ID)) + 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) 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 07d0ea9b41..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_manage_security_title) - 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 + 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 8651dbd9d3..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/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 95eada77e7..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 @@ -38,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 -> { @@ -73,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) } @@ -98,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 882158b731..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_receive_message.text = getQRReceiveMessage(tv_receive_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/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 c8482b0624..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 @@ -32,13 +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() { @@ -49,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() @@ -76,43 +69,44 @@ class TwinsCardsFragment : BaseOnboardingFragment() { super.onViewCreated(view, savedInstanceState) addBackPressHandler(this) - reconfigureLayoutForTwins() + reconfigureLayoutForTwins(binding.onboardingTopContainer) val typedValue = TypedValue() resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true) val deviceScaleFactorForWelcomeState = typedValue.float - twinsWidget = TwinsCardWidget(LeapfrogWidget(cards_container), deviceScaleFactorForWelcomeState) { + twinsWidget = TwinsCardWidget(LeapfrogWidget(binding.onboardingTopContainer.cardsContainer), deviceScaleFactorForWelcomeState) { 285f * deviceScaleFactorForWelcomeState } - btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container) + btnRefreshBalanceWidget = RefreshBalanceWidget(binding.onboardingTopContainer.onboardingMainContainer) - toolbar.title = getText(R.string.twins_recreate_toolbar) + 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 -> @@ -124,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) @@ -148,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 = "" + } } } @@ -169,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 9c61e0c402..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 @@ -125,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) { @@ -218,13 +222,11 @@ private fun handleBackupAction(appState: () -> AppState?, 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) { @@ -236,9 +238,11 @@ private fun handleBackupAction(appState: () -> AppState?, 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)) @@ -250,9 +254,11 @@ private fun handleBackupAction(appState: () -> AppState?, 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 65d8a935a8..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 @@ -8,6 +8,7 @@ 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 @@ -23,11 +24,8 @@ 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), @@ -35,6 +33,12 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), private var accessCodeDialog: AccessCodeDialog? = null 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) @@ -56,19 +60,22 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), val leapfrogCalculator = PropertyCalculator( yTranslationFactor = 25f * deviceScaleFactor, ) - val leapfrog = LeapfrogWidget(fl_cards_container, leapfrogCalculator) + 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) @@ -90,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 -> { @@ -120,15 +129,15 @@ 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(false) { startPostponedEnterTransition() } } @@ -137,7 +146,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), 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) @@ -148,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) { @@ -215,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() { @@ -266,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() @@ -278,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) @@ -324,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) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt index eb5ae173af..9f42b3be0b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt @@ -71,7 +71,7 @@ class WalletCardsWidget( ) leapfrogWidget.fold { animator.doOnEnd { -// leapfrogWidget.initViews() + leapfrogWidget.initViews() leapfrogWidget.unfold() } animator.start() 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 34a63dd905..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 @@ -246,10 +246,11 @@ private fun sendTransaction( 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_tx_send - )) + R.string.alert_demo_feature_disabled + ) { dispatch(NavigationAction.PopBackTo()) }) } else -> { (sendResult.error as? TangemSdkError)?.let { error -> 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 index 9f7e7937b4..500cc39285 100644 --- 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 @@ -34,5 +34,7 @@ sealed class ShopAction : Action { 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 index 605d5fe9d2..464a99de96 100644 --- 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 @@ -110,7 +110,18 @@ private fun handle(action: Action) { } 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 index 8d4b4ff2a7..ee12f53109 100644 --- 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 @@ -63,6 +63,7 @@ private fun internalReduce(action: Action, state: ShopState): ShopState { 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/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt index 27a20c0842..51fe6cb54c 100644 --- 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 @@ -9,6 +9,8 @@ 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 @@ -18,12 +20,15 @@ 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 kotlinx.android.synthetic.main.fragment_shop.* +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 -> @@ -44,6 +49,11 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< }) } + override fun onDestroyView() { + super.onDestroyView() + keyboardObserver.unregisterListener() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -51,41 +61,49 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< setupProductSelection() setupPromoCodeEditText() - toolbar.setNavigationOnClickListener { + binding.toolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } - - val keyboardObserver = KeyboardObserver(requireActivity()) - keyboardObserver.registerListener { isVisible -> - fl_cards.show(!isVisible) + keyboardObserver = KeyboardObserver(requireActivity()).apply { + registerListener { isVisible -> + binding.flCards.show(!isVisible) + } } } + private var cardTranslationY = 70f private fun setupCardsImages() { - imv_second.animate() - .translationY(70f) - .scaleX(0.9f) - .scaleY(0.9f) - .start() - imv_third.animate() - .translationY(140f) - .scaleX(0.8f) - .scaleY(0.8f) - .start() + 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() { - chip_product_1.setOnCheckedChangeListener { _, isChecked -> + private fun setupProductSelection() = with(binding) { + chipProduct1.setOnCheckedChangeListener { _, isChecked -> if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_3_CARDS)) } - chip_product_2.setOnCheckedChangeListener { _, isChecked -> + chipProduct2.setOnCheckedChangeListener { _, isChecked -> if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_2_CARDS)) } } - private fun setupPromoCodeEditText() { - et_promo_code.setOnEditorActionListener { view, actionId, event -> + 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 @@ -96,15 +114,15 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< return@setOnEditorActionListener false } - et_promo_code.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> + etPromoCode.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> if (!hasFocus) { - store.dispatch(ShopAction.ApplyPromoCode(et_promo_code.text.toString())) + store.dispatch(ShopAction.ApplyPromoCode(etPromoCode.text.toString())) } } } override fun newState(state: ShopState) { - if (activity == null) return + if (activity == null || view == null) return animateProductSelection(state.selectedProduct) handlePriceState(state) @@ -120,43 +138,43 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< showOrHideThirdCardWithAnimation(show) } - private fun showOrHideThirdCardWithAnimation(show: Boolean) { - val translationY = if (show) 140f else 0f - if (show) imv_third.show() - imv_third.animate() + 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) - imv_third?.show(show) + imvThird.show(show) } }) } - private fun handlePriceState(state: ShopState) { - tv_total.text = state.total - tv_total_before_discount.text = state.priceBeforeDiscount + private fun handlePriceState(state: ShopState) = with(binding) { + tvTotal.text = state.total + tvTotalBeforeDiscount.text = state.priceBeforeDiscount - pb_price.show(state.total == null) + pbPrice.show(state.total == null) } - private fun handlePromoCodeState(state: ShopState) { - if (state.promoCode == null && !et_promo_code.hasFocus()) { - et_promo_code.setText("") + private fun handlePromoCodeState(state: ShopState) = with(binding) { + if (state.promoCode == null && !etPromoCode.hasFocus()) { + etPromoCode.setText("") } - pb_promo_code.show(state.promoCodeLoading) + pbPromoCode.show(state.promoCodeLoading) } - private fun handleButtonsState(state: ShopState) { - btn_pay_google_pay.show(state.isGooglePayAvailable) - btn_alternative_payment.show(state.isGooglePayAvailable) - btn_main_action.show(!state.isGooglePayAvailable) + private fun handleButtonsState(state: ShopState) = with(binding) { + btnPayGooglePay.root.show(state.isGooglePayAvailable) + btnAlternativePayment.show(state.isGooglePayAvailable) + btnMainAction.show(!state.isGooglePayAvailable) if (state.total != null) { - btn_alternative_payment.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) } - btn_main_action.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) } - btn_pay_google_pay.setOnClickListener { store.dispatch(ShopAction.BuyWithGooglePay) } + btnAlternativePayment.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) } + btnMainAction.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) } + btnPayGooglePay.root.setOnClickListener { store.dispatch(ShopAction.BuyWithGooglePay) } } } 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 6a2559848e..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,7 @@ 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 @@ -19,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 @@ -29,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) @@ -57,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) 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/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 1d31def08c..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,12 +1,10 @@ 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 @@ -15,8 +13,6 @@ 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.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.features.wallet.redux.WalletState @@ -38,7 +34,6 @@ 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) } @@ -68,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 -> { @@ -213,38 +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 -> { - 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 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 fdf3312ab0..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 @@ -6,17 +6,16 @@ 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 { @@ -33,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) { @@ -91,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 088fe72d40..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 @@ -11,6 +11,7 @@ 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.common.analytics.Analytics import com.tangem.tap.common.extensions.* @@ -147,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 @@ -156,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 b7c104861d..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 @@ -51,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), @@ -100,7 +100,7 @@ class WarningsMiddleware { addWarningMessage(WarningMessagesManager.onlineVerificationFailed()) } } - if (scanResponse.isDemoCard()){ + if (scanResponse.isDemoCard()) { addWarningMessage(WarningMessagesManager.demoCardWarning()) } setWarningMessages() @@ -110,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( @@ -123,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()) { @@ -135,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() @@ -160,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 f1d99a3053..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,19 +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.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) @@ -65,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() @@ -74,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 ) @@ -167,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_receive_message.text = getQRReceiveMessage(tv_receive_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 @@ -213,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?) { @@ -321,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.amountUi?.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 index aa15f94e9f..3fc5187741 100644 --- 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 @@ -18,7 +18,10 @@ class SimpleOkDialog { setTitle(dialog.header) setMessage(dialog.message) setPositiveButton(R.string.common_ok) { _, _ -> } - setOnDismissListener { store.dispatchDialogHide() } + setOnDismissListener { + store.dispatchDialogHide() + dialog.onOk?.invoke() + } }.create() } @@ -27,7 +30,10 @@ class SimpleOkDialog { setTitle(context.getString(dialog.headerId)) setMessage(dialog.messageId) setPositiveButton(R.string.common_ok) { _, _ -> } - setOnDismissListener { store.dispatchDialogHide() } + setOnDismissListener { + store.dispatchDialogHide() + dialog.onOk?.invoke() + } }.create() } } 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/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-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-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/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/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/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/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/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/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/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 0000000000..30dfad0768 --- /dev/null +++ b/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,11 @@ + + + + + + \ 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 fa3d618abc..82b94a5def 100644 --- a/app/src/main/res/layout/fragment_onboarding_main.xml +++ b/app/src/main/res/layout/fragment_onboarding_main.xml @@ -33,8 +33,9 @@ android:orientation="vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml index 2c1c940cb5..904ab50059 100644 --- a/app/src/main/res/layout/fragment_onboarding_wallet.xml +++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml @@ -218,6 +218,7 @@ 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"> @@ -109,7 +112,6 @@ android:id="@+id/tv_header" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="44dp" android:layout_marginBottom="14dp" android:fontFamily="sans-serif-medium" android:text="@string/shop_one_wallet" @@ -343,7 +345,7 @@ android:layout_height="48sp" android:layout_marginStart="14dp" android:layout_marginEnd="14dp" - android:layout_marginBottom="70dp" + android:layout_marginBottom="20dp" android:text="@string/shop_buy_now" android:textAlignment="center" android:textSize="16sp" /> 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 6b8ad727ed..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"> - - + 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_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.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" /> - One Wallet + Tangem Wallet 3 cards 2 cards Shipping @@ -26,12 +26,13 @@ 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 - Web3 Compatible + 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. - The transaction has been successfully signed, but not sent to the blockchain because of Demo mode. + This feature is disabled in Demo mode \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml index 339691d69b..fa5d2dc4b3 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -1,6 +1,6 @@ - One Wallet + Tangem Wallet 3 cards 2 cards Shipping @@ -26,12 +26,13 @@ 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 - Web3 Compatible + 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. - The transaction has been successfully signed, but not sent to the blockchain because of Demo mode. + This feature is disabled in Demo mode \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml index 339691d69b..fa5d2dc4b3 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -1,6 +1,6 @@ - One Wallet + Tangem Wallet 3 cards 2 cards Shipping @@ -26,12 +26,13 @@ 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 - Web3 Compatible + 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. - The transaction has been successfully signed, but not sent to the blockchain because of Demo mode. + 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 index 9581d6c26c..c54c69a74d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -10,15 +10,15 @@ Принять %s Доступ к камере запрещен - Вы не предоставили доступ к своей камере, измените настройки конфиденциальности. + Вы не предоставили доступ к своей камере, измените настройки конфиденциальности Настройки Добро пожаловать в Tangem.\nУ Вас есть одна из наших карт? Добро пожаловать обратно в Тангем. \nОтсканируйте карту, чтобы начать. Магазин - Приготовьтесь приложить свою\nкарту к задней панели\nтелефона. + Приготовьтесь приложить свою\nкарту к задней панели\nтелефона Нажмите на Сканировать карту - Да! Отсканировать + Да! Отсканировать. Сканировать Отправить Создать кошелек @@ -48,7 +48,7 @@ PayString имя $payid.tangem.com Создать - Ваша PayString — это уникальная для вас информация, такая как номер телефона, электронная почта или ABN. + Ваша PayString — это уникальная для вас информация, такая как номер телефона, электронная почта или ABN PayString успешно создан и скопирован в буфер обмена Ошибка при создании PayString Такой PayString уже существует. Попробуйте другой. @@ -73,7 +73,7 @@ ≈ %1$s (включая комиссию: %2$s) %1$s %2$s и %3$s %4$s будут отправлены Баланс: %1$s %2$s - Транзакция успешно подписана и отправлена в блокчейн. Баланс кошелька будет обновлен через некоторое время + Транзакция успешно подписана и отправлена в блокчейн. Баланс кошелька будет обновлен через некоторое время. Подробности ID карты Эмитент @@ -91,17 +91,17 @@ Длительное удержание Этот механизм защищает карту от бесконтактной атаки. Это обеспечит задержку между приемом и выполнением команды. После первой подписанной транзакции этот телефон будет привязан к карте, и транзакции будут подписаны без задержки. Пароль - Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. + Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль Код доступа - Перед сканированием карты вам нужно будет ввести правильный код доступа. - У Вас могут возникнуть проблемы с NFC на некоторых iPhone 7/7+ во время извлечения. + Перед сканированием карты вам нужно будет ввести правильный код доступа + У Вас могут возникнуть проблемы с NFC на некоторых iPhone 7/7+ во время извлечения Эта карта уже пополнялась и подписывала транзакции в прошлом. Рассмотрите возможность немедленного снятия всех средств, если Вы получили эту карту из ненадежного источника. Предупреждение Эта карта не предназначена для работы с этим приложением Карта, которую Вы отсканировали, является картой для разработчиков. Не принимайте ее в качестве оплаты. Карты Tangem, выпущенные до сентября 2019 года, в настоящее время не могут быть извлечены с помощью iPhone. Мы усердно работаем с Apple, чтобы сделать это возможным в будущих версиях iOS. Нажмите, чтобы подписать - Приложите карту к телефону, как показано выше. + Приложите карту к телефону, как показано выше Приложите, чтобы удалить кошелек Приложите, чтобы создать кошелек Приложите, чтобы изменить код доступа @@ -132,7 +132,7 @@ Чтобы не платить повышенную комиссию при следующем пополнении кошелька, уменьшите сумму на %s XTZ Уменьшить на %s XTZ Нет, отправить все - Ошибка сканирования карты. Попробуй снова + Ошибка сканирования карты. Попробуй снова. Купить Продать Торговать @@ -141,10 +141,10 @@ Устаревший Создать кошелек Создать кошелек - Сгенерируйте ключи кошелька на обеих картах, чтобы начать использовать свои Twin карты. + Сгенерируйте ключи кошелька на обеих картах, чтобы начать использовать свои Twin карты Tangem Twin Один кошелек. Две карты. - Одна, которую Вы держите в руках, и вторая карта с номером #%s. + Одна, которую Вы держите в руках, и вторая карта с номером #%s Карта %1d из %2d Пересоздать Twin кошелек Условия использования карты @@ -165,20 +165,20 @@ Поиск Памятка Тег назначения - Недопустимый тег назначения. Он не будет добавлен в транзакцию - Недопустимый идентификатор памятки. Он не будет добавлен в транзакцию + Недопустимый тег назначения. Он не будет добавлен в транзакцию. + Недопустимый идентификатор памятки. Он не будет добавлен в транзакцию. Приложение Отправить отзыв Успешно отправлено Спасибо за ваш отзыв Ваши предложения отправлены - Спасибо за ваш отзыв. Мы ответим как можно скорее + Спасибо за ваш отзыв. Мы ответим как можно скорее. Не удалось отправить электронное письмо Причина: %s Не могу отправить транзакцию Причина: %s. Хотите отправить отзыв? У Вас возникли трудности со сканированием вашей карты? - Пожалуйста, попробуйте приложить карту точно так, как показано на анимации, или обратитесь в поддержку. + Пожалуйста, попробуйте приложить карту точно так, как показано на анимации, или обратитесь в поддержку Отмена Попробуйте снова Обратиться в поддержку @@ -206,25 +206,20 @@ Символ токена Адрес контракта Десятичные - бывший. USD Coin - бывший. USDC + бывший USD Coin + бывший USDC Добавлен Удалить токен Значок токена Блокчейн Управление токенами Блокчейны - Ethereum токены - Binance Smart Chain токены - Binance Chain токены - Avalanche C-Chain токены - Polygon токены Копировать Поделиться + Добавить токены Пожалуйста, дождитесь завершения транзакции, чтобы иметь возможность отправить средства Проверка подлинности не удалась - Эта карта может быть производственным образцом или подделкой. + Эта карта может быть производственным образцом или подделкой Важная информация о безопасности \u26A0 Эта карта ранее подписывала транзакции Узнать подробнее @@ -236,7 +231,7 @@ Отклонить Это тестовая карта. Не принимайте ее в качестве оплаты. Она должна использоваться только в целях тестирования и разработки. Вы хотите купить или продать криптовалюту? - К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений. + К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений Добро пожаловать в Tangem Самый безопасный способ покупать, использовать и\n хранить криптовалюту Получить новую карту @@ -248,7 +243,7 @@ Создать кошелек Пополните свой кошелек Чтобы начать, просто пополните карту на любую сумму - Чтобы начать, просто пополните кошелек более чем на %s %s. + Чтобы начать, просто пополните кошелек более чем на %s %s Купить криптовалюту Показать адрес кошелька Отсканируйте адрес, чтобы пополнить кошелек @@ -261,7 +256,7 @@ Если процесс повторного создания кошелька каким-либо образом прервется, вам придется начать все сначала. Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. Внутренняя ошибка: не удается создать менеджер кошельков - Блокчейн недоступен. Попробуй позже + Блокчейн недоступен. Попробуй позже. Хорошо, понял! Успешно! Начнем @@ -279,40 +274,40 @@ Добавлена одна резервная карта Добавлены две резервные карты Резервная карта #%d - Вы можете привязать до трех карт к одному кошельку. + Вы можете привязать до трех карт к одному кошельку Чтобы начать процесс резервного копирования, добавьте еще максимум 2 карты Подготовьте основную карту с номером %s Подготовьте основную карту Вы можете добавить еще одну карту или завершить процесс резервного копирования - Добавлено максимальное количество карт. - Если процесс резервного копирования будет прерван каким-либо образом, вам придется начать все сначала. + Добавлено максимальное количество карт + Если процесс резервного копирования будет прерван каким-либо образом, вам придется начать все сначала Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? Создать код доступа Введите код доступа повторно Защитить Персонализировать Восстановить - Вам необходимо установить единый код доступа для защиты всех ваших кошельков. - Позже Вы сможете установить индивидуальный код доступа для каждой карты. - Код доступа можно восстановить с помощью привязанной карты, не храните все карты в одном месте. - Выберите любое слово, фразу или номер в качестве кода доступа. - Код доступа должен состоять не менее чем из 4 символов. + Вам необходимо установить единый код доступа для защиты всех ваших кошельков + Позже Вы сможете установить индивидуальный код доступа для каждой карты + Код доступа можно восстановить с помощью привязанной карты, не храните все карты в одном месте + Выберите любое слово, фразу или номер в качестве кода доступа + Код доступа должен состоять не менее чем из 4 символов Введенный код доступа не соответствует исходному коду доступа Создать Подтвердить - Вы создали 2 резервные карты, и теперь эти карты готовы к использованию. + Вы создали 2 резервные карты, и теперь эти карты готовы к использованию Создание резервной копии Процесс резервного копирования частично завершен. Вы не можете выйти из него сейчас. Резервный кошелек Идентичные карты Код доступа Восстановление кода доступа - Вы можете сделать до двух резервных копий карты Tangem Wallet. - Все резервные карты могут использоваться как полнофункциональные с одинаковыми ключами. - Вы сможете установить код доступа для защиты своих кошельков. - Код доступа можно восстановить с помощью одной из резервных карт. - Ваша карта Tangem Wallet настроена и готова к использованию. - Вы создали 1 резервную карту, и теперь эта карта готова к использованию. + Вы можете сделать до двух резервных копий карты Tangem Wallet + Все резервные карты могут использоваться как полнофункциональные с одинаковыми ключами + Вы сможете установить код доступа для защиты своих кошельков + Код доступа можно восстановить с помощью одной из резервных карт + Ваша карта Tangem Wallet настроена и готова к использованию + Вы создали 1 резервную карту, и теперь эта карта готова к использованию Основная карта Я понимаю Статус резервного копирования @@ -336,20 +331,20 @@ Подготовьте основную карту с номером %s Сброс до заводских настроек Это действие необратимо. Если после сброса карты кто-то отправит на нее средства, то Вы не сможете их вывести. - Код доступа доступен только для карт с резервной копией. + Код доступа доступен только для карт с резервной копией Сеть Solana взымает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы использовать его бесплатно. %1s (%2s) Эта карта не является векселем на предъявителя. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец скрывает автономную подпись, что является проблемой безопасности.\n\nНе принимайте эту карту в качестве физического платежа от кого-то, кому Вы не доверяете.\n\nВо всех остальных отношениях - это совершенно безопасно.\n\nTangem — единственный аппаратный кошелек, предлагающий защиту от подсчета подписей. - Wallet Connect + 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 @@ -360,7 +355,7 @@ Операция не может быть завершена Операция не может быть завершена. \n\nВы уже установили сеанс WalletConnect с этими параметрами. Не удалось установить сеанс WalletConnect: ошибка времени выполнения. Пожалуйста, повторите попытку позже. - Транзакция BNB успешно подписана и отправлена в DApp. + Транзакция BNB успешно подписана и отправлена в DApp DApp %s, запрашивает\nподпись транзакции BNB с\nкартой: %s\n\n%s Сведения о транзакции:\nОт: %s\nКому: %s\nСумма: %s Торговый ордер на %s\nЦена: %s\nСумма к получению: %s\nСумма к оплате: %s @@ -370,10 +365,10 @@ Не могу протолкнуть транзакцию Обращение в поддержку 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 index dac3254b83..288761b97c 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -1,15 +1,15 @@ - Один Кошелек + Tangem Wallet 3 карты 2 карты Доставка Бесплатно - У меня есть промо-код… + У меня есть промо-код... Итого Другие способы оплаты Купить сейчас - Оформление вашего заказа + Оформление заказа Ваш заказ Встречайте\nTangem Покупайте @@ -17,21 +17,22 @@ Отправляйте Расплачивайтесь Обменивайте - Одалживайте + Вкладывайте Занимайте - Вкладывайте + Стейкайте Революционный аппаратный кошелек - Держите свои криптосбережения в безопасности - приватные ключи надежно хранятся на карте - Ультрабезопасная резервная копия + Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. + Все ключи в безопасности До трех карт с одним кошельком Тысячи криптовалют Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте - Поддерживает Web3 - Обменивайте, покупайте NFT, получайте займы и вклады в более чем 100 различных децентрализованных сервисах + Поддержка DeFi + Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах Кошелек для каждого - Используйте его на ходу, в любом месте, в любое время. Без проводов и аккумуляторов. Как только понадобится крипта, просто приложите карту к телефону. - Заказать карту + Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. + Купить + Поиск токенов + Поиск токенов Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие. Эта функция недоступна в демонстрационном режиме - Транзакция успешно подписана, но не отправлена в блокчейн, потому что активирован демонстрационный режим. \ No newline at end of file diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index 339691d69b..fa5d2dc4b3 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -1,6 +1,6 @@ - One Wallet + Tangem Wallet 3 cards 2 cards Shipping @@ -26,12 +26,13 @@ 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 - Web3 Compatible + 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. - The transaction has been successfully signed, but not sent to the blockchain because of Demo mode. + 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 f746827108..40ba641e70 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -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