Updated on 2026-08-14
This commit is contained in:
commit
4efdd325be
245 changed files with 52320 additions and 19378 deletions
|
|
@ -1,22 +1,22 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 31
|
||||
defaultConfig {
|
||||
applicationId "com.tangem.wallet"
|
||||
versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 1
|
||||
versionName project.hasProperty('versionName') ? project.property('versionName') : "1.0.0.-SNAPSHOT"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
targetSdkVersion 31
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
buildFeatures {
|
||||
compose true
|
||||
viewBinding true
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
|
|
@ -47,12 +47,14 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
coreLibraryDesugaringEnabled false
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.1.0'
|
||||
}
|
||||
packagingOptions {
|
||||
exclude 'lib/x86_64/darwin/libscrypt.dylib'
|
||||
exclude 'lib/x86_64/freebsd/libscrypt.so'
|
||||
|
|
@ -67,19 +69,19 @@ repositories {
|
|||
dependencies {
|
||||
implementation fileTree(include: ['*.aar'], dir: 'libs')
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.6.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.3.6'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation "androidx.core:core-ktx:1.6.0"
|
||||
implementation 'com.google.android.play:core:1.10.2'
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.4.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation "androidx.core:core-ktx:1.7.0"
|
||||
implementation 'com.google.android.play:core:1.10.3'
|
||||
implementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'com.tangem:blockchain:develop-64'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-131'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-131'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-139'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-139'
|
||||
|
||||
// WebView
|
||||
implementation "androidx.browser:browser:1.3.0"
|
||||
|
|
@ -129,6 +131,24 @@ dependencies {
|
|||
// animation
|
||||
implementation "com.airbnb.android:lottie:3.4.0"
|
||||
|
||||
// Shopify
|
||||
implementation('com.shopify.mobilebuysdk:buy3:12.0.0') {
|
||||
exclude group: "com.shopify.graphql.support"
|
||||
exclude module: 'joda-time'
|
||||
}
|
||||
|
||||
// Google Pay
|
||||
implementation 'com.google.android.gms:play-services-wallet:19.1.0'
|
||||
|
||||
//Compose
|
||||
implementation 'androidx.activity:activity-compose:1.4.0'
|
||||
implementation 'androidx.compose.material:material:1.1.0'
|
||||
implementation 'androidx.compose.animation:animation:1.1.0'
|
||||
implementation 'androidx.compose.ui:ui-tooling:1.1.0'
|
||||
implementation "com.google.accompanist:accompanist-appcompat-theme:0.23.0"
|
||||
|
||||
implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation "com.google.truth:truth:1.1.3"
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
|
|
|
|||
6
app/src/debug/res/values-ru/strings.xml
Normal file
6
app/src/debug/res/values-ru/strings.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">DebugTangem</string>
|
||||
|
||||
</resources>
|
||||
6
app/src/debug_beta/res/values-ru/strings.xml
Normal file
6
app/src/debug_beta/res/values-ru/strings.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">BetaTangem</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -39,8 +39,12 @@
|
|||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:fullBackupContent">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.wallet.api.enabled"
|
||||
android:value="true" />
|
||||
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:name="com.tangem.tap.MainActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="portrait"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
1815
app/src/main/assets/ftm_tokens.json
Normal file
1815
app/src/main/assets/ftm_tokens.json
Normal file
File diff suppressed because it is too large
Load diff
16
app/src/main/assets/ftm_tokens_testnet.json
Normal file
16
app/src/main/assets/ftm_tokens_testnet.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[
|
||||
{
|
||||
"decimalCount": 18,
|
||||
"customIconUrl": "https://cdn.worldvectorlogo.com/logos/usd-1.svg",
|
||||
"symbol": "FUSD",
|
||||
"name": "Fantom USD",
|
||||
"contractAddress": "0x91ea991bd52EE3C40EdA2509701d905e1Ee54074"
|
||||
},
|
||||
{
|
||||
"decimalCount": 18,
|
||||
"customIconUrl": "https://cryptologos.cc/logos/fantom-ftm-logo.svg?v=003",
|
||||
"symbol": "WFTM",
|
||||
"name": "Wrapped Fantom",
|
||||
"contractAddress": "0xf1277d1Ed8AD466beddF92ef448A132661956621"
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1 +1 @@
|
|||
Subproject commit 42803296690ad6914ec2c59c5fdc3f0fb30f8afc
|
||||
Subproject commit 44ccf4e7766e51e869eabe8bc68026b5b7edd237
|
||||
|
|
@ -6,6 +6,7 @@ import android.os.Bundle
|
|||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.operations.backup.BackupService
|
||||
|
|
@ -18,10 +19,14 @@ import com.tangem.tap.common.redux.global.AndroidResources
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.common.shop.GooglePayService
|
||||
import com.tangem.tap.common.shop.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE
|
||||
import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient
|
||||
import com.tangem.tap.domain.TangemSdkManager
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.shop.redux.ShopAction
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import com.tangem.wallet.databinding.ActivityMainBinding
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -59,6 +64,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
|
|||
private var snackbar: Snackbar? = null
|
||||
private val dialogManager = DialogManager()
|
||||
private val intentHandler = IntentHandler()
|
||||
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -73,8 +79,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
|
|||
|
||||
store.dispatch(GlobalAction.SetResources(getAndroidResources()))
|
||||
store.dispatch(WalletConnectAction.RestoreSessions)
|
||||
store.dispatch(
|
||||
ShopAction.CheckIfGooglePayAvailable(
|
||||
GooglePayService(createPaymentsClient(this), this)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun getAndroidResources(): AndroidResources {
|
||||
return AndroidResources(
|
||||
AndroidResources.RString(
|
||||
|
|
@ -92,12 +104,13 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
notificationsHandler = NotificationsHandler(fragment_container)
|
||||
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
|
||||
|
||||
val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
|
||||
val isScannedBefore = store.state.globalState.scanResponse != null
|
||||
val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted
|
||||
if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) {
|
||||
val shopOpened = store.state.shopState.total != null
|
||||
if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore && !shopOpened)) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
}
|
||||
intentHandler.handleIntent(intent)
|
||||
|
|
@ -128,7 +141,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
|
|||
if (snackbar != null) return
|
||||
|
||||
snackbar = Snackbar.make(
|
||||
fragment_container, getString(text), Snackbar.LENGTH_INDEFINITE
|
||||
binding.fragmentContainer, getString(text), Snackbar.LENGTH_INDEFINITE
|
||||
)
|
||||
if (buttonTitle != null && action != null) {
|
||||
snackbar?.setAction(getString(buttonTitle), action)
|
||||
|
|
@ -140,4 +153,15 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
|
|||
snackbar?.dismiss()
|
||||
snackbar = null
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when (requestCode) {
|
||||
LOAD_PAYMENT_DATA_REQUEST_CODE -> {
|
||||
store.dispatch(
|
||||
ShopAction.BuyWithGooglePay.HandleGooglePayResponse(resultCode, data)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.tap.common.images.PicassoHelper
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.appReducer
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.shop.TangemShopService
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
|
||||
import com.tangem.tap.domain.configurable.config.FeaturesRemoteLoader
|
||||
|
|
@ -37,6 +38,7 @@ val store = Store(
|
|||
lateinit var preferencesStorage: PreferencesStorage
|
||||
lateinit var currenciesRepository: CurrenciesRepository
|
||||
lateinit var walletConnectRepository: WalletConnectRepository
|
||||
lateinit var shopService: TangemShopService
|
||||
|
||||
class TapApplication : Application() {
|
||||
override fun onCreate() {
|
||||
|
|
@ -72,7 +74,10 @@ class TapApplication : Application() {
|
|||
val localLoader = FeaturesLocalLoader(this, moshi)
|
||||
val remoteLoader = FeaturesRemoteLoader(moshi)
|
||||
val configManager = ConfigManager(localLoader, remoteLoader)
|
||||
configManager.load { store.dispatch(GlobalAction.SetConfigManager(configManager)) }
|
||||
configManager.load {
|
||||
store.dispatch(GlobalAction.SetConfigManager(configManager))
|
||||
shopService = TangemShopService(this, configManager.config.shopify!!)
|
||||
}
|
||||
val warningsManager = WarningMessagesManager(RemoteWarningLoader(moshi))
|
||||
warningsManager.load { store.dispatch(GlobalAction.SetWarningManager(warningsManager)) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInPro
|
|||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.SimpleOkDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -48,6 +49,8 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
if (dialog != null) return
|
||||
|
||||
dialog = when (state.dialog) {
|
||||
is AppDialog.SimpleOkDialog -> SimpleOkDialog.create(state.dialog, context)
|
||||
is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context)
|
||||
is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
|
||||
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
||||
is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import android.nfc.NfcAdapter
|
|||
import android.nfc.Tag
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.topup.TradeCryptoHelper
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
|
|
@ -15,6 +14,11 @@ import timber.log.Timber
|
|||
|
||||
class IntentHandler {
|
||||
|
||||
private val TRANSACTION_ID_PARAM = "transactionId"
|
||||
private val CURRENCY_CODE_PARAM = "baseCurrencyCode"
|
||||
private val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
|
||||
private val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
|
||||
|
||||
fun handleIntent(intent: Intent?) {
|
||||
handleBackgroundScan(intent)
|
||||
handleWalletConnectLink(intent)
|
||||
|
|
@ -23,8 +27,8 @@ class IntentHandler {
|
|||
|
||||
private fun handleBackgroundScan(intent: Intent?) {
|
||||
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action ||
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action
|
||||
)
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action
|
||||
)
|
||||
) {
|
||||
val tag = intent.getParcelableExtra<Tag>(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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String, String> = emptyMap()
|
||||
)
|
||||
|
||||
abstract fun triggerEvent(
|
||||
event: String,
|
||||
params: Map<String, String>
|
||||
)
|
||||
|
||||
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<String, String>
|
||||
}
|
||||
|
|
@ -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<String, String> {
|
||||
|
||||
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<String, String>) {
|
||||
AppsFlyerLib.getInstance().logEvent(context, event, params)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<String, String> {
|
||||
|
||||
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<String, String>) {
|
||||
Firebase.analytics.logEvent(event, params.toBundle())
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AnalyticsHandler>) :
|
|||
analyticsHandlers.forEach { it.triggerEvent(event, card, blockchain) }
|
||||
}
|
||||
|
||||
override fun triggerEvent(event: String, params: Map<String, String>) {
|
||||
analyticsHandlers.forEach { it.triggerEvent(event, params) }
|
||||
}
|
||||
|
||||
override fun logCardSdkError(
|
||||
error: TangemSdkError,
|
||||
actionToLog: Analytics.ActionToLog,
|
||||
|
|
@ -28,6 +33,14 @@ class GlobalAnalyticsHandler(val analyticsHandlers: List<AnalyticsHandler>) :
|
|||
analyticsHandlers.forEach { it.logError(error, params) }
|
||||
}
|
||||
|
||||
override fun getOrderEvent(): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun getOrderParams(order: Storefront.Order): Map<String, String> {
|
||||
return emptyMap()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createDefaultAnalyticHandlers(context: Application): GlobalAnalyticsHandler {
|
||||
return GlobalAnalyticsHandler(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCar
|
|||
import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.shop.ui.ShopFragment
|
||||
import com.tangem.tap.features.tokens.ui.AddTokensFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
|
|
@ -69,6 +70,7 @@ fun FragmentActivity.addOnBackPressedDispatcher(
|
|||
private fun fragmentFactory(screen: AppScreen): Fragment {
|
||||
return when (screen) {
|
||||
AppScreen.Home -> HomeFragment()
|
||||
AppScreen.Shop -> ShopFragment()
|
||||
AppScreen.OnboardingNote -> OnboardingNoteFragment()
|
||||
AppScreen.OnboardingWallet -> OnboardingWalletFragment()
|
||||
AppScreen.OnboardingTwins -> TwinsCardsFragment()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
fun String.urlEncode(): String = Uri.encode(this)
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -37,10 +37,19 @@ fun Context.getColorCompat(@ColorRes colorRes: Int): Int {
|
|||
return ContextCompat.getColor(this, colorRes)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun View.getColor(@ColorRes colorRes: Int): Int {
|
||||
return ContextCompat.getColor(context, colorRes)
|
||||
}
|
||||
|
||||
fun View.getString(@StringRes id: Int): String {
|
||||
return context.getString(id)
|
||||
}
|
||||
|
||||
fun View.getString(@StringRes id: Int, vararg formatArgs: String): String {
|
||||
return context.getString(id, *formatArgs)
|
||||
}
|
||||
|
||||
fun View.getResourceName(): String {
|
||||
return try {
|
||||
resources.getResourceEntryName(id)
|
||||
|
|
@ -68,9 +77,16 @@ fun View.hide(invokeBeforeStateChanged: (() -> Unit)? = null) {
|
|||
this.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun View.makeInvisible() {
|
||||
if (this.visibility == View.INVISIBLE) return
|
||||
this.visibility = View.INVISIBLE
|
||||
fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> Unit)? = null) {
|
||||
if (invisible) {
|
||||
if (this.visibility == View.INVISIBLE) return
|
||||
|
||||
invokeBeforeStateChanged?.invoke()
|
||||
this.visibility = View.INVISIBLE
|
||||
} else {
|
||||
this.show(invokeBeforeStateChanged)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun Context.dpToPixels(dp: Int): Int =
|
||||
|
|
@ -158,4 +174,8 @@ fun Context.safeStartActivity(
|
|||
} finally {
|
||||
finally?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
|
||||
return context.getString(resId, formatArgs)
|
||||
}
|
||||
|
|
@ -29,6 +29,10 @@ fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition())
|
|||
TransitionManager.beginDelayedTransition(this, transition)
|
||||
}
|
||||
|
||||
fun View.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
(this as? ViewGroup)?.beginDelayedTransition(transition)
|
||||
}
|
||||
|
||||
fun ChipGroup.fitChipsByGroupWidth() {
|
||||
val layoutStateHandler = GlobalLayoutStateHandler(this)
|
||||
layoutStateHandler.onStateChanged = stateHandler@{
|
||||
|
|
|
|||
|
|
@ -7,19 +7,29 @@ import com.tangem.common.services.Result
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.amountToCreateAccount
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.topup.TradeCryptoHelper
|
||||
import com.tangem.tap.features.demo.isDemoWallet
|
||||
import com.tangem.tap.features.wallet.redux.AddressData
|
||||
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.delay
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
||||
update()
|
||||
Result.Success(wallet)
|
||||
if (isDemoWallet()) {
|
||||
delay(500)
|
||||
Result.Success(wallet)
|
||||
} else {
|
||||
update()
|
||||
Result.Success(wallet)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception)
|
||||
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
||||
Result.Failure(TapError.NoInternetConnection)
|
||||
} else {
|
||||
|
|
@ -36,17 +46,17 @@ suspend fun WalletManager.safeUpdate(): Result<Wallet> = 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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOt
|
|||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
|
||||
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
|
||||
import com.tangem.tap.features.shop.redux.ShopReducer
|
||||
import com.tangem.tap.features.tokens.redux.TokensReducer
|
||||
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -32,7 +33,8 @@ fun appReducer(action: Action, state: AppState?): AppState {
|
|||
detailsState = DetailsReducer.reduce(action, state),
|
||||
disclaimerState = DisclaimerReducer.reduce(action, state),
|
||||
tokensState = TokensReducer.reduce(action, state),
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState)
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||
shopState = ShopReducer.reduce(action, state.shopState),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWallet
|
|||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.send.redux.middlewares.SendMiddleware
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
import com.tangem.tap.features.shop.redux.ShopMiddleware
|
||||
import com.tangem.tap.features.shop.redux.ShopState
|
||||
import com.tangem.tap.features.tokens.redux.TokensMiddleware
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
|
@ -44,6 +46,7 @@ data class AppState(
|
|||
val disclaimerState: DisclaimerState = DisclaimerState(),
|
||||
val tokensState: TokensState = TokensState(),
|
||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||
val shopState: ShopState = ShopState(),
|
||||
) : StateType {
|
||||
|
||||
companion object {
|
||||
|
|
@ -62,7 +65,8 @@ data class AppState(
|
|||
DisclaimerMiddleware().disclaimerMiddleware,
|
||||
TokensMiddleware().tokensMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware
|
||||
BackupMiddleware().backupMiddleware,
|
||||
ShopMiddleware().shopMiddleware,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.common.redux
|
||||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.tap.features.wallet.redux.AddressData
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
|
||||
|
|
@ -9,6 +10,8 @@ import com.tangem.tap.features.wallet.redux.Currency
|
|||
interface StateDialog
|
||||
|
||||
sealed class AppDialog : StateDialog {
|
||||
data class SimpleOkDialog(val header: String, val message: String, val onOk: VoidCallback? = null) : AppDialog()
|
||||
data class SimpleOkDialogRes(val headerId: Int, val messageId: Int, val onOk: VoidCallback? = null) : AppDialog()
|
||||
object ScanFailsDialog : AppDialog()
|
||||
data class AddressInfoDialog(
|
||||
val currency: Currency,
|
||||
|
|
|
|||
|
|
@ -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<WalletManager>) : GlobalAction()
|
||||
|
||||
object GetMoonPayStatus : GlobalAction() {
|
||||
data class Success(val moonPayStatus: MoonpayStatus) : GlobalAction()
|
||||
object InitCurrencyExchangeManager : GlobalAction() {
|
||||
data class Success(val exchangeManager: CurrencyExchangeManager) : GlobalAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AppState> = { 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ data class NavigationState(
|
|||
|
||||
enum class AppScreen {
|
||||
Home,
|
||||
Shop,
|
||||
Disclaimer,
|
||||
OnboardingNote, OnboardingWallet, OnboardingTwins, OnboardingOther,
|
||||
Wallet, WalletDetails,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,230 @@
|
|||
package com.tangem.tap.common.shop
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import com.google.android.gms.wallet.PaymentData
|
||||
import com.shopify.buy3.Storefront
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.common.shop.data.ProductType
|
||||
import com.tangem.tap.common.shop.data.TangemProduct
|
||||
import com.tangem.tap.common.shop.data.TotalSum
|
||||
import com.tangem.tap.common.shop.shopify.ShopifyService
|
||||
import com.tangem.tap.common.shop.shopify.ShopifyShop
|
||||
import com.tangem.tap.common.shop.shopify.data.CheckoutItem
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import java.math.BigDecimal
|
||||
import java.util.*
|
||||
|
||||
class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
||||
|
||||
private val shopifyService = ShopifyService(application, shopifyShop)
|
||||
|
||||
private lateinit var product: Storefront.Product
|
||||
|
||||
private val checkouts = mutableMapOf<ProductType, Storefront.Checkout>()
|
||||
private val variants = mutableMapOf<ProductType, Storefront.ProductVariant>()
|
||||
|
||||
private lateinit var googlePayService: GooglePayService
|
||||
|
||||
suspend fun getProducts(): Result<List<TangemProduct>> {
|
||||
val result = shopifyService.getProducts()
|
||||
|
||||
result.onSuccess {
|
||||
product = it.first {
|
||||
val variantsSku = it.variants.edges.map { it.node.sku }
|
||||
variantsSku.contains(ProductType.WALLET_2_CARDS.sku) && variantsSku.contains(
|
||||
ProductType.WALLET_3_CARDS.sku
|
||||
)
|
||||
}
|
||||
product.variants.edges.map { it.node }
|
||||
.forEach { variant ->
|
||||
if (variant.sku == ProductType.WALLET_2_CARDS.sku) {
|
||||
variants[ProductType.WALLET_2_CARDS] = variant
|
||||
} else if (variant.sku == ProductType.WALLET_3_CARDS.sku) {
|
||||
variants[ProductType.WALLET_3_CARDS] = variant
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val twoCardsProduct = TangemProduct(
|
||||
type = ProductType.WALLET_2_CARDS,
|
||||
totalSum = TotalSum(
|
||||
finalValue = variants[ProductType.WALLET_2_CARDS]?.priceV2?.format(),
|
||||
beforeDiscount = variants[ProductType.WALLET_2_CARDS]?.compareAtPriceV2?.format()
|
||||
)
|
||||
)
|
||||
val threeCardsProduct = TangemProduct(
|
||||
type = ProductType.WALLET_3_CARDS,
|
||||
totalSum = TotalSum(
|
||||
finalValue = variants[ProductType.WALLET_3_CARDS]?.priceV2?.format(),
|
||||
beforeDiscount = variants[ProductType.WALLET_3_CARDS]?.compareAtPriceV2?.format()
|
||||
)
|
||||
)
|
||||
createCheckouts()
|
||||
return Result.success(listOf(twoCardsProduct, threeCardsProduct))
|
||||
}
|
||||
return Result.failure(result.exceptionOrNull()!!)
|
||||
}
|
||||
|
||||
private suspend fun createCheckouts() {
|
||||
variants.keys.map { coroutineScope { async { createCheckout(it) } } }.awaitAll()
|
||||
}
|
||||
|
||||
private suspend fun createCheckout(productType: ProductType) {
|
||||
val checkoutItem = CheckoutItem(variants[productType]!!.id, 1)
|
||||
val result = shopifyService.createCheckout(listOf(checkoutItem))
|
||||
result.onSuccess { checkout ->
|
||||
checkouts[productType] = checkout
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkIfGooglePayAvailable(googlePayService: GooglePayService): Result<Boolean> {
|
||||
this.googlePayService = googlePayService
|
||||
return googlePayService.checkIfGooglePayAvailable()
|
||||
}
|
||||
|
||||
fun buyWithGooglePay(productType: ProductType) {
|
||||
val totalPrice = checkouts[productType]!!.totalPriceV2.amount
|
||||
googlePayService.payWithGooglePay(
|
||||
totalPriceCents = totalPrice, currencyCode = checkouts[productType]!!.currencyCode.name,
|
||||
merchantID = shopifyService.shop.merchantID
|
||||
)
|
||||
}
|
||||
|
||||
// fun subscribeToGooglePayResult(
|
||||
// productType: ProductType,
|
||||
// resultCallback: (Result<PaymentData>) -> Unit
|
||||
// ) {
|
||||
// googlePayService.responseCallback = { result ->
|
||||
// result.onFailure { }
|
||||
// result.onSuccess {
|
||||
// completeTokenizedPayment(it, productType)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
suspend fun handleGooglePayResult(
|
||||
resultCode: Int,
|
||||
data: Intent?,
|
||||
productType: ProductType
|
||||
): Result<Unit> {
|
||||
val result = googlePayService.handleResponseFromGooglePay(resultCode, data)
|
||||
result.onSuccess {
|
||||
val finalizePaymentResult = completeTokenizedPayment(it, productType)
|
||||
finalizePaymentResult.onSuccess {
|
||||
return Result.success(Unit)
|
||||
}
|
||||
return Result.failure(finalizePaymentResult.exceptionOrNull()!!)
|
||||
}
|
||||
return Result.failure(result.exceptionOrNull()!!)
|
||||
}
|
||||
|
||||
private suspend fun completeTokenizedPayment(
|
||||
paymentData: PaymentData,
|
||||
productType: ProductType
|
||||
): Result<Storefront.Checkout> {
|
||||
val checkout = checkouts[productType]!!
|
||||
val googlePayResponse =
|
||||
googlePayService.parsePaymentData(paymentData)
|
||||
?: return Result.failure(Exception("cannot parse GPay result"))
|
||||
|
||||
val amount =
|
||||
Storefront.MoneyInput(checkout.totalPriceV2.amount, checkout.totalPriceV2.currencyCode)
|
||||
val idempotencyKey = UUID.randomUUID().toString()
|
||||
val addressGPay = googlePayResponse.billingAddress
|
||||
|
||||
val address = Storefront.MailingAddressInput().apply {
|
||||
lastName = addressGPay.name
|
||||
address1 = addressGPay.address1
|
||||
address2 = addressGPay.address2 + addressGPay.address3
|
||||
province = addressGPay.administrativeArea
|
||||
zip = addressGPay.postalCode
|
||||
phone = addressGPay.phoneNumber
|
||||
}
|
||||
|
||||
|
||||
val payment = Storefront.TokenizedPaymentInputV3(
|
||||
amount,
|
||||
idempotencyKey,
|
||||
address,
|
||||
paymentData.toJson(),
|
||||
Storefront.PaymentTokenType.GOOGLE_PAY
|
||||
)
|
||||
.setTest(true)
|
||||
|
||||
return shopifyService.completeWithTokenizedPayment(
|
||||
payment = payment,
|
||||
checkoutID = checkout.id
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun applyPromoCode(promoCode: String): Result<List<TangemProduct>> {
|
||||
val products = variants.keys
|
||||
.map { coroutineScope { async { applyPromoCode(promoCode, it) } } }
|
||||
.awaitAll()
|
||||
.map { result -> result.getOrElse { return Result.failure(it) } }
|
||||
|
||||
return Result.success(products)
|
||||
}
|
||||
|
||||
suspend fun applyPromoCode(promoCode: String, productType: ProductType): Result<TangemProduct> {
|
||||
val checkout = checkouts[productType] ?: return Result.failure(Exception("No checkout"))
|
||||
|
||||
val result = if (promoCode.isBlank()) {
|
||||
shopifyService.removeDiscount(checkout.id)
|
||||
} else {
|
||||
shopifyService.applyDiscount(promoCode, checkout.id)
|
||||
}
|
||||
|
||||
result.onSuccess {
|
||||
checkouts[productType] = it
|
||||
return Result.success(
|
||||
TangemProduct(
|
||||
productType,
|
||||
TotalSum(
|
||||
finalValue = it.totalPriceV2.format(),
|
||||
beforeDiscount = variants[productType]!!.compareAtPriceV2.format()
|
||||
),
|
||||
appliedDiscount = it.getAppliedDiscount()
|
||||
)
|
||||
|
||||
)
|
||||
}
|
||||
return Result.failure(result.exceptionOrNull()!!)
|
||||
}
|
||||
|
||||
fun getCheckoutUrl(productType: ProductType): String {
|
||||
return checkouts[productType]!!.webUrl
|
||||
}
|
||||
|
||||
suspend fun waitForCheckout(
|
||||
productType: ProductType,
|
||||
analyticsHandler: AnalyticsHandler?
|
||||
) {
|
||||
val result = shopifyService.checkout(true, checkouts[productType]!!.id)
|
||||
result.onSuccess {
|
||||
if (it.order != null) {
|
||||
analyticsHandler?.logShopifyOrder(order = it.order)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TANGEM_WALLET_2_CARDS_SKU = "TG115x2"
|
||||
const val TANGEM_WALLET_3_CARDS_SKU = "TG115x3"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Storefront.MoneyV2.format(): String {
|
||||
val currencySymbol = Currency.getInstance(currencyCode.name).symbol
|
||||
val amountFormatted = BigDecimal(amount).setScale(2)
|
||||
return currencySymbol + amountFormatted
|
||||
}
|
||||
|
||||
private fun Storefront.Checkout.getAppliedDiscount(): String? {
|
||||
val discountApplication =
|
||||
discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication
|
||||
return discountApplication?.code
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.tap.common.shop.data
|
||||
|
||||
import com.tangem.tap.common.shop.TangemShopService
|
||||
|
||||
enum class ProductType(val sku: String) {
|
||||
WALLET_2_CARDS(TangemShopService.TANGEM_WALLET_2_CARDS_SKU),
|
||||
WALLET_3_CARDS(TangemShopService.TANGEM_WALLET_3_CARDS_SKU)
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.tap.common.shop.data
|
||||
|
||||
data class TangemProduct(
|
||||
val type: ProductType,
|
||||
val totalSum: TotalSum? = null,
|
||||
val appliedDiscount: String? = null
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.tap.common.shop.data
|
||||
|
||||
data class TotalSum(
|
||||
val finalValue: String? = null,
|
||||
val beforeDiscount: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package com.tangem.tap.common.shop
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Activity.RESULT_CANCELED
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.google.android.gms.wallet.*
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.shop.googlepay.GooglePayUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class GooglePayService(private val paymentsClient: PaymentsClient, private val activity: Activity) {
|
||||
|
||||
// var responseCallback: ((Result<PaymentData>) -> Unit)? = null
|
||||
|
||||
suspend fun checkIfGooglePayAvailable(): Result<Boolean> {
|
||||
|
||||
val isReadyToPayJson = GooglePayUtil.isReadyToPayRequest() ?: return Result.success(false)
|
||||
val request = IsReadyToPayRequest.fromJson(isReadyToPayJson.toString())
|
||||
|
||||
val task = paymentsClient.isReadyToPay(request)
|
||||
return withContext(Dispatchers.IO) {
|
||||
suspendCoroutine { continuation ->
|
||||
task.addOnCompleteListener { completedTask ->
|
||||
try {
|
||||
val result = completedTask.getResult(ApiException::class.java)
|
||||
continuation.resume(Result.success(true))
|
||||
} catch (exception: ApiException) {
|
||||
// Process error
|
||||
Timber.w("isReadyToPay failed: $exception")
|
||||
continuation.resume(Result.failure(exception))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun payWithGooglePay(totalPriceCents: String, currencyCode: String, merchantID: String) {
|
||||
val paymentDataRequestJson = GooglePayUtil.getPaymentDataRequest(
|
||||
totalPriceCents,
|
||||
currencyCode = currencyCode,
|
||||
countryCode = "RU",
|
||||
merchantID = merchantID
|
||||
)
|
||||
if (paymentDataRequestJson == null) {
|
||||
Timber.e("RequestPayment: can't fetch payment data request")
|
||||
return
|
||||
}
|
||||
val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())
|
||||
|
||||
AutoResolveHelper.resolveTask(
|
||||
paymentsClient.loadPaymentData(request), activity, LOAD_PAYMENT_DATA_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
|
||||
fun handleResponseFromGooglePay(resultCode: Int, data: Intent?): Result<PaymentData> {
|
||||
val result = when (resultCode) {
|
||||
RESULT_OK -> {
|
||||
val paymentData = data?.let { intent -> PaymentData.getFromIntent(intent) }
|
||||
if (paymentData == null) {
|
||||
Result.failure(Exception("No payment data"))
|
||||
} else {
|
||||
Result.success(paymentData)
|
||||
}
|
||||
}
|
||||
RESULT_CANCELED -> {
|
||||
Result.failure(TangemSdkError.UserCancelled())
|
||||
}
|
||||
AutoResolveHelper.RESULT_ERROR -> {
|
||||
val statusCode = AutoResolveHelper.getStatusFromIntent(data)?.statusCode
|
||||
if (statusCode == null) {
|
||||
Result.failure(Exception("Unknown Status"))
|
||||
} else {
|
||||
Result.failure(Exception("$statusCode"))
|
||||
}
|
||||
|
||||
}
|
||||
else -> Result.failure(Exception("Unknown Status"))
|
||||
}
|
||||
// responseCallback?.invoke(result)
|
||||
return result
|
||||
}
|
||||
|
||||
fun parsePaymentData(paymentData: PaymentData): GooglePayResponse? {
|
||||
val paymentInformation = paymentData.toJson()
|
||||
|
||||
try {
|
||||
// Token will be null if PaymentDataRequest was not constructed using fromJson(String).
|
||||
val paymentMethodData =
|
||||
JSONObject(paymentInformation).getJSONObject("paymentMethodData")
|
||||
val addressJson = paymentMethodData.getJSONObject("info")
|
||||
.getJSONObject("billingAddress")
|
||||
|
||||
val address = Address(
|
||||
name = addressJson.getString("name"),
|
||||
postalCode = addressJson.getString("postalCode"),
|
||||
countryCode = addressJson.getString("countryCode"),
|
||||
phoneNumber = addressJson.getString("phoneNumber"),
|
||||
address1 = addressJson.getString("address1"),
|
||||
address2 = addressJson.getString("address2"),
|
||||
address3 = addressJson.getString("address3"),
|
||||
locality = addressJson.getString("locality"),
|
||||
administrativeArea = addressJson.getString("administrativeArea"),
|
||||
sortingCode = addressJson.getString("sortingCode"),
|
||||
)
|
||||
|
||||
val token = paymentMethodData
|
||||
.getJSONObject("tokenizationData")
|
||||
.getString("token")
|
||||
|
||||
return GooglePayResponse(address, token)
|
||||
|
||||
} catch (e: JSONException) {
|
||||
Log.e("handlePaymentSuccess", "Error: " + e.toString())
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LOAD_PAYMENT_DATA_REQUEST_CODE = 315
|
||||
}
|
||||
}
|
||||
|
||||
data class GooglePayResponse(
|
||||
val billingAddress: Address,
|
||||
val token: String,
|
||||
)
|
||||
|
||||
data class Address(
|
||||
val name: String,
|
||||
val postalCode: String,
|
||||
val countryCode: String,
|
||||
val phoneNumber: String,
|
||||
val address1: String,
|
||||
val address2: String,
|
||||
val address3: String,
|
||||
val locality: String,
|
||||
val administrativeArea: String,
|
||||
val sortingCode: String
|
||||
)
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package com.tangem.tap.common.shop.googlepay
|
||||
|
||||
import android.app.Activity
|
||||
import com.google.android.gms.wallet.PaymentsClient
|
||||
import com.google.android.gms.wallet.Wallet
|
||||
import com.google.android.gms.wallet.WalletConstants
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
object GooglePayUtil {
|
||||
private val baseRequest = JSONObject().apply {
|
||||
put("apiVersion", 2)
|
||||
put("apiVersionMinor", 0)
|
||||
}
|
||||
|
||||
private fun gatewayTokenizationSpecification(merchantID: String): JSONObject {
|
||||
return JSONObject().apply {
|
||||
put("type", "PAYMENT_GATEWAY")
|
||||
put(
|
||||
"parameters", JSONObject(
|
||||
mapOf(
|
||||
"gateway" to "shopify",
|
||||
"gatewayMerchantId" to merchantID
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val allowedCardNetworks = JSONArray(
|
||||
listOf(
|
||||
"AMEX",
|
||||
"DISCOVER",
|
||||
"INTERAC",
|
||||
"JCB",
|
||||
"MASTERCARD",
|
||||
"VISA"
|
||||
)
|
||||
)
|
||||
|
||||
private val allowedCardAuthMethods = JSONArray(
|
||||
listOf(
|
||||
"PAN_ONLY",
|
||||
"CRYPTOGRAM_3DS"
|
||||
)
|
||||
)
|
||||
|
||||
private fun baseCardPaymentMethod(): JSONObject {
|
||||
return JSONObject().apply {
|
||||
|
||||
val parameters = JSONObject().apply {
|
||||
put("allowedAuthMethods", allowedCardAuthMethods)
|
||||
put("allowedCardNetworks", allowedCardNetworks)
|
||||
put("billingAddressRequired", true)
|
||||
put("billingAddressParameters", JSONObject().apply {
|
||||
put("format", "FULL")
|
||||
})
|
||||
}
|
||||
|
||||
put("type", "CARD")
|
||||
put("parameters", parameters)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cardPaymentMethod(merchantID: String): JSONObject {
|
||||
val cardPaymentMethod = baseCardPaymentMethod()
|
||||
cardPaymentMethod.put("tokenizationSpecification", gatewayTokenizationSpecification(merchantID))
|
||||
|
||||
return cardPaymentMethod
|
||||
}
|
||||
|
||||
fun createPaymentsClient(activity: Activity): PaymentsClient {
|
||||
val walletOptions = Wallet.WalletOptions.Builder()
|
||||
.setEnvironment(PAYMENTS_ENVIRONMENT)
|
||||
.build()
|
||||
|
||||
return Wallet.getPaymentsClient(activity, walletOptions)
|
||||
}
|
||||
|
||||
fun isReadyToPayRequest(): JSONObject? {
|
||||
return try {
|
||||
baseRequest.apply {
|
||||
put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod()))
|
||||
}
|
||||
|
||||
} catch (e: JSONException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTransactionInfo(
|
||||
price: String,
|
||||
countryCode: String,
|
||||
currencyCode: String
|
||||
): JSONObject {
|
||||
return JSONObject().apply {
|
||||
put("totalPrice", price)
|
||||
put("totalPriceStatus", "FINAL")
|
||||
put("countryCode", countryCode)
|
||||
put("currencyCode", currencyCode)
|
||||
}
|
||||
}
|
||||
|
||||
private val merchantInfo: JSONObject =
|
||||
JSONObject().put("merchantName", "Example Merchant")
|
||||
|
||||
|
||||
fun getPaymentDataRequest(
|
||||
price: String,
|
||||
countryCode: String,
|
||||
currencyCode: String,
|
||||
merchantID: String
|
||||
): JSONObject? {
|
||||
try {
|
||||
return baseRequest.apply {
|
||||
put("allowedPaymentMethods", JSONArray().put(cardPaymentMethod(merchantID)))
|
||||
put("transactionInfo", getTransactionInfo(price, countryCode, currencyCode))
|
||||
put("merchantInfo", merchantInfo)
|
||||
|
||||
// An optional shipping address requirement is a top-level property of the
|
||||
// PaymentDataRequest JSON object.
|
||||
val shippingAddressParameters = JSONObject().apply {
|
||||
put("phoneNumberRequired", false)
|
||||
// put("allowedCountryCodes", JSONArray(listOf("US", "GB")))
|
||||
}
|
||||
put("shippingAddressParameters", shippingAddressParameters)
|
||||
put("shippingAddressRequired", true)
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const val PAYMENTS_ENVIRONMENT = WalletConstants.ENVIRONMENT_TEST
|
||||
|
|
@ -0,0 +1,383 @@
|
|||
package com.tangem.tap.common.shop.shopify
|
||||
|
||||
import android.app.Application
|
||||
import com.shopify.buy3.GraphCallResult
|
||||
import com.shopify.buy3.GraphClient
|
||||
import com.shopify.buy3.RetryHandler
|
||||
import com.shopify.buy3.Storefront.*
|
||||
import com.shopify.graphql.support.ID
|
||||
import com.shopify.graphql.support.Input
|
||||
import com.tangem.tap.common.shop.shopify.data.CheckoutItem
|
||||
import com.tangem.tap.common.shop.shopify.data.checkoutFieldsFragment
|
||||
import com.tangem.tap.common.shop.shopify.data.collectionFieldsFragment
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
class ShopifyService(private val application: Application, val shop: ShopifyShop) {
|
||||
val client: GraphClient by lazy { initClient() }
|
||||
|
||||
|
||||
suspend fun getShopName(): Result<String> {
|
||||
val query = query { rootQuery: QueryRootQuery ->
|
||||
rootQuery
|
||||
.shop { shopQuery: ShopQuery ->
|
||||
shopQuery
|
||||
.name()
|
||||
}
|
||||
}
|
||||
return when (val result = queryAsync(query)) {
|
||||
is GraphCallResult.Success -> {
|
||||
val name = result.response.data!!.shop.name
|
||||
Result.success(name)
|
||||
}
|
||||
is GraphCallResult.Failure -> {
|
||||
Result.failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getProducts(collectionTitleFilter: String? = null): Result<List<Product>> {
|
||||
val filter = collectionTitleFilter?.let { "title:\"$it\"" }
|
||||
|
||||
val query = query { rootQuery: QueryRootQuery ->
|
||||
rootQuery
|
||||
.collections(
|
||||
{ arg -> arg.first(250).query(filter) },
|
||||
) { collectionConnectionQuery ->
|
||||
collectionConnectionQuery.collectionFieldsFragment()
|
||||
}
|
||||
}
|
||||
return when (val result = queryAsync(query)) {
|
||||
is GraphCallResult.Success -> {
|
||||
val products = result.response.data!!.collections.edges
|
||||
.map { it.node.products }
|
||||
.flatMap { it.edges }
|
||||
.map { it.node }
|
||||
Result.success(products)
|
||||
}
|
||||
is GraphCallResult.Failure -> {
|
||||
Result.failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkout(pollUntilOrder: Boolean, checkoutID: ID): Result<Checkout> {
|
||||
|
||||
val query = query { rootQuery: QueryRootQuery ->
|
||||
rootQuery
|
||||
.node(checkoutID) { query ->
|
||||
query.onCheckout { checkoutQuery ->
|
||||
with(checkoutQuery) {
|
||||
checkoutFieldsFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val retryHandler = RetryHandler.build<QueryRoot>(
|
||||
1, TimeUnit.SECONDS
|
||||
) {
|
||||
this.retryWhen { result ->
|
||||
when (result) {
|
||||
is GraphCallResult.Success -> {
|
||||
val checkout = result.response.data?.node as? Checkout
|
||||
checkout?.order == null
|
||||
}
|
||||
is GraphCallResult.Failure -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = if (pollUntilOrder) queryAsync(query, retryHandler) else queryAsync(query)
|
||||
return when (result) {
|
||||
is GraphCallResult.Success -> {
|
||||
val checkout = result.response.data!!.node as? Checkout
|
||||
if (checkout != null) {
|
||||
Result.success(checkout)
|
||||
} else {
|
||||
Result.failure(ShopifyError.Unknown)
|
||||
}
|
||||
|
||||
}
|
||||
is GraphCallResult.Failure -> {
|
||||
Result.failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createCheckout(
|
||||
checkoutItems: List<CheckoutItem>,
|
||||
checkoutID: ID? = null
|
||||
): Result<Checkout> {
|
||||
|
||||
|
||||
val storefrontLineItems: MutableList<CheckoutLineItemInput> = checkoutItems
|
||||
.map { CheckoutLineItemInput(it.quantity, it.id) }.toMutableList()
|
||||
|
||||
val query = if (checkoutID != null) {
|
||||
mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutLineItemsReplace(
|
||||
storefrontLineItems, checkoutID
|
||||
) { payloadQuery: CheckoutLineItemsReplacePayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.userErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val input = CheckoutCreateInput()
|
||||
.setLineItemsInput(
|
||||
Input.value(storefrontLineItems)
|
||||
)
|
||||
mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutCreate(
|
||||
input
|
||||
) { payloadQuery: CheckoutCreatePayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
suspend fun applyDiscount(discountCode: String, checkoutID: ID): Result<Checkout> {
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutDiscountCodeApplyV2(
|
||||
discountCode, checkoutID
|
||||
) { payloadQuery: CheckoutDiscountCodeApplyV2PayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
suspend fun removeDiscount(checkoutID: ID): Result<Checkout> {
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutDiscountCodeRemove(
|
||||
checkoutID
|
||||
) { payloadQuery: CheckoutDiscountCodeRemovePayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
suspend fun updateAddress(
|
||||
address: MailingAddress,
|
||||
checkoutID: ID,
|
||||
waitForShippingRates: Boolean
|
||||
): Result<Checkout> {
|
||||
val input = MailingAddressInput()
|
||||
.setAddress1(address.address1)
|
||||
.setAddress2(address.address2)
|
||||
.setCity(address.city)
|
||||
.setCountry(address.country)
|
||||
.setFirstName(address.firstName)
|
||||
.setLastName(address.lastName)
|
||||
.setPhone(address.phone)
|
||||
.setProvince(address.province)
|
||||
.setZip(address.zip)
|
||||
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutShippingAddressUpdateV2(
|
||||
input, checkoutID
|
||||
) { shippingAddressUpdatePayloadQuery: CheckoutShippingAddressUpdateV2PayloadQuery ->
|
||||
shippingAddressUpdatePayloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
suspend fun updateEmail(email: String?, checkoutID: ID): Result<Checkout> {
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutEmailUpdateV2(
|
||||
checkoutID, email
|
||||
) { emailUpdatePayloadQuery: CheckoutEmailUpdateV2PayloadQuery ->
|
||||
emailUpdatePayloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
suspend fun updateShippingRate(handle: String?, checkoutID: ID): Result<Checkout> {
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutShippingLineUpdate(
|
||||
checkoutID, handle
|
||||
) { shippingLineUpdatePayloadQuery: CheckoutShippingLineUpdatePayloadQuery ->
|
||||
shippingLineUpdatePayloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
suspend fun completeWithTokenizedPayment(
|
||||
payment: TokenizedPaymentInputV3,
|
||||
checkoutID: ID
|
||||
): Result<Checkout> {
|
||||
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutCompleteWithTokenizedPaymentV3(
|
||||
checkoutID, payment
|
||||
) { payloadQuery: CheckoutCompleteWithTokenizedPaymentV3PayloadQuery ->
|
||||
payloadQuery
|
||||
.payment { paymentQuery: PaymentQuery ->
|
||||
paymentQuery
|
||||
.ready()
|
||||
.errorMessage()
|
||||
}
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery
|
||||
.ready()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
fun startGooglePaySession() {
|
||||
// PaySession()
|
||||
}
|
||||
|
||||
private suspend fun runCheckoutMutation(mutation: MutationQuery): Result<Checkout> {
|
||||
return when (val result = mutationQueryAsync(mutation)) {
|
||||
is GraphCallResult.Success -> {
|
||||
val checkout = result.response.data!!.checkoutCreate?.checkout
|
||||
?: result.response.data!!.checkoutDiscountCodeApplyV2?.checkout
|
||||
?: result.response.data!!.checkoutDiscountCodeRemove?.checkout
|
||||
?: result.response.data!!.checkoutShippingAddressUpdateV2?.checkout
|
||||
?: result.response.data!!.checkoutEmailUpdateV2?.checkout
|
||||
?: result.response.data!!.checkoutShippingLineUpdate?.checkout
|
||||
?: result.response.data!!.checkoutCompleteWithTokenizedPaymentV3.checkout
|
||||
|
||||
Result.success(checkout)
|
||||
}
|
||||
is GraphCallResult.Failure -> Result.failure(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun queryAsync(
|
||||
query: QueryRootQuery,
|
||||
retryHandler: RetryHandler<QueryRoot>
|
||||
): GraphCallResult<QueryRoot> =
|
||||
withContext(Dispatchers.IO) {
|
||||
suspendCoroutine { continuation ->
|
||||
client.queryGraph(query).enqueue(retryHandler = retryHandler) { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun queryAsync(
|
||||
query: QueryRootQuery,
|
||||
): GraphCallResult<QueryRoot> =
|
||||
withContext(Dispatchers.IO) {
|
||||
suspendCoroutine { continuation ->
|
||||
client.queryGraph(query).enqueue { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun mutationQueryAsync(query: MutationQuery): GraphCallResult<Mutation> =
|
||||
withContext(Dispatchers.IO) {
|
||||
suspendCoroutine { continuation ->
|
||||
client.mutateGraph(query).enqueue { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun initClient(): GraphClient {
|
||||
return GraphClient.build(
|
||||
application,
|
||||
shop.domain,
|
||||
shop.storefrontApiKey
|
||||
) {
|
||||
// httpCache(application.filesDir) {
|
||||
// cacheMaxSizeBytes = (1024 * 1024 * 10)
|
||||
// defaultCachePolicy =
|
||||
// HttpCachePolicy.Default.CACHE_FIRST.expireAfter(20, TimeUnit.MINUTES)
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed class ShopifyError : Throwable() {
|
||||
object Unknown : ShopifyError()
|
||||
object GooglePayFailed : ShopifyError()
|
||||
class UserError(val errorMessage: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.tap.common.shop.shopify
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ShopifyShop(
|
||||
val domain: String,
|
||||
val storefrontApiKey: String,
|
||||
val merchantID: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
package com.tangem.tap.common.shop.shopify.data
|
||||
|
||||
import com.shopify.buy3.Storefront
|
||||
|
||||
fun Storefront.CheckoutQuery.checkoutFieldsFragment() {
|
||||
// id()
|
||||
ready()
|
||||
webUrl()
|
||||
currencyCode()
|
||||
lineItemsSubtotalPrice { it.amount() }
|
||||
totalPriceV2 {
|
||||
it.currencyCode()
|
||||
it.amount() }
|
||||
lineItems({ arg -> arg.first(250) }) {
|
||||
it.edges {
|
||||
it.node {
|
||||
// it.id()
|
||||
it.title()
|
||||
it.quantity()
|
||||
it.variant() {
|
||||
it.priceV2() { it.amount() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
shippingLine {
|
||||
it.handle()
|
||||
it.title()
|
||||
it.priceV2 { it.amount() }
|
||||
}
|
||||
availableShippingRates {
|
||||
it.ready()
|
||||
it.shippingRates {
|
||||
it.handle()
|
||||
it.title()
|
||||
it.priceV2 { it.amount() }
|
||||
}
|
||||
}
|
||||
shippingAddress {
|
||||
it.address1()
|
||||
it.address2()
|
||||
it.city()
|
||||
// .company()
|
||||
it.country()
|
||||
// .countryCodeV2()
|
||||
it.firstName()
|
||||
// .formatted()
|
||||
// .formattedArea()
|
||||
// .id()
|
||||
it.lastName()
|
||||
// .latitude()
|
||||
// .longitude()
|
||||
// .name()
|
||||
it.phone()
|
||||
it.province()
|
||||
// .provinceCode()
|
||||
it.zip()
|
||||
}
|
||||
discountApplications({ arg -> arg.first(250) }) {
|
||||
it.edges {
|
||||
it.node {
|
||||
it.onDiscountCodeApplication {
|
||||
it.code()
|
||||
// .applicable()
|
||||
// .allocationMethod()
|
||||
// .targetSelection()
|
||||
// .targetType()
|
||||
it.value {
|
||||
it.onMoneyV2 {
|
||||
it.amount()
|
||||
}
|
||||
it.onPricingPercentageValue {
|
||||
it.percentage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
order {
|
||||
it.cancelReason()
|
||||
it.canceledAt()
|
||||
it.currencyCode()
|
||||
// .currentSubtotalPrice()
|
||||
// .currentTotalDuties()
|
||||
// .currentTotalPrice()
|
||||
// .currentTotalTax()
|
||||
it.customerLocale()
|
||||
it.customerUrl()
|
||||
// .discountApplications()
|
||||
it.edited()
|
||||
it.email()
|
||||
it.financialStatus()
|
||||
it.fulfillmentStatus()
|
||||
// .id()
|
||||
// .lineItems()
|
||||
// .metafield()
|
||||
// .metafields()
|
||||
it.name()
|
||||
it.orderNumber()
|
||||
// .originalTotalDuties()
|
||||
// .originalTotalPrice()
|
||||
it.phone()
|
||||
it.processedAt()
|
||||
it.shippingAddress {
|
||||
it.address1()
|
||||
it.address2()
|
||||
it.city()
|
||||
it.company()
|
||||
it.country()
|
||||
it.countryCodeV2()
|
||||
it.firstName()
|
||||
it.formatted()
|
||||
it.formattedArea()
|
||||
// .id()
|
||||
it.lastName()
|
||||
it.latitude()
|
||||
it.longitude()
|
||||
it.name()
|
||||
it.phone()
|
||||
it.province()
|
||||
it.provinceCode()
|
||||
it.zip()
|
||||
}
|
||||
// .shippingDiscountAllocations()
|
||||
it.statusUrl()
|
||||
// .subtotalPriceV2()
|
||||
// .successfulFulfillments()
|
||||
// .totalPriceV2()
|
||||
// .totalRefundedV2()
|
||||
// .totalShippingPriceV2()
|
||||
// .totalTaxV2()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.tap.common.shop.shopify.data
|
||||
|
||||
import com.shopify.graphql.support.ID
|
||||
|
||||
data class CheckoutItem(
|
||||
val id: ID,
|
||||
val quantity: Int
|
||||
)
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.tap.common.shop.shopify.data
|
||||
|
||||
import com.shopify.buy3.Storefront
|
||||
|
||||
fun Storefront.CollectionConnectionQuery.collectionFieldsFragment() {
|
||||
edges { collectionEdgeQuery ->
|
||||
collectionEdgeQuery
|
||||
.node { collectionQuery ->
|
||||
collectionQuery
|
||||
.title()
|
||||
.products({ arg -> arg.first(250) }
|
||||
) { productConnectionQuery ->
|
||||
productConnectionQuery
|
||||
.edges { productEdgeQuery ->
|
||||
productEdgeQuery
|
||||
.node { productQuery ->
|
||||
productQuery.title()
|
||||
.productType()
|
||||
.description()
|
||||
.variants({ arg -> arg.first(10) }) { variantConnectionQuery ->
|
||||
variantConnectionQuery.edges { variantQuery ->
|
||||
variantQuery.node {
|
||||
it.title()
|
||||
it.sku()
|
||||
it.currentlyNotInStock()
|
||||
it.priceV2 {
|
||||
it.amount()
|
||||
it.currencyCode()
|
||||
}
|
||||
it.compareAtPriceV2 {
|
||||
it.amount()
|
||||
it.currencyCode()
|
||||
}
|
||||
it.compareAtPriceV2 {
|
||||
it.amount()
|
||||
}
|
||||
it.product {
|
||||
it.title()
|
||||
.productType()
|
||||
.description()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,33 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.blockchains.solana.RentProvider
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
import com.tangem.tap.domain.extensions.*
|
||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -30,7 +39,7 @@ class TapWalletManager {
|
|||
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
||||
}
|
||||
val walletManagerFactory: WalletManagerFactory
|
||||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
|
||||
suspend fun loadWalletData(walletManager: WalletManager) {
|
||||
handleUpdateWalletResult(walletManager.safeUpdate(), walletManager)
|
||||
|
|
@ -51,8 +60,8 @@ class TapWalletManager {
|
|||
|
||||
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
|
||||
val currencies = wallet.getTokens()
|
||||
.map { Currency.Token(it) }
|
||||
.plus(Currency.Blockchain(wallet.blockchain))
|
||||
.map { Currency.Token(it) }
|
||||
.plus(Currency.Blockchain(wallet.blockchain))
|
||||
loadFiatRate(fiatCurrency, currencies)
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +98,7 @@ class TapWalletManager {
|
|||
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.turnOff(ConfigManager.isTopUpEnabled)
|
||||
} else if (blockchain == Blockchain.Bitcoin
|
||||
|| data.walletData?.blockchain == Blockchain.Bitcoin.id) {
|
||||
|| data.walletData?.blockchain == Blockchain.Bitcoin.id) {
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
} else {
|
||||
|
|
@ -129,8 +138,7 @@ class TapWalletManager {
|
|||
loadMultiWalletData(data, blockchain, null)
|
||||
}
|
||||
}
|
||||
val moonPayStatus = store.state.globalState.moonpayStatus
|
||||
store.dispatch(WalletAction.LoadWallet(moonPayStatus))
|
||||
store.dispatch(WalletAction.LoadWallet())
|
||||
store.dispatch(WalletAction.LoadFiatRate())
|
||||
}
|
||||
}
|
||||
|
|
@ -156,7 +164,7 @@ class TapWalletManager {
|
|||
CardCurrencies(blockchains = blockchains, tokens = emptyList())
|
||||
))
|
||||
val walletManagers =
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList())
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList())
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains.toList()))
|
||||
}
|
||||
|
|
@ -165,12 +173,12 @@ class TapWalletManager {
|
|||
} else {
|
||||
val blockchains = savedCurrencies.blockchains.toList()
|
||||
val walletManagers = if (
|
||||
primaryTokens.isNotEmpty() &&
|
||||
primaryWalletManager != null && primaryBlockchain != null
|
||||
primaryTokens.isNotEmpty() &&
|
||||
primaryWalletManager != null && primaryBlockchain != null
|
||||
) {
|
||||
val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain }
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchainsWithoutPrimary)
|
||||
.plus(primaryWalletManager)
|
||||
.plus(primaryWalletManager)
|
||||
} else {
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains)
|
||||
}
|
||||
|
|
@ -192,8 +200,7 @@ class TapWalletManager {
|
|||
return@withContext
|
||||
}
|
||||
|
||||
val moonPayStatus = store.state.globalState.moonpayStatus
|
||||
store.dispatch(WalletAction.LoadWallet(moonPayStatus))
|
||||
store.dispatch(WalletAction.LoadWallet())
|
||||
store.dispatch(WalletAction.LoadFiatRate())
|
||||
}
|
||||
}
|
||||
|
|
@ -207,6 +214,9 @@ class TapWalletManager {
|
|||
data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
|
||||
WalletAction.LoadData.Failure(TapError.UnknownBlockchain)
|
||||
}
|
||||
data.isDemoCard() -> {
|
||||
return null
|
||||
}
|
||||
data.card.wallets.isEmpty() -> {
|
||||
WalletAction.EmptyWallet
|
||||
}
|
||||
|
|
@ -217,7 +227,10 @@ class TapWalletManager {
|
|||
private suspend fun handleUpdateWalletResult(result: Result<Wallet>, walletManager: WalletManager) {
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success -> store.dispatch(WalletAction.LoadWallet.Success(result.data))
|
||||
is Result.Success -> {
|
||||
checkForRentWarning(walletManager)
|
||||
store.dispatch(WalletAction.LoadWallet.Success(result.data))
|
||||
}
|
||||
is Result.Failure -> {
|
||||
val error = (result.error as? TapError) ?: TapError.UnknownError
|
||||
when (error) {
|
||||
|
|
@ -226,11 +239,11 @@ class TapWalletManager {
|
|||
}
|
||||
is TapError.WalletManagerUpdate.NoAccountError -> {
|
||||
store.dispatch(WalletAction.LoadWallet
|
||||
.NoAccount(walletManager.wallet, error.customMessage))
|
||||
.NoAccount(walletManager.wallet, error.customMessage))
|
||||
}
|
||||
is TapError.WalletManagerUpdate.InternalError -> {
|
||||
store.dispatch(WalletAction.LoadWallet
|
||||
.Failure(walletManager.wallet, error.customMessage))
|
||||
.Failure(walletManager.wallet, error.customMessage))
|
||||
}
|
||||
else -> {
|
||||
store.dispatchDebugErrorNotification(error)
|
||||
|
|
@ -241,6 +254,38 @@ class TapWalletManager {
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkForRentWarning(walletManager: WalletManager) {
|
||||
val rentProvider = walletManager as? RentProvider ?: return
|
||||
|
||||
scope.launch {
|
||||
when (val result = rentProvider.minimalBalanceForRentExemption()) {
|
||||
is com.tangem.blockchain.extensions.Result.Success -> {
|
||||
fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean = balance < rentExempt
|
||||
|
||||
val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
|
||||
val outgoingTxs = walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing)
|
||||
val rentExempt = result.data
|
||||
val show = if (outgoingTxs.isEmpty()) {
|
||||
isNeedToShowWarning(balance, rentExempt)
|
||||
} else {
|
||||
val outgoingAmount = outgoingTxs.sumOf { it.amount ?: BigDecimal.ZERO }
|
||||
val rest = balance.minus(outgoingAmount)
|
||||
isNeedToShowWarning(rest, rentExempt)
|
||||
}
|
||||
if (!show) return@launch
|
||||
|
||||
val currency = walletManager.wallet.blockchain.currency
|
||||
store.dispatchOnMain(WalletAction.SetWalletRent(
|
||||
blockchain = walletManager.wallet.blockchain,
|
||||
minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
|
||||
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
|
||||
))
|
||||
}
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleFiatRatesResult(results: List<Pair<Currency, Result<BigDecimal>?>>) {
|
||||
withContext(Dispatchers.Main) {
|
||||
results.map {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.domain.configurable.config
|
|||
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.tap.common.shop.shopify.ShopifyShop
|
||||
import com.tangem.tap.domain.configurable.Loader
|
||||
|
||||
/**
|
||||
|
|
@ -10,13 +11,15 @@ import com.tangem.tap.domain.configurable.Loader
|
|||
data class Config(
|
||||
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
|
||||
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
|
||||
val onramperApiKey: String = "pk_test_Ix2aCF3ej_5tcDKkBR7MChIvf5Nb0oPORPQ3Oal5G8I0",
|
||||
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
|
||||
val appsFlyerDevKey: String = "",
|
||||
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
|
||||
val isSendingToPayIdEnabled: Boolean = true,
|
||||
val isTopUpEnabled: Boolean = false,
|
||||
@Deprecated("Not relevant since version 3.23")
|
||||
val isCreatingTwinCardsAllowed: Boolean = false
|
||||
val isCreatingTwinCardsAllowed: Boolean = false,
|
||||
val shopify: ShopifyShop? = null
|
||||
)
|
||||
|
||||
class ConfigManager(
|
||||
|
|
@ -52,10 +55,10 @@ class ConfigManager(
|
|||
fun resetToDefault(name: String) {
|
||||
when (name) {
|
||||
isSendingToPayIdEnabled -> config =
|
||||
config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
|
||||
config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
|
||||
isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
|
||||
isCreatingTwinCardsAllowed -> config =
|
||||
config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
|
||||
config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +82,7 @@ class ConfigManager(
|
|||
config = config.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
moonPayApiKey = values.moonPayApiKey,
|
||||
onramperApiKey = values.onramperApiKey,
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey,
|
||||
blockchainSdkConfig = BlockchainSdkConfig(
|
||||
blockchairApiKey = values.blockchairApiKey,
|
||||
|
|
@ -87,10 +91,12 @@ class ConfigManager(
|
|||
infuraProjectId = values.infuraProjectId
|
||||
),
|
||||
appsFlyerDevKey = values.appsFlyerDevKey,
|
||||
shopify = values.shopifyShop,
|
||||
)
|
||||
defaultConfig = defaultConfig.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
moonPayApiKey = values.moonPayApiKey,
|
||||
onramperApiKey = values.onramperApiKey,
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey,
|
||||
blockchainSdkConfig = BlockchainSdkConfig(
|
||||
blockchairApiKey = values.blockchairApiKey,
|
||||
|
|
@ -99,6 +105,7 @@ class ConfigManager(
|
|||
infuraProjectId = values.infuraProjectId
|
||||
),
|
||||
appsFlyerDevKey = values.appsFlyerDevKey,
|
||||
shopify = values.shopifyShop,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.tap.domain.configurable.config
|
||||
|
||||
import com.tangem.tap.common.shop.shopify.ShopifyShop
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -14,12 +16,14 @@ class FeatureModel(
|
|||
class ConfigValueModel(
|
||||
val coinMarketCapKey: String,
|
||||
val moonPayApiKey: String,
|
||||
val onramperApiKey: String,
|
||||
val moonPayApiSecretKey: String,
|
||||
val blockchairApiKey: String?,
|
||||
val blockchairAuthorizationToken: String?,
|
||||
val blockcypherTokens: Set<String>?,
|
||||
val infuraProjectId: String?,
|
||||
val appsFlyerDevKey: String,
|
||||
val shopifyShop: ShopifyShop?
|
||||
)
|
||||
|
||||
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import com.tangem.wallet.R
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WarningMessagesManager(
|
||||
private val warningLoader: RemoteWarningLoader,
|
||||
private val warningLoader: RemoteWarningLoader,
|
||||
) {
|
||||
|
||||
private val warningsList: MutableList<WarningMessage> = mutableListOf()
|
||||
|
|
@ -33,15 +33,15 @@ class WarningMessagesManager(
|
|||
|
||||
fun getWarnings(location: WarningMessage.Location, forBlockchains: List<Blockchain> = emptyList()): List<WarningMessage> {
|
||||
return warningsList
|
||||
.filter { !it.isHidden && it.location.contains(location) }
|
||||
.filter {
|
||||
val list = it.blockchainList
|
||||
when {
|
||||
list == null -> true
|
||||
list.containsAny(forBlockchains) -> true
|
||||
else -> false
|
||||
}
|
||||
.filter { !it.isHidden && it.location.contains(location) }
|
||||
.filter {
|
||||
val list = it.blockchainList
|
||||
when {
|
||||
list == null -> true
|
||||
list.containsAny(forBlockchains) -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun hideWarning(warning: WarningMessage): Boolean {
|
||||
|
|
@ -49,7 +49,7 @@ class WarningMessagesManager(
|
|||
return when {
|
||||
foundWarning == null -> false
|
||||
foundWarning.type == WarningMessage.Type.Temporary
|
||||
|| foundWarning.type == WarningMessage.Type.AppRating -> {
|
||||
|| foundWarning.type == WarningMessage.Type.AppRating -> {
|
||||
if (foundWarning.isHidden) {
|
||||
false
|
||||
} else {
|
||||
|
|
@ -80,32 +80,32 @@ class WarningMessagesManager(
|
|||
|
||||
companion object {
|
||||
fun devCardWarning(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.alert_title,
|
||||
R.string.alert_developer_card,
|
||||
WarningMessage.Origin.Local
|
||||
"",
|
||||
"",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.alert_title,
|
||||
R.string.alert_developer_card,
|
||||
WarningMessage.Origin.Local
|
||||
)
|
||||
|
||||
fun alreadySignedHashesWarning(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
type = WarningMessage.Type.Temporary,
|
||||
priority = WarningMessage.Priority.Info,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.alert_title,
|
||||
R.string.alert_card_signed_transactions,
|
||||
WarningMessage.Origin.Local
|
||||
"",
|
||||
"",
|
||||
type = WarningMessage.Type.Temporary,
|
||||
priority = WarningMessage.Priority.Info,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.alert_title,
|
||||
R.string.alert_card_signed_transactions,
|
||||
WarningMessage.Origin.Local
|
||||
)
|
||||
|
||||
fun signedHashesMultiWalletWarning(): WarningMessage = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Temporary,
|
||||
priority = WarningMessage.Priority.Info,
|
||||
location = listOf(WarningMessage.Location.MainScreen),
|
||||
|
|
@ -117,15 +117,15 @@ class WarningMessagesManager(
|
|||
)
|
||||
|
||||
fun appRatingWarning(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
WarningMessage.Type.AppRating,
|
||||
WarningMessage.Priority.Info,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.warning_rate_app_title,
|
||||
R.string.warning_rate_app_message,
|
||||
WarningMessage.Origin.Local
|
||||
"",
|
||||
"",
|
||||
WarningMessage.Type.AppRating,
|
||||
WarningMessage.Priority.Info,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.warning_rate_app_title,
|
||||
R.string.warning_rate_app_message,
|
||||
WarningMessage.Origin.Local
|
||||
)
|
||||
|
||||
fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
|
||||
|
|
@ -133,15 +133,15 @@ class WarningMessagesManager(
|
|||
}
|
||||
|
||||
fun onlineVerificationFailed(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.warning_failed_to_verify_card_title,
|
||||
R.string.warning_failed_to_verify_card_message,
|
||||
WarningMessage.Origin.Local
|
||||
"",
|
||||
"",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.warning_failed_to_verify_card_title,
|
||||
R.string.warning_failed_to_verify_card_message,
|
||||
WarningMessage.Origin.Local
|
||||
)
|
||||
|
||||
fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage = WarningMessage(
|
||||
|
|
@ -162,13 +162,25 @@ class WarningMessagesManager(
|
|||
"",
|
||||
type = WarningMessage.Type.TestCard,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
listOf(WarningMessage.Location.MainScreen, WarningMessage.Location.SendScreen),
|
||||
null,
|
||||
R.string.alert_title,
|
||||
R.string.warning_testnet_card_message,
|
||||
WarningMessage.Origin.Local
|
||||
)
|
||||
|
||||
fun demoCardWarning(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
type = WarningMessage.Type.Permanent,
|
||||
priority = WarningMessage.Priority.Critical,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
null,
|
||||
R.string.alert_title,
|
||||
R.string.alert_demo_message,
|
||||
WarningMessage.Origin.Local
|
||||
)
|
||||
|
||||
const val REMAINING_SIGNATURES_WARNING = 10
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -5,8 +5,10 @@ import com.tangem.common.card.EllipticCurve
|
|||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
|
||||
|
|
@ -16,10 +18,12 @@ import com.tangem.operations.wallet.CreateWalletResponse
|
|||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.tap.domain.ProductType
|
||||
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
|
||||
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
|
||||
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
|
||||
import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
|
||||
|
||||
data class CreateProductWalletTaskResponse(
|
||||
|
|
@ -96,25 +100,27 @@ private class CreateWalletTangemNote : ProductCommandProcessor<CreateWalletRespo
|
|||
|
||||
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWalletTaskResponse> {
|
||||
|
||||
private lateinit var card: Card
|
||||
private var primaryCard: PrimaryCard? = null
|
||||
private var createWalletResponse: CreateWalletResponse? = null
|
||||
|
||||
override fun proceed(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
this.card = card
|
||||
val curves = card.getCurvesForNonCreatedWallets()
|
||||
|
||||
CreateWalletsTask(curves).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
createWalletResponse = result.data.createWalletResponses[0]
|
||||
val createWalletResponses = result.data.createWalletResponses
|
||||
when {
|
||||
card.settings.isBackupAllowed -> {
|
||||
linkPrimaryCard(session, callback)
|
||||
linkPrimaryCard(createWalletResponses, session, callback)
|
||||
}
|
||||
card.settings.isHDWalletAllowed -> {
|
||||
deriveKeys(session, callback)
|
||||
deriveKeys(createWalletResponses, session, callback)
|
||||
}
|
||||
else -> {
|
||||
callback(
|
||||
|
|
@ -124,7 +130,6 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
|
|
@ -132,6 +137,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
}
|
||||
|
||||
private fun linkPrimaryCard(
|
||||
createWalletResponse: List<CreateWalletResponse>,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
|
|
@ -139,7 +145,20 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
primaryCard = result.data
|
||||
deriveKeys(session, callback)
|
||||
when {
|
||||
card.settings.isHDWalletAllowed -> {
|
||||
deriveKeys(createWalletResponse, session, callback)
|
||||
}
|
||||
else -> {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
CreateProductWalletTaskResponse(
|
||||
card = session.environment.card!!, primaryCard = primaryCard
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
|
|
@ -149,28 +168,26 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
}
|
||||
|
||||
private fun deriveKeys(
|
||||
createWalletResponse: List<CreateWalletResponse>,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CreateProductWalletTaskResponse>) -> Unit,
|
||||
) {
|
||||
val derivationPaths = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
.mapNotNull { it.derivationPath() }
|
||||
val response = createWalletResponse.guard {
|
||||
val map = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
|
||||
createWalletResponse.forEach { response ->
|
||||
val blockchainsForCurve = getBlockchains(response.cardId).filter {
|
||||
it.getSupportedCurves().contains(response.wallet.curve)
|
||||
}
|
||||
val derivationPaths = blockchainsForCurve.mapNotNull { it.derivationPath() }
|
||||
if (derivationPaths.isNotEmpty()) {
|
||||
map[response.wallet.publicKey.toMapKey()] = derivationPaths
|
||||
}
|
||||
}
|
||||
if (map.isEmpty()) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.UnknownError()))
|
||||
return
|
||||
}
|
||||
|
||||
if (derivationPaths.isNullOrEmpty()) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
CreateProductWalletTaskResponse(
|
||||
card = session.environment.card!!, primaryCard = primaryCard
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
DeriveMultipleWalletPublicKeysTask(mapOf(response.wallet.publicKey.toMapKey() to derivationPaths))
|
||||
DeriveMultipleWalletPublicKeysTask(map)
|
||||
.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
@ -188,4 +205,12 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBlockchains(cardId: String): List<Blockchain> {
|
||||
return when {
|
||||
DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.demoBlockchains
|
||||
card.isTestCard -> listOf(Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet)
|
||||
else -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ScanResponse>) -> Unit
|
||||
) {
|
||||
val derivations = collectDerivations(card)
|
||||
if (derivations.isEmpty()) {
|
||||
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
|
|
|
|||
|
|
@ -13,9 +13,12 @@ import com.tangem.tap.common.extensions.appendIf
|
|||
import com.tangem.tap.common.extensions.readJsonFileToString
|
||||
import com.tangem.tap.domain.extensions.getCustomIconUrl
|
||||
import com.tangem.tap.domain.extensions.setCustomIconUrl
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.network.createMoshi
|
||||
import timber.log.Timber
|
||||
|
||||
class CurrenciesRepository(val context: Application) {
|
||||
|
||||
private val moshi = createMoshi()
|
||||
private val blockchainsAdapter: JsonAdapter<List<Blockchain>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, Blockchain::class.java)
|
||||
|
|
@ -28,10 +31,13 @@ class CurrenciesRepository(val context: Application) {
|
|||
)
|
||||
|
||||
fun loadCardCurrencies(cardId: String): CardCurrencies? {
|
||||
val blockchains = loadSavedBlockchains(cardId)
|
||||
val blockchains = loadSavedBlockchains(cardId).toMutableSet()
|
||||
if (DemoHelper.isDemoCardId(cardId)) {
|
||||
blockchains.addAll(DemoHelper.config.demoBlockchains)
|
||||
}
|
||||
if (blockchains.isEmpty()) return null
|
||||
|
||||
return CardCurrencies(loadSavedTokens(cardId), blockchains)
|
||||
return CardCurrencies(loadSavedTokens(cardId), blockchains.toList())
|
||||
}
|
||||
|
||||
fun saveCardCurrencies(cardId: String, currencies: CardCurrencies) {
|
||||
|
|
@ -117,16 +123,21 @@ class CurrenciesRepository(val context: Application) {
|
|||
getSupportedTokens()
|
||||
}
|
||||
|
||||
val blockchainTokens = supportedTokens.map {
|
||||
fromJsonToTokensDao(loadTokensJson(it), it)
|
||||
val blockchainTokens = supportedTokens.mapNotNull { blockchain ->
|
||||
loadTokensJson(blockchain)?.let { fromJsonToTokensDao(it, blockchain) }
|
||||
}
|
||||
val tokens = blockchainTokens.flatten().map { it.toToken() }
|
||||
return tokens
|
||||
}
|
||||
|
||||
private fun loadTokensJson(blockchain: Blockchain): String {
|
||||
private fun loadTokensJson(blockchain: Blockchain): String? {
|
||||
val fileName = getFileName(blockchain)
|
||||
return context.assets.readJsonFileToString(fileName)
|
||||
return try {
|
||||
context.assets.readJsonFileToString(fileName)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Tokens with the file name %s not found", fileName)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileName(blockchain: Blockchain): String {
|
||||
|
|
@ -143,12 +154,12 @@ class CurrenciesRepository(val context: Application) {
|
|||
|
||||
private fun getSupportedTokens(): List<Blockchain> {
|
||||
return listOf(
|
||||
Blockchain.Avalanche,
|
||||
Blockchain.Binance,
|
||||
Blockchain.BSC,
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.BSC,
|
||||
Blockchain.Binance,
|
||||
Blockchain.Polygon,
|
||||
Blockchain.Solana,
|
||||
Blockchain.Avalanche,
|
||||
Blockchain.Fantom,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -164,11 +175,11 @@ class CurrenciesRepository(val context: Application) {
|
|||
return excludeUnsupportedBlockchains(blockchains)
|
||||
}
|
||||
|
||||
//TODO: move to the App settings
|
||||
// Use this list to temporarily exclude a blockchain from the list of tokens.
|
||||
private fun excludeUnsupportedBlockchains(blockchains: List<Blockchain>): List<Blockchain> {
|
||||
return blockchains.toMutableList().apply {
|
||||
removeAll(listOf(
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet
|
||||
// Any blockchain
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -183,6 +194,13 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
fun Blockchain.getTokensName(): String {
|
||||
return when (this) {
|
||||
Blockchain.Fantom -> "Fantom Opera"
|
||||
else -> this.fullName
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenDao(
|
||||
val name: String,
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
package com.tangem.tap.domain.topup
|
||||
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TangemSigner
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdk
|
||||
import java.math.BigDecimal
|
||||
|
||||
class TopUpManager {
|
||||
suspend fun topUpTestErc20Tokens(walletManager: EthereumWalletManager, token: Token) {
|
||||
walletManager.update()
|
||||
|
||||
val amountToSend = Amount(walletManager.wallet.blockchain)
|
||||
val destinationAddress = token.contractAddress
|
||||
|
||||
val feeResult = walletManager.getFee(amountToSend,
|
||||
destinationAddress) as? Result.Success ?: return
|
||||
val fee = feeResult.data[0]
|
||||
|
||||
if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) {
|
||||
return
|
||||
}
|
||||
|
||||
val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress)
|
||||
|
||||
val signer = TangemSigner(
|
||||
tangemSdk = tangemSdk, Message()
|
||||
) { signResponse ->
|
||||
store.dispatch(
|
||||
GlobalAction.UpdateWalletSignedHashes(
|
||||
walletSignedHashes = signResponse.totalSignedHashes,
|
||||
walletPublicKey = walletManager.wallet.publicKey.seedKey,
|
||||
remainingSignatures = signResponse.remainingSignatures
|
||||
)
|
||||
)
|
||||
}
|
||||
walletManager.send(transaction, signer)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
|||
*/
|
||||
class TwinsCardWidget(
|
||||
val leapfrogWidget: LeapfrogWidget,
|
||||
private val deviceScaleFactor: Float = 1f,
|
||||
val getTopOfAnchorViewForActivateState: () -> Float
|
||||
) {
|
||||
|
||||
|
|
@ -87,20 +88,20 @@ class TwinsCardWidget(
|
|||
return when (cardNumber) {
|
||||
TwinCardNumber.First -> {
|
||||
TwinsCardProperties(
|
||||
xTranslation = -120f,
|
||||
yTranslation = -220f,
|
||||
xTranslation = -120f * deviceScaleFactor,
|
||||
yTranslation = -220f * deviceScaleFactor,
|
||||
rotation = -3f,
|
||||
elevation = 1f,
|
||||
scale = 1f,
|
||||
scale = deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
TwinCardNumber.Second -> {
|
||||
TwinsCardProperties(
|
||||
xTranslation = 170f,
|
||||
yTranslation = 170f,
|
||||
xTranslation = 170f * deviceScaleFactor,
|
||||
yTranslation = 170f * deviceScaleFactor,
|
||||
rotation = -3f,
|
||||
elevation = 0f,
|
||||
scale = 1f,
|
||||
scale = deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +124,7 @@ class TwinsCardWidget(
|
|||
xTranslation = twinProperties.xTranslation,
|
||||
yTranslation = twinProperties.yTranslation - topOfAnchorView,
|
||||
rotation = 0f,
|
||||
scale = twinProperties.scale - 0.5f,
|
||||
scale = (twinProperties.scale - 0.5f) * deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.common.extensions.toHexString
|
|||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.features.details.redux.walletconnect.*
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
|
||||
|
|
@ -35,18 +36,11 @@ class WalletConnectSdkHelper {
|
|||
id: Long,
|
||||
type: WcTransactionType,
|
||||
): WcTransactionData? {
|
||||
|
||||
val walletManager = getWalletManager(session) ?: return null
|
||||
try {
|
||||
walletManager.update()
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception)
|
||||
return null
|
||||
}
|
||||
|
||||
walletManager.safeUpdate()
|
||||
val wallet = walletManager.wallet
|
||||
val balance =
|
||||
wallet.amounts[AmountType.Coin]?.value ?: return null
|
||||
val balance = wallet.amounts[AmountType.Coin]?.value ?: return null
|
||||
|
||||
val gas = transaction.gas?.hexToBigDecimal()
|
||||
?: transaction.gasLimit?.hexToBigDecimal()
|
||||
|
|
|
|||
203
app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
Normal file
203
app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
package com.tangem.tap.features.demo
|
||||
|
||||
import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface DemoMiddleware {
|
||||
fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean
|
||||
}
|
||||
|
||||
object DemoHelper {
|
||||
val config = DemoConfig()
|
||||
|
||||
private val demoMiddlewares = listOf(
|
||||
DemoOnboardingNoteMiddleware(),
|
||||
)
|
||||
|
||||
private val disabledActionFeatures = listOf(
|
||||
WalletConnectAction.StartWalletConnect::class.java,
|
||||
WalletAction.TradeCryptoAction.Buy::class.java,
|
||||
WalletAction.TradeCryptoAction.Sell::class.java,
|
||||
BackupAction.StartBackup::class.java,
|
||||
WalletAction.ExploreAddress::class.java
|
||||
)
|
||||
|
||||
fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
|
||||
|
||||
fun isDemoCardId(cardId: String): Boolean = config.isDemoCardId(cardId)
|
||||
|
||||
fun tryHandle(appState: () -> AppState?, action: Action): Boolean {
|
||||
val scanResponse = getScanResponse(appState) ?: return false
|
||||
if (!scanResponse.isDemoCard()) return false
|
||||
|
||||
demoMiddlewares.forEach {
|
||||
if (it.tryHandle(config, scanResponse, action)) return true
|
||||
}
|
||||
|
||||
disabledActionFeatures.firstOrNull { it == action::class.java }?.let {
|
||||
store.dispatchNotification(R.string.alert_demo_feature_disabled)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun injectDemoBalance(walletManager: WalletManager?) {
|
||||
val manager = walletManager ?: return
|
||||
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
val amount = config.getBalance(blockchain)
|
||||
manager.wallet.setAmount(amount)
|
||||
}
|
||||
|
||||
private fun getScanResponse(appState: () -> AppState?): ScanResponse? {
|
||||
val state = appState() ?: return null
|
||||
|
||||
return state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: state.globalState.scanResponse
|
||||
}
|
||||
}
|
||||
|
||||
class DemoConfig {
|
||||
|
||||
val demoBlockchains = listOf(
|
||||
Blockchain.Bitcoin,
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.Dogecoin,
|
||||
Blockchain.Solana,
|
||||
)
|
||||
|
||||
val demoCardIds: List<String> by lazy {
|
||||
val demoIds = (releaseDemoCardIds + testDemoCardIds).toMutableList()
|
||||
if (BuildConfig.DEBUG) demoIds.addAll(debugTestDemoCardIds)
|
||||
|
||||
return@lazy demoIds.distinct()
|
||||
}
|
||||
|
||||
private val walletBalances: Map<Blockchain, Amount> = mapOf(
|
||||
Blockchain.Bitcoin to Amount(0.028.toBigDecimal(), Blockchain.Bitcoin),
|
||||
Blockchain.Ethereum to Amount(0.2311.toBigDecimal(), Blockchain.Ethereum),
|
||||
Blockchain.Dogecoin to Amount(1450.025.toBigDecimal(), Blockchain.Dogecoin),
|
||||
Blockchain.Solana to Amount(13.246.toBigDecimal(), Blockchain.Solana),
|
||||
)
|
||||
|
||||
fun isDemoCardId(cardId: String): Boolean = demoCardIds.contains(cardId)
|
||||
|
||||
fun getBalance(blockchain: Blockchain): Amount = walletBalances[blockchain]?.copy()
|
||||
?: Amount(BigDecimal.ZERO, blockchain).copy()
|
||||
|
||||
private val releaseDemoCardIds = mutableListOf<String>(
|
||||
// Tangem Wallet:
|
||||
"AC01000000041100",
|
||||
"AC01000000042462",
|
||||
"AC01000000041647",
|
||||
"AC01000000041621",
|
||||
"AC01000000041217",
|
||||
"AC01000000041225",
|
||||
"AC01000000041209",
|
||||
"AC01000000041092",
|
||||
"AC01000000041472",
|
||||
"AC01000000041662",
|
||||
"AC01000000045754",
|
||||
"AC01000000045960",
|
||||
// Tangem Note BTC:
|
||||
"AB0100000046530",
|
||||
"AB0100000046720",
|
||||
"AB0100000046746",
|
||||
"AB0100000046498",
|
||||
"AB0100000046753",
|
||||
"AB0100000049608",
|
||||
"AB0100000046761",
|
||||
"AB0100000049574",
|
||||
"AB0100000046605",
|
||||
"AB0100000046571",
|
||||
"AB0100000046704",
|
||||
"AB0100000046647",
|
||||
// Tangem Note Ethereum:
|
||||
"AB02000000051000",
|
||||
"AB02000000050986",
|
||||
"AB02000000051026",
|
||||
"AB02000000051042",
|
||||
"AB02000000051091",
|
||||
"AB02000000051083",
|
||||
"AB02000000050960",
|
||||
"AB02000000051034",
|
||||
"AB02000000050911",
|
||||
"AB02000000051133",
|
||||
"AB02000000051158",
|
||||
"AB02000000051059",
|
||||
)
|
||||
|
||||
private val testDemoCardIds = listOf(
|
||||
"FB20000000000186", // Note ETH
|
||||
"FB10000000000196", // Note BTC
|
||||
"FB30000000000176", // Wallet
|
||||
//TODO: delete bellow ids before 3.28 release
|
||||
"AB01000000045060", // Note BTC
|
||||
"AB02000000045028", // Note ETH
|
||||
"AC79000000000004", // Wallet 4.46
|
||||
)
|
||||
|
||||
private val debugTestDemoCardIds = listOf<String>(
|
||||
)
|
||||
}
|
||||
|
||||
class DemoTransactionSender(
|
||||
private val walletManager: WalletManager,
|
||||
private val sender: TransactionSender = walletManager as TransactionSender
|
||||
) : TransactionSender {
|
||||
|
||||
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
return when (walletManager) {
|
||||
is BitcoinWalletManager -> Result.Success(listOf(
|
||||
Amount(0.0001.toBigDecimal(), blockchain),
|
||||
Amount(0.0003.toBigDecimal(), blockchain),
|
||||
Amount(0.00055.toBigDecimal(), blockchain),
|
||||
))
|
||||
else -> sender.getFee(amount, destination)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
|
||||
val dataToSign = randomString(32).toByteArray()
|
||||
val signerResponse = signer.sign(dataToSign, walletManager.wallet.cardId, walletManager.wallet.publicKey)
|
||||
return when (signerResponse) {
|
||||
is CompletionResult.Success -> SimpleResult.Failure(Exception(ID))
|
||||
is CompletionResult.Failure -> SimpleResult.fromTangemSdkError(signerResponse.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun randomInt(from: Int, to: Int): Int = kotlin.random.Random.nextInt(from, to)
|
||||
|
||||
private fun randomString(length: Int): String {
|
||||
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
||||
return (1..length)
|
||||
.map { randomInt(0, charPool.size) }
|
||||
.map(charPool::get)
|
||||
.joinToString("")
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = DemoTransactionSender::class.java.simpleName
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.tangem.tap.features.demo
|
||||
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.tap.common.extensions.withMainContext
|
||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
|
||||
|
||||
override fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean {
|
||||
val globalState = store.state.globalState
|
||||
val noteState = store.state.onboardingNoteState
|
||||
|
||||
when (action) {
|
||||
is OnboardingNoteAction.Balance.Update -> {
|
||||
val walletManager = if (noteState.walletManager != null) {
|
||||
noteState.walletManager
|
||||
} else {
|
||||
val wmFactory = globalState.tapWalletManager.walletManagerFactory
|
||||
val walletManager = wmFactory.makePrimaryWalletManager(scanResponse).guard {
|
||||
return false
|
||||
}
|
||||
store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
|
||||
walletManager
|
||||
}
|
||||
val balanceAmount = config.getBalance(walletManager.wallet.blockchain)
|
||||
val loadedBalance = noteState.walletBalance.copy(
|
||||
value = balanceAmount.value!!,
|
||||
currency = Currency.Blockchain(walletManager.wallet.blockchain),
|
||||
state = ProgressState.Done,
|
||||
error = null,
|
||||
criticalError = null
|
||||
)
|
||||
walletManager.wallet.setAmount(balanceAmount)
|
||||
|
||||
scope.launch {
|
||||
withMainContext {
|
||||
store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance))
|
||||
store.dispatch(OnboardingNoteAction.Balance.SetCriticalError(loadedBalance.criticalError))
|
||||
store.dispatch(OnboardingNoteAction.Balance.SetNonCriticalError(loadedBalance.error))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
12
app/src/main/java/com/tangem/tap/features/demo/Extentions.kt
Normal file
12
app/src/main/java/com/tangem/tap/features/demo/Extentions.kt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tap.features.demo
|
||||
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun ScanResponse.isDemoCard(): Boolean = DemoHelper.isDemoCardId(card.cardId)
|
||||
fun WalletManager.isDemoWallet(): Boolean = DemoHelper.isDemoCardId(wallet.cardId)
|
||||
fun Wallet.isDemoWallet(): Boolean = DemoHelper.isDemoCardId(cardId)
|
||||
|
|
@ -17,9 +17,11 @@ import com.tangem.tap.domain.tasks.product.ScanResponse
|
|||
import com.tangem.tap.domain.walletconnect.BnbHelper
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class WalletConnectMiddleware {
|
||||
|
|
@ -28,138 +30,131 @@ class WalletConnectMiddleware {
|
|||
val walletConnectMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
handle(state, action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is WalletConnectAction.RestoreSessions -> {
|
||||
walletConnectManager.restoreSessions()
|
||||
}
|
||||
private fun handle(state: () -> AppState?, action: Action) {
|
||||
if (DemoHelper.tryHandle(state, action)) return
|
||||
|
||||
is WalletConnectAction.HandleDeepLink -> {
|
||||
if (!action.wcUri.isNullOrBlank()) {
|
||||
if (WalletConnectManager.isCorrectWcUri(action.wcUri)) {
|
||||
store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is WalletConnectAction.StartWalletConnect -> {
|
||||
val uri = action.activity.getFromClipboard()?.toString()
|
||||
if (uri != null && WalletConnectManager.isCorrectWcUri(uri)) {
|
||||
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.QrScan))
|
||||
}
|
||||
}
|
||||
|
||||
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.ClipboardOrScanQr(
|
||||
action.wcUri
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is WalletConnectAction.OpeningSessionTimeout -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
|
||||
}
|
||||
|
||||
is WalletConnectAction.FailureEstablishingSession -> {
|
||||
if (action.session != null) {
|
||||
walletConnectManager.disconnect(action.session)
|
||||
}
|
||||
}
|
||||
|
||||
is WalletConnectAction.UnsupportedCard -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard))
|
||||
}
|
||||
|
||||
is WalletConnectAction.OpenSession -> {
|
||||
walletConnectManager.connect(
|
||||
wcUri = action.wcUri,
|
||||
)
|
||||
}
|
||||
|
||||
is WalletConnectAction.RefuseOpeningSession -> {
|
||||
store.dispatch(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.OpeningSessionRejected
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is WalletConnectAction.ScanCard -> {
|
||||
scanCard(action.session, action.chainId)
|
||||
}
|
||||
|
||||
is WalletConnectAction.ApproveSession -> {
|
||||
walletConnectManager.approve(action.session)
|
||||
}
|
||||
|
||||
is WalletConnectAction.DisconnectSession -> {
|
||||
walletConnectManager.disconnect(action.session)
|
||||
}
|
||||
|
||||
is WalletConnectAction.HandleTransactionRequest -> {
|
||||
walletConnectManager.handleTransactionRequest(
|
||||
transaction = action.transaction,
|
||||
session = action.session,
|
||||
id = action.id,
|
||||
type = action.type
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.HandlePersonalSignRequest -> {
|
||||
walletConnectManager.handlePersonalSignRequest(
|
||||
message = action.message,
|
||||
session = action.session,
|
||||
id = action.id
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.RejectRequest -> {
|
||||
walletConnectManager.rejectRequest(action.session, action.id)
|
||||
}
|
||||
is WalletConnectAction.SendTransaction -> {
|
||||
walletConnectManager.completeTransaction(action.session)
|
||||
}
|
||||
is WalletConnectAction.SignMessage -> {
|
||||
walletConnectManager.sendSignedMessage(action.session)
|
||||
}
|
||||
is WalletConnectAction.BinanceTransaction.Trade -> {
|
||||
val messageData = BnbHelper.createMessageData(action.order)
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.BnbTransactionDialog(
|
||||
data = messageData,
|
||||
session = action.sessionData.session,
|
||||
sessionId = action.id,
|
||||
cardId = action.sessionData.wallet.cardId,
|
||||
dAppName = action.sessionData.peerMeta.name
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.BinanceTransaction.Transfer -> {
|
||||
val messageData = BnbHelper.createMessageData(action.order)
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.BnbTransactionDialog(
|
||||
data = messageData,
|
||||
session = action.sessionData.session,
|
||||
sessionId = action.id,
|
||||
cardId = action.sessionData.wallet.cardId,
|
||||
dAppName = action.sessionData.peerMeta.name
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.BinanceTransaction.Sign -> {
|
||||
walletConnectManager.signBnb(
|
||||
action.id, action.data, action.sessionData
|
||||
)
|
||||
when (action) {
|
||||
is WalletConnectAction.RestoreSessions -> {
|
||||
walletConnectManager.restoreSessions()
|
||||
}
|
||||
is WalletConnectAction.HandleDeepLink -> {
|
||||
if (!action.wcUri.isNullOrBlank()) {
|
||||
if (WalletConnectManager.isCorrectWcUri(action.wcUri)) {
|
||||
store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
is WalletConnectAction.StartWalletConnect -> {
|
||||
val uri = action.activity.getFromClipboard()?.toString()
|
||||
if (uri != null && WalletConnectManager.isCorrectWcUri(uri)) {
|
||||
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.QrScan))
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.ClipboardOrScanQr(
|
||||
action.wcUri
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.OpeningSessionTimeout -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.SessionTimeout))
|
||||
}
|
||||
is WalletConnectAction.FailureEstablishingSession -> {
|
||||
if (action.session != null) {
|
||||
walletConnectManager.disconnect(action.session)
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.UnsupportedCard -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard))
|
||||
}
|
||||
is WalletConnectAction.OpenSession -> {
|
||||
walletConnectManager.connect(
|
||||
wcUri = action.wcUri,
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.RefuseOpeningSession -> {
|
||||
store.dispatch(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.OpeningSessionRejected
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.ScanCard -> {
|
||||
scanCard(action.session, action.chainId)
|
||||
}
|
||||
is WalletConnectAction.ApproveSession -> {
|
||||
walletConnectManager.approve(action.session)
|
||||
}
|
||||
is WalletConnectAction.DisconnectSession -> {
|
||||
walletConnectManager.disconnect(action.session)
|
||||
}
|
||||
is WalletConnectAction.HandleTransactionRequest -> {
|
||||
walletConnectManager.handleTransactionRequest(
|
||||
transaction = action.transaction,
|
||||
session = action.session,
|
||||
id = action.id,
|
||||
type = action.type
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.HandlePersonalSignRequest -> {
|
||||
walletConnectManager.handlePersonalSignRequest(
|
||||
message = action.message,
|
||||
session = action.session,
|
||||
id = action.id
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.RejectRequest -> {
|
||||
walletConnectManager.rejectRequest(action.session, action.id)
|
||||
}
|
||||
is WalletConnectAction.SendTransaction -> {
|
||||
walletConnectManager.completeTransaction(action.session)
|
||||
}
|
||||
is WalletConnectAction.SignMessage -> {
|
||||
walletConnectManager.sendSignedMessage(action.session)
|
||||
}
|
||||
is WalletConnectAction.BinanceTransaction.Trade -> {
|
||||
val messageData = BnbHelper.createMessageData(action.order)
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.BnbTransactionDialog(
|
||||
data = messageData,
|
||||
session = action.sessionData.session,
|
||||
sessionId = action.id,
|
||||
cardId = action.sessionData.wallet.cardId,
|
||||
dAppName = action.sessionData.peerMeta.name
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.BinanceTransaction.Transfer -> {
|
||||
val messageData = BnbHelper.createMessageData(action.order)
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.BnbTransactionDialog(
|
||||
data = messageData,
|
||||
session = action.sessionData.session,
|
||||
sessionId = action.id,
|
||||
cardId = action.sessionData.wallet.cardId,
|
||||
dAppName = action.sessionData.peerMeta.name
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.BinanceTransaction.Sign -> {
|
||||
walletConnectManager.signBnb(
|
||||
action.id, action.data, action.sessionData
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -190,9 +185,9 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
|
||||
val walletManager = getWalletManager(scanResponse, blockchain).guard {
|
||||
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
|
||||
return
|
||||
}
|
||||
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
|
||||
return
|
||||
}
|
||||
|
||||
val wallet = walletManager.wallet
|
||||
val derivedKey =
|
||||
|
|
@ -227,7 +222,7 @@ class WalletConnectMiddleware {
|
|||
): WalletManager? {
|
||||
val card = scanResponse.card
|
||||
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
|
||||
val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
|
||||
val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
blockchain
|
||||
|
|
|
|||
|
|
@ -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<DetailsState> {
|
||||
|
||||
private val binding: FragmentDetailsConfirmBinding by viewBinding(FragmentDetailsConfirmBinding::bind)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
|
|
@ -47,38 +50,39 @@ class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm),
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
toolbar.setNavigationOnClickListener {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun newState(state: DetailsState) {
|
||||
if (activity == null) return
|
||||
if (activity == null || view == null) return
|
||||
setState(state)
|
||||
}
|
||||
|
||||
private fun setState(state: DetailsState) = with(binding) {
|
||||
when (state.confirmScreenState) {
|
||||
ConfirmScreenState.EraseWallet -> {
|
||||
toolbar.title = getString(R.string.details_row_title_reset_factory_settings)
|
||||
tv_warning_description.text = getString(R.string.details_row_title_reset_factory_settings_warning)
|
||||
btn_confirm.text = getString(R.string.details_row_title_reset_factory_settings)
|
||||
btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
null, null, getDrawable(R.drawable.ic_send), null
|
||||
tvWarningDescription.text = getString(R.string.details_row_title_reset_factory_settings_warning)
|
||||
btnConfirm.text = getString(R.string.details_row_title_reset_factory_settings)
|
||||
btnConfirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
null, null, getDrawable(R.drawable.ic_send), null
|
||||
)
|
||||
btn_confirm.setOnClickListener { store.dispatch(DetailsAction.ResetToFactory.Confirm) }
|
||||
btnConfirm.setOnClickListener { store.dispatch(DetailsAction.ResetToFactory.Confirm) }
|
||||
}
|
||||
ConfirmScreenState.LongTap, ConfirmScreenState.AccessCode,
|
||||
ConfirmScreenState.PassCode -> {
|
||||
toolbar.title = getString(R.string.details_row_title_manage_security)
|
||||
tv_warning_description.text = getString(R.string.details_security_management_warning)
|
||||
btn_confirm.text = getString(R.string.common_save_changes)
|
||||
btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
null, null, getDrawable(R.drawable.ic_save), null
|
||||
toolbar.title = getString(R.string.details_manage_security_title)
|
||||
tvWarningDescription.text = getString(R.string.details_security_management_warning)
|
||||
btnConfirm.text = getString(R.string.common_save_changes)
|
||||
btnConfirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
null, null, getDrawable(R.drawable.ic_save), null
|
||||
)
|
||||
btn_confirm.setOnClickListener { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) }
|
||||
btnConfirm.setOnClickListener { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<DetailsState> {
|
||||
|
||||
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<Det
|
|||
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){
|
||||
|
||||
if (state.cardInfo != null) {
|
||||
val cardId = if (state.isTangemTwins) {
|
||||
|
|
@ -71,61 +75,61 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
} else {
|
||||
state.cardInfo.cardId
|
||||
}
|
||||
tv_card_id.text = cardId
|
||||
tv_issuer.text = state.cardInfo.issuer
|
||||
tv_signed_hashes.text = getString(
|
||||
tvCardId.text = cardId
|
||||
tvIssuer.text = state.cardInfo.issuer
|
||||
tvSignedHashes.text = getString(
|
||||
R.string.details_row_subtitle_signed_hashes_format,
|
||||
state.cardInfo.signedHashes.toString()
|
||||
)
|
||||
}
|
||||
|
||||
tv_signed_hashes.show(!state.isTangemTwins)
|
||||
tv_signed_hashes_title.show(!state.isTangemTwins)
|
||||
tvSignedHashes.show(!state.isTangemTwins)
|
||||
tvSignedHashesTitle.show(!state.isTangemTwins)
|
||||
|
||||
tv_disclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
|
||||
tvDisclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
|
||||
|
||||
tv_card_tou.show(state.cardTermsOfUseUrl != null)
|
||||
tv_card_tou.setOnClickListener {
|
||||
tvCardTou.show(state.cardTermsOfUseUrl != null)
|
||||
tvCardTou.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = state.cardTermsOfUseUrl
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
if (state.isTangemTwins) {
|
||||
tv_reset_to_factory.show()
|
||||
tv_reset_to_factory.text = getText(R.string.details_row_title_twins_recreate)
|
||||
tv_reset_to_factory.setOnClickListener {
|
||||
tvResetToFactory.show()
|
||||
tvResetToFactory.text = getText(R.string.details_row_title_twins_recreate)
|
||||
tvResetToFactory.setOnClickListener {
|
||||
store.dispatch(DetailsAction.ReCreateTwinsWallet(state.twinCardsState.cardNumber!!))
|
||||
}
|
||||
} else {
|
||||
tv_reset_to_factory.show()
|
||||
tv_reset_to_factory.text = getText(R.string.details_row_title_reset_factory_settings)
|
||||
tv_reset_to_factory.setOnClickListener {
|
||||
tvResetToFactory.show()
|
||||
tvResetToFactory.text = getText(R.string.details_row_title_reset_factory_settings)
|
||||
tvResetToFactory.setOnClickListener {
|
||||
store.dispatch(DetailsAction.ResetToFactory.Check)
|
||||
store.dispatch(DetailsAction.ResetToFactory.Proceed)
|
||||
}
|
||||
}
|
||||
|
||||
tv_create_backup.show(state.createBackupAllowed)
|
||||
tv_create_backup.setOnClickListener {
|
||||
tvCreateBackup.show(state.createBackupAllowed)
|
||||
tvCreateBackup.setOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateBackup)
|
||||
}
|
||||
|
||||
tv_app_currency.text = state.appCurrencyState.fiatCurrencyName
|
||||
tvAppCurrency.text = state.appCurrencyState.fiatCurrencyName
|
||||
|
||||
tv_app_currency_title.setOnClickListener {
|
||||
tvAppCurrencyTitle.setOnClickListener {
|
||||
store.dispatch(DetailsAction.AppCurrencyAction.ChooseAppCurrency)
|
||||
}
|
||||
tv_send_feedback.setOnClickListener {
|
||||
tvSendFeedback.setOnClickListener {
|
||||
store.dispatch(GlobalAction.SendFeedback(FeedbackEmail()))
|
||||
}
|
||||
|
||||
tv_wallet_connect.show(state.scanResponse?.card?.isMultiwalletAllowed == true)
|
||||
tv_wallet_connect.setOnClickListener {
|
||||
tvWalletConnect.show(state.scanResponse?.card?.isMultiwalletAllowed == true)
|
||||
tvWalletConnect.setOnClickListener {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
|
||||
}
|
||||
|
||||
tv_security_title.setOnClickListener {
|
||||
llManageSecurity.setOnClickListener {
|
||||
store.dispatch(DetailsAction.ManageSecurity.CheckCurrentSecurityOption(state.scanResponse!!.card))
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +139,7 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
SecurityOption.AccessCode -> 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<Det
|
|||
} else {
|
||||
currencySelectionDialog.clear()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.common.card.Card
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
|
|
@ -13,14 +14,17 @@ import com.tangem.tap.features.details.redux.DetailsState
|
|||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_details_confirm.toolbar
|
||||
import kotlinx.android.synthetic.main.fragment_details_security.*
|
||||
import com.tangem.wallet.databinding.FragmentDetailsSecurityBinding
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import java.util.*
|
||||
|
||||
class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
|
||||
StoreSubscriber<DetailsState> {
|
||||
|
||||
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<SecurityOption>) {
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WalletConnectSession, WalletConnectSessionsAdapter.SessionsViewHolder>(
|
||||
|
|
@ -41,15 +42,14 @@ class WalletConnectSessionsAdapter
|
|||
RecyclerView.ViewHolder(view) {
|
||||
|
||||
fun bind(session: WalletConnectSession) {
|
||||
view.tv_card_id.text = view.context.getString(
|
||||
view.findViewById<TextView>(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<TextView>(R.id.tv_d_app_name).text = session.peerMeta.name
|
||||
|
||||
view.btn_disconnect.setOnClickListener {
|
||||
view.findViewById<Chip>(R.id.btn_disconnect).setOnClickListener {
|
||||
store.dispatch(WalletConnectAction.DisconnectSession(session.session))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<WalletConnectState> {
|
||||
|
||||
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<WalletConnectSession>) = with(binding) {
|
||||
walletConnectSessionsAdapter.submitList(sessions)
|
||||
rvWalletConnectSessions.show(sessions.isNotEmpty())
|
||||
tvNoSessions.show(sessions.isEmpty())
|
||||
tvNoSessionsTitle.show(sessions.isEmpty())
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DisclaimerState> {
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,9 @@ import com.tangem.common.card.Card
|
|||
import com.tangem.tap.common.extensions.sendEmail
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.domain.TapWorkarounds
|
||||
import com.tangem.tap.features.feedback.EmailData.Companion.appendBlankLine
|
||||
import com.tangem.tap.features.feedback.EmailData.Companion.appendDelimiter
|
||||
import com.tangem.wallet.R
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
|
|
@ -40,7 +43,8 @@ class FeedbackManager(
|
|||
val fileLog = if (emailData is ScanFailsEmail) createLogFile() else null
|
||||
activity.sendEmail(
|
||||
email = getSupportEmail(),
|
||||
subject = emailData.subject, message = emailData.joinTogether(infoHolder, emailData !is ScanFailsEmail),
|
||||
subject = activity.getString(emailData.subjectResId),
|
||||
message = emailData.joinTogether(activity, infoHolder),
|
||||
file = fileLog,
|
||||
onFail = onFail
|
||||
)
|
||||
|
|
@ -142,7 +146,7 @@ class AdditionalEmailInfo {
|
|||
cardFirmwareVersion = card.firmwareVersion.stringValue
|
||||
cardIssuer = card.issuer.name
|
||||
signedHashesCount = card.wallets
|
||||
.joinToString(";") { "${it.curve?.curve} - ${it.totalSignedHashes}" }
|
||||
.joinToString(";") { "${it.curve?.curve} - ${it.totalSignedHashes}" }
|
||||
}
|
||||
|
||||
fun setWalletsInfo(walletManagers: List<WalletManager>) {
|
||||
|
|
@ -201,43 +205,40 @@ class AdditionalEmailInfo {
|
|||
}
|
||||
|
||||
interface EmailData {
|
||||
val subject: String
|
||||
val mainMessage: String
|
||||
val subjectResId: Int
|
||||
val mainMessageResId: Int
|
||||
|
||||
fun getDataCollectionMessageResId(): Int = R.string.feedback_data_collection_message
|
||||
|
||||
fun prepare(infoHolder: AdditionalEmailInfo) {}
|
||||
|
||||
fun appendDelimiter(builder: StringBuilder) {
|
||||
builder.append("----------\n")
|
||||
}
|
||||
|
||||
fun appendBlankLine(builder: StringBuilder) {
|
||||
builder.append("\n")
|
||||
}
|
||||
|
||||
fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String
|
||||
|
||||
fun joinTogether(infoHolder: AdditionalEmailInfo, allowToErasePhoneInfo: Boolean): String {
|
||||
val allowToErasePhoneInfoDisclaimer = if (allowToErasePhoneInfo) {
|
||||
"Following information is optional. You can erase it if you don’t want to share it.\n\n"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
return "$mainMessage\n\n\n\n" +
|
||||
allowToErasePhoneInfoDisclaimer +
|
||||
createOptionalMessage(infoHolder)
|
||||
fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String {
|
||||
return StringBuilder().apply {
|
||||
append(context.getString(mainMessageResId))
|
||||
append("\n\n\n\n")
|
||||
append(context.getString(getDataCollectionMessageResId()))
|
||||
append(createOptionalMessage(infoHolder))
|
||||
}.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal fun StringBuilder.appendDelimiter() = append("----------\n")
|
||||
internal fun StringBuilder.appendBlankLine() = append("\n")
|
||||
}
|
||||
}
|
||||
|
||||
class RateCanBeBetterEmail : EmailData {
|
||||
override val subject: String = "My suggestions"
|
||||
override val mainMessage: String = "Tell us what functions you are missing, and we will try to help you."
|
||||
override val subjectResId: Int = R.string.feedback_subject_rate_negative
|
||||
override val mainMessageResId: Int = R.string.feedback_preface_rate_negative
|
||||
|
||||
override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
|
||||
val walletInfo = infoHolder.walletsInfo[0]
|
||||
return StringBuilder().apply {
|
||||
appendKeyValue("Card ID", infoHolder.cardId)
|
||||
appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
|
||||
appendBlankLine(this)
|
||||
appendBlankLine()
|
||||
appendKeyValue("Phone model", infoHolder.phoneModel)
|
||||
appendKeyValue("OS version", infoHolder.osVersion)
|
||||
appendKeyValue("App version", infoHolder.appVersion)
|
||||
|
|
@ -246,11 +247,21 @@ class RateCanBeBetterEmail : EmailData {
|
|||
}
|
||||
|
||||
class ScanFailsEmail : EmailData {
|
||||
override val subject: String = "Can’t scan a card"
|
||||
override val mainMessage: String = "Please tell us what card do you have?"
|
||||
|
||||
override val subjectResId: Int = R.string.feedback_subject_scan_failed
|
||||
override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
|
||||
|
||||
override fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String {
|
||||
return StringBuilder().apply {
|
||||
append(context.getString(mainMessageResId))
|
||||
append("\n\n\n\n")
|
||||
append(createOptionalMessage(infoHolder))
|
||||
}.toString()
|
||||
}
|
||||
|
||||
override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
|
||||
return StringBuilder().apply {
|
||||
appendBlankLine(this)
|
||||
appendBlankLine()
|
||||
appendKeyValue("Phone model", infoHolder.phoneModel)
|
||||
appendKeyValue("OS version", infoHolder.osVersion)
|
||||
appendKeyValue("App version", infoHolder.appVersion)
|
||||
|
|
@ -259,8 +270,8 @@ class ScanFailsEmail : EmailData {
|
|||
}
|
||||
|
||||
class SendTransactionFailedEmail(private val error: String) : EmailData {
|
||||
override val subject: String = "Can’t send a transaction"
|
||||
override val mainMessage: String = "Please tell us more about your issue. Every small detail can help."
|
||||
override val subjectResId: Int = R.string.feedback_subject_tx_failed
|
||||
override val mainMessageResId: Int = R.string.feedback_preface_tx_failed
|
||||
|
||||
override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
|
||||
val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo()
|
||||
|
|
@ -268,17 +279,17 @@ class SendTransactionFailedEmail(private val error: String) : EmailData {
|
|||
appendKeyValue("Card ID", infoHolder.cardId)
|
||||
appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
|
||||
appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
|
||||
appendDelimiter(this)
|
||||
appendDelimiter()
|
||||
appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
|
||||
appendKeyValue("Host", walletInfo.host)
|
||||
appendKeyValue("Token", infoHolder.token)
|
||||
appendKeyValue("Error", error)
|
||||
appendDelimiter(this)
|
||||
appendDelimiter()
|
||||
appendKeyValue("Source address", walletInfo.address)
|
||||
appendKeyValue("Destination address", infoHolder.destinationAddress)
|
||||
appendKeyValue("Amount", infoHolder.amount)
|
||||
appendKeyValue("Fee", infoHolder.fee)
|
||||
appendBlankLine(this)
|
||||
appendBlankLine()
|
||||
appendKeyValue("Phone model", infoHolder.phoneModel)
|
||||
appendKeyValue("OS version", infoHolder.osVersion)
|
||||
appendKeyValue("App version", infoHolder.appVersion)
|
||||
|
|
@ -288,16 +299,16 @@ class SendTransactionFailedEmail(private val error: String) : EmailData {
|
|||
}
|
||||
|
||||
class FeedbackEmail : EmailData {
|
||||
override val subject: String
|
||||
override val subjectResId: Int
|
||||
get() = if (isS2CCard) s2cSubject else tangemSubject
|
||||
override val mainMessage: String
|
||||
override val mainMessageResId: Int
|
||||
get() = if (isS2CCard) s2cMainMessage else tangemMainMessage
|
||||
|
||||
private val tangemSubject = "Tangem feedback"
|
||||
private val tangemMainMessage = "Hi support team,"
|
||||
private val tangemSubject: Int = R.string.feedback_subject_support_tangem
|
||||
private val tangemMainMessage: Int = R.string.feedback_preface_support
|
||||
|
||||
private val s2cSubject = "Feedback"
|
||||
private val s2cMainMessage = "Hi support team,"
|
||||
private val s2cSubject: Int = R.string.feedback_subject_support
|
||||
private val s2cMainMessage: Int = R.string.feedback_preface_support
|
||||
|
||||
private var isS2CCard = false
|
||||
|
||||
|
|
@ -312,26 +323,26 @@ class FeedbackEmail : EmailData {
|
|||
builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
|
||||
|
||||
infoHolder.walletsInfo.forEach {
|
||||
appendDelimiter(builder)
|
||||
builder.appendDelimiter()
|
||||
builder.appendKeyValue("Blockchain", it.blockchain.fullName)
|
||||
builder.appendKeyValue("Host", it.host)
|
||||
builder.appendKeyValue("Wallet address", it.address)
|
||||
builder.appendKeyValue("Explorer link", it.explorerLink)
|
||||
}
|
||||
appendBlankLine(builder)
|
||||
builder.appendBlankLine()
|
||||
|
||||
infoHolder.tokens.forEach { tokens ->
|
||||
appendDelimiter(builder)
|
||||
builder.appendDelimiter()
|
||||
builder.appendKeyValue("Blockchain", tokens.key.fullName)
|
||||
builder.appendKeyValue("Tokens", tokens.value.map { "${it.name} - ${it.symbol}" }.toString())
|
||||
}
|
||||
appendDelimiter(builder)
|
||||
appendBlankLine(builder)
|
||||
|
||||
builder.appendDelimiter()
|
||||
builder.appendBlankLine()
|
||||
// appendKeyValue("Outputs count", infoHolder.outputsCount)
|
||||
builder.appendKeyValue("Phone model", infoHolder.phoneModel)
|
||||
builder.appendKeyValue("OS version", infoHolder.osVersion)
|
||||
builder.appendKeyValue("App version", infoHolder.appVersion)
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HomeState>() {
|
||||
class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState> {
|
||||
|
||||
private lateinit var btnScanCard: ViewStateWidget
|
||||
|
||||
override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_home_top
|
||||
var homeState: MutableState<HomeState> = 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<HomeState>() {
|
|||
|
||||
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<ComposeView>(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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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) {}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<HomeState> = 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 = {})
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
@ -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) { }
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.tangem.tap.common.analytics.AnalyticsEvent
|
|||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.GetCardSourceParams
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.onCardScanned
|
||||
import com.tangem.tap.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.post
|
||||
|
|
@ -39,7 +38,7 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
when (action) {
|
||||
is HomeAction.Init -> {
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
store.dispatch(GlobalAction.GetMoonPayStatus)
|
||||
store.dispatch(GlobalAction.InitCurrencyExchangeManager)
|
||||
store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
|
||||
}
|
||||
is HomeAction.ShouldScanCardOnResume -> {
|
||||
|
|
@ -50,7 +49,7 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
}
|
||||
is HomeAction.ReadCard -> handleReadCard()
|
||||
is HomeAction.GoToShop -> {
|
||||
store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
|
||||
store.state.globalState.analyticsHandlers?.triggerEvent(
|
||||
event = AnalyticsEvent.GET_CARD,
|
||||
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.WELCOME.param)
|
||||
|
|
@ -74,14 +73,14 @@ private fun handleReadCard() {
|
|||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
val navigateTo = OnboardingHelper.whereToNavigate(scanResponse)
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse))
|
||||
navigateTo(navigateTo, store.state.homeState.shareTransition)
|
||||
navigateTo(navigateTo)
|
||||
} else {
|
||||
scope.launch {
|
||||
store.onCardScanned(scanResponse)
|
||||
withMainContext {
|
||||
if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly))
|
||||
navigateTo(AppScreen.OnboardingTwins, store.state.homeState.shareTransition)
|
||||
navigateTo(AppScreen.OnboardingTwins)
|
||||
} else {
|
||||
navigateTo(AppScreen.Wallet, null)
|
||||
}
|
||||
|
|
@ -99,7 +98,7 @@ private fun changeButtonState(state: ButtonState) {
|
|||
store.dispatch(HomeAction.ChangeScanCardButtonState(btnState))
|
||||
}
|
||||
|
||||
private fun navigateTo(screen: AppScreen, transition: FragmentShareTransition?) {
|
||||
private fun navigateTo(screen: AppScreen, transition: FragmentShareTransition? = null) {
|
||||
post(DELAY_SDK_DIALOG_CLOSE) {
|
||||
changeButtonState(ButtonState.ENABLED)
|
||||
store.dispatch(NavigationAction.NavigateTo(screen, transition))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
|
|
@ -8,11 +10,11 @@ import com.tangem.tap.common.extensions.dispatchDialogHide
|
|||
import com.tangem.tap.common.extensions.dispatchShare
|
||||
import com.tangem.tap.common.extensions.dispatchToastNotification
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.features.wallet.redux.AddressData
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.dialog_onboarding_address_info.*
|
||||
import kotlinx.android.synthetic.main.layout_pseudo_toolbar.*
|
||||
import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -22,30 +24,40 @@ class AddressInfoBottomSheetDialog(
|
|||
context: Context
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
init {
|
||||
this.setContentView(R.layout.dialog_onboarding_address_info)
|
||||
var binding: DialogOnboardingAddressInfoBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DialogOnboardingAddressInfoBinding
|
||||
.inflate(LayoutInflater.from(context))
|
||||
setContentView(binding!!.root)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
setOnCancelListener { store.dispatchDialogHide() }
|
||||
setOnCancelListener {
|
||||
store.dispatchDialogHide()
|
||||
binding = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
val data = stateDialog.addressData
|
||||
showData(data = stateDialog.addressData)
|
||||
}
|
||||
|
||||
imv_close.setOnClickListener {
|
||||
private fun showData(data: AddressData) = with(binding!!) {
|
||||
pseudoToolbar.imvClose.setOnClickListener {
|
||||
dismissWithAnimation = true
|
||||
cancel()
|
||||
}
|
||||
imv_qr_code.setImageBitmap(data.qrCode)
|
||||
tv_address.text = data.address
|
||||
btn_fl_copy_address.setOnClickListener {
|
||||
imvQrCode.setImageBitmap(data.qrCode)
|
||||
tvAddress.text = data.address
|
||||
btnFlCopyAddress.setOnClickListener {
|
||||
context.copyToClipboard(data.address)
|
||||
store.dispatchToastNotification(R.string.copy_toast_msg)
|
||||
}
|
||||
btn_fl_share.setOnClickListener {
|
||||
btnFlShare.setOnClickListener {
|
||||
store.dispatchShare(data.shareUrl)
|
||||
}
|
||||
tv_recieve_message.text = getQRReceiveMessage(tv_recieve_message.context, stateDialog.currency)
|
||||
tvReceiveMessage.text = getQRReceiveMessage(tvReceiveMessage.context, stateDialog.currency)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<T> : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber<T> {
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<OnboardingNoteState>() {
|
|||
|
||||
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<OnboardingNoteState>() {
|
|||
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<OnboardingNoteState>() {
|
|||
}
|
||||
|
||||
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<OnboardingNoteState>() {
|
|||
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<OnboardingNoteState>() {
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.hasWallets
|
||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.scope
|
||||
|
|
@ -33,14 +34,16 @@ class OnboardingNoteMiddleware {
|
|||
private val onboardingNoteMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleNoteAction(action, dispatch)
|
||||
handleNoteAction(state, action, dispatch)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNoteAction(action: Action, dispatch: DispatchFunction) {
|
||||
private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch: DispatchFunction) {
|
||||
if (action !is OnboardingNoteAction) return
|
||||
if (DemoHelper.tryHandle(appState, action)) return
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager ?: return
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ data class OnboardingNoteState(
|
|||
get() = steps.indexOf(currentStep)
|
||||
|
||||
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { thisRef, property ->
|
||||
store.state.globalState.moonpayStatus?.buyIsAllowed(walletBalance.currency) ?: false
|
||||
store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<OnboardingOtherCardsState>() {
|
||||
|
||||
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<OnboardingOtherCards
|
|||
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)
|
||||
binding.toolbar.setTitle(R.string.onboarding_title)
|
||||
store.dispatch(OnboardingOtherCardsAction.LoadCardArtwork)
|
||||
store.dispatch(OnboardingOtherCardsAction.DetermineStepOfScreen)
|
||||
}
|
||||
|
|
@ -57,64 +51,75 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
|
|||
}
|
||||
|
||||
override fun newState(state: OnboardingOtherCardsState) {
|
||||
if (activity == null) return
|
||||
if (activity == null || view == null) return
|
||||
if (state.currentStep == OnboardingOtherCardsStep.None) 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) {
|
||||
OnboardingOtherCardsStep.CreateWallet -> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ data class TwinCardsState(
|
|||
get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
|
||||
|
||||
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { thisRef, property ->
|
||||
store.state.globalState.moonpayStatus?.buyIsAllowed(walletBalance.currency) ?: false
|
||||
store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
|
|
@ -31,12 +32,7 @@ import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
|||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_onboarding_main.*
|
||||
import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
|
||||
import kotlinx.android.synthetic.main.layout_onboarding_container_top.*
|
||||
import kotlinx.android.synthetic.main.view_bg_twins_welcome.*
|
||||
import kotlinx.android.synthetic.main.view_onboarding_progress.*
|
||||
import kotlinx.android.synthetic.main.view_onboarding_tv_balance.*
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
|
||||
|
||||
class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
||||
|
||||
|
|
@ -47,12 +43,11 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
|
||||
private val assetReader: AssetReader by lazy {
|
||||
object : AssetReader {
|
||||
override fun readAssetAsString(name: String): String = requireContext().readAssetAsString(name)
|
||||
override fun readAssetAsString(name: String): String =
|
||||
requireContext().readAssetAsString(name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
postponeEnterTransition()
|
||||
|
|
@ -74,36 +69,44 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(this)
|
||||
|
||||
reconfigureLayoutForTwins()
|
||||
twinsWidget = TwinsCardWidget(LeapfrogWidget(cards_container)) { 285f }
|
||||
btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container)
|
||||
reconfigureLayoutForTwins(binding.onboardingTopContainer)
|
||||
|
||||
toolbar.title = getText(R.string.twins_recreate_toolbar)
|
||||
val typedValue = TypedValue()
|
||||
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
|
||||
val deviceScaleFactorForWelcomeState = typedValue.float
|
||||
|
||||
twinsWidget = TwinsCardWidget(LeapfrogWidget(binding.onboardingTopContainer.cardsContainer), deviceScaleFactorForWelcomeState) {
|
||||
285f * deviceScaleFactorForWelcomeState
|
||||
}
|
||||
btnRefreshBalanceWidget = RefreshBalanceWidget(binding.onboardingTopContainer.onboardingMainContainer)
|
||||
|
||||
binding.toolbar.title = getText(R.string.twins_recreate_toolbar)
|
||||
|
||||
Picasso.get()
|
||||
.load(Artwork.TWIN_CARD_1)
|
||||
.error(R.drawable.card_placeholder_black)
|
||||
.placeholder(R.drawable.card_placeholder_black)
|
||||
?.into(imv_twin_front_card)
|
||||
.load(Artwork.TWIN_CARD_1)
|
||||
.error(R.drawable.card_placeholder_black)
|
||||
.placeholder(R.drawable.card_placeholder_black)
|
||||
?.into(binding.onboardingTopContainer.imvTwinFrontCard)
|
||||
|
||||
Picasso.get()
|
||||
.load(Artwork.TWIN_CARD_2)
|
||||
.error(R.drawable.card_placeholder_white)
|
||||
.placeholder(R.drawable.card_placeholder_white)
|
||||
?.into(imv_twin_back_card)
|
||||
.load(Artwork.TWIN_CARD_2)
|
||||
.error(R.drawable.card_placeholder_white)
|
||||
.placeholder(R.drawable.card_placeholder_white)
|
||||
?.into(binding.onboardingTopContainer.imvTwinBackCard)
|
||||
}
|
||||
|
||||
private fun reconfigureLayoutForTwins() {
|
||||
imv_front_card.hide()
|
||||
imv_card_background.hide()
|
||||
cards_container.show()
|
||||
private fun reconfigureLayoutForTwins(containerBinding: LayoutOnboardingContainerTopBinding) =
|
||||
with(containerBinding) {
|
||||
imvFrontCard.hide()
|
||||
imvCardBackground.hide()
|
||||
cardsContainer.show()
|
||||
|
||||
imv_twin_front_card.transitionName = ShareElement.imvFrontCard
|
||||
imv_twin_back_card.transitionName = ShareElement.imvBackCard
|
||||
imvTwinFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
imvTwinBackCard.transitionName = ShareElement.imvBackCard
|
||||
|
||||
// if don't this, the bg_circle_... is overflow the app_bar
|
||||
app_bar.bringToFront()
|
||||
}
|
||||
// if don't this, the bg_circle_... is overflow the app_bar
|
||||
binding.appBar.bringToFront()
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
store.subscribe(this) { state ->
|
||||
|
|
@ -115,10 +118,10 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
}
|
||||
|
||||
override fun newState(state: TwinCardsState) {
|
||||
if (activity == null) return
|
||||
if (activity == null || view == null) return
|
||||
|
||||
pb_state.max = state.steps.size - 1
|
||||
pb_state.progress = state.progress
|
||||
pbBinding.pbState.max = state.steps.size - 1
|
||||
pbBinding.pbState.progress = state.progress
|
||||
|
||||
when (state.currentStep) {
|
||||
TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state)
|
||||
|
|
@ -139,13 +142,15 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
private fun setBalance(state: TwinCardsState) {
|
||||
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
|
||||
|
||||
if (state.balanceCriticalError == null) {
|
||||
val balanceValue = state.walletBalance.value.stripZeroPlainString()
|
||||
tv_balance_value.text = balanceValue
|
||||
tv_balance_currency.text = state.walletBalance.currency.currencySymbol
|
||||
} else {
|
||||
tv_balance_value.text = "–"
|
||||
tv_balance_currency.text = ""
|
||||
with(binding.onboardingTopContainer.onboardingTvBalance) {
|
||||
if (state.balanceCriticalError == null) {
|
||||
val balanceValue = state.walletBalance.value.stripZeroPlainString()
|
||||
tvBalanceValue.text = balanceValue
|
||||
tvBalanceCurrency.text = state.walletBalance.currency.currencySymbol
|
||||
} else {
|
||||
tvBalanceValue.text = "–"
|
||||
tvBalanceCurrency.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,203 +165,235 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.tangem.tap.common.analytics.AnalyticsEvent
|
|||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.GetCardSourceParams
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -16,6 +15,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen
|
|||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.extensions.hasWallets
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
|
|
@ -124,10 +124,15 @@ private fun handleWalletAction(action: Action) {
|
|||
} else {
|
||||
null
|
||||
}
|
||||
BackupAction.IntroduceBackup(url)
|
||||
if (walletState.backupState.backupStep == BackupStep.InitBackup ||
|
||||
walletState.backupState.backupStep == BackupStep.Finished) {
|
||||
BackupAction.IntroduceBackup(url)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
store.dispatch(newAction)
|
||||
newAction?.let { store.dispatch(it) }
|
||||
}
|
||||
OnboardingWalletAction.OnBackPressed -> {
|
||||
when (walletState.backupState.backupStep) {
|
||||
|
|
@ -164,14 +169,16 @@ class BackupMiddleware {
|
|||
val backupMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (action is BackupAction) handleBackupAction(action)
|
||||
if (action is BackupAction) handleBackupAction(state, action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackupAction(action: BackupAction) {
|
||||
private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) {
|
||||
if (DemoHelper.tryHandle(appState, action)) return
|
||||
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
|
||||
val globalState = store.state.globalState
|
||||
|
|
@ -215,13 +222,11 @@ private fun handleBackupAction(action: BackupAction) {
|
|||
}
|
||||
}
|
||||
is BackupAction.GoToShop -> {
|
||||
backupState.buyAdditionalCardsUrl?.let { url ->
|
||||
store.dispatchOpenUrl(url)
|
||||
store.state.globalState.analyticsHandlers?.triggerEvent(
|
||||
event = AnalyticsEvent.GET_CARD,
|
||||
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.ONBOARDING.param)
|
||||
)
|
||||
}
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
|
||||
store.state.globalState.analyticsHandlers?.triggerEvent(
|
||||
event = AnalyticsEvent.GET_CARD,
|
||||
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.ONBOARDING.param)
|
||||
)
|
||||
}
|
||||
is BackupAction.FinishAddingBackupCards -> {
|
||||
if (backupService.addedBackupCardsCount == 1) {
|
||||
|
|
@ -233,9 +238,11 @@ private fun handleBackupAction(action: BackupAction) {
|
|||
}
|
||||
is BackupAction.CheckAccessCode -> {
|
||||
if (action.accessCode.length < 4) {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(
|
||||
AccessCodeError.CodeTooShort
|
||||
))
|
||||
store.dispatch(
|
||||
BackupAction.SetAccessCodeError(
|
||||
AccessCodeError.CodeTooShort
|
||||
)
|
||||
)
|
||||
} else {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(null))
|
||||
store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode))
|
||||
|
|
@ -247,9 +254,11 @@ private fun handleBackupAction(action: BackupAction) {
|
|||
backupService.setAccessCode(action.accessCodeConfirmation)
|
||||
store.dispatch(BackupAction.PrepareToWritePrimaryCard)
|
||||
} else {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(
|
||||
AccessCodeError.CodesDoNotMatch
|
||||
))
|
||||
store.dispatch(
|
||||
BackupAction.SetAccessCodeError(
|
||||
AccessCodeError.CodesDoNotMatch
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
is BackupAction.WritePrimaryCard -> {
|
||||
|
|
|
|||
|
|
@ -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<BackupInfoViewHolder>() {
|
||||
|
||||
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)
|
||||
class BackupInfoViewHolder(val binding: ItemBackupInfoAdapterBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
|
@ -1,18 +1,21 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.*
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import androidx.transition.TransitionManager
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.common.CardIdFormatter
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
|
|
@ -21,18 +24,21 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
|||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.fragment_onboarding_wallet.*
|
||||
import kotlinx.android.synthetic.main.layout_onboarding_buttons_add_cards.*
|
||||
import kotlinx.android.synthetic.main.layout_onboarding_buttons_common.*
|
||||
import kotlinx.android.synthetic.main.view_confetti.*
|
||||
import kotlinx.android.synthetic.main.view_onboarding_progress.*
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
||||
StoreSubscriber<OnboardingWalletState>, FragmentOnBackPressedHandler {
|
||||
|
||||
private var accessCodeDialog: AccessCodeDialog? = null
|
||||
private lateinit var cardsWidget: BackupCardsWidget
|
||||
private lateinit var cardsWidget: WalletCardsWidget
|
||||
private val binding: FragmentOnboardingWalletBinding by viewBinding(
|
||||
FragmentOnboardingWalletBinding::bind
|
||||
)
|
||||
private val pbBinding: ViewOnboardingProgressBinding by viewBinding(
|
||||
ViewOnboardingProgressBinding::bind)
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -47,19 +53,29 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val leapfrog = LeapfrogWidget(fl_cards_container)
|
||||
cardsWidget = BackupCardsWidget(leapfrog) { 200f }
|
||||
val typedValue = TypedValue()
|
||||
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
|
||||
val deviceScaleFactor = typedValue.float
|
||||
|
||||
val leapfrogCalculator = PropertyCalculator(
|
||||
yTranslationFactor = 25f * deviceScaleFactor,
|
||||
)
|
||||
val leapfrog = LeapfrogWidget(binding.flCardsContainer, leapfrogCalculator)
|
||||
cardsWidget = WalletCardsWidget(leapfrog, deviceScaleFactor) { 200f * deviceScaleFactor }
|
||||
startPostponedEnterTransition()
|
||||
|
||||
view_pager_backup_info.adapter = BackupInfoAdapter()
|
||||
TabLayoutMediator(tab_layout_backup_info, view_pager_backup_info) { tab, position ->
|
||||
binding.viewPagerBackupInfo.adapter = BackupInfoAdapter()
|
||||
TabLayoutMediator(
|
||||
binding.tabLayoutBackupInfo,
|
||||
binding.viewPagerBackupInfo
|
||||
) { tab, position ->
|
||||
//Some implementation
|
||||
}.attach()
|
||||
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
|
||||
store.dispatch(OnboardingWalletAction.Init)
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
|
||||
store.dispatch(OnboardingWalletAction.LoadArtwork)
|
||||
|
||||
|
|
@ -81,16 +97,18 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
}
|
||||
|
||||
override fun newState(state: OnboardingWalletState) {
|
||||
if (activity == null) return
|
||||
if (activity == null || view == null) return
|
||||
|
||||
requireActivity().invalidateOptionsMenu()
|
||||
|
||||
loadImageIntoImageView(state.cardArtworkUrl, imv_front_card)
|
||||
loadImageIntoImageView(state.cardArtworkUrl, imv_first_backup_card)
|
||||
loadImageIntoImageView(state.cardArtworkUrl, imv_second_backup_card)
|
||||
with(binding) {
|
||||
loadImageIntoImageView(state.cardArtworkUrl, imvFrontCard)
|
||||
loadImageIntoImageView(state.cardArtworkUrl, imvFirstBackupCard)
|
||||
loadImageIntoImageView(state.cardArtworkUrl, imvSecondBackupCard)
|
||||
}
|
||||
|
||||
pb_state.max = 6
|
||||
pb_state.progress = state.getProgressStep()
|
||||
pbBinding.pbState.max = 6
|
||||
pbBinding.pbState.progress = state.getProgressStep()
|
||||
|
||||
when (state.step) {
|
||||
OnboardingWalletStep.None -> {
|
||||
|
|
@ -111,25 +129,24 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
?.into(view)
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState() {
|
||||
btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btn_main_action.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) }
|
||||
btn_alternative_action.hide()
|
||||
private fun setupCreateWalletState() = with(binding) {
|
||||
layoutButtonsCommon.btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
layoutButtonsCommon.btnMainAction.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) }
|
||||
layoutButtonsCommon.btnAlternativeAction.hide()
|
||||
|
||||
toolbar.title = getText(R.string.onboarding_getting_started)
|
||||
|
||||
tv_header.setText(R.string.onboarding_create_wallet_header)
|
||||
tv_body.setText(R.string.onboarding_create_wallet_body)
|
||||
tvHeader.setText(R.string.onboarding_create_wallet_header)
|
||||
tvBody.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
cardsWidget.toFolded()
|
||||
startPostponedEnterTransition()
|
||||
cardsWidget.toFolded(false) { startPostponedEnterTransition() }
|
||||
}
|
||||
|
||||
|
||||
private fun setBackupState(state: BackupState) {
|
||||
when (state.backupStep) {
|
||||
BackupStep.InitBackup -> showBackupIntro(state)
|
||||
BackupStep.ScanOriginCard -> showScanOriginCard(state)
|
||||
BackupStep.ScanOriginCard -> showScanOriginCard()
|
||||
BackupStep.AddBackupCards -> showAddBackupCards(state)
|
||||
BackupStep.SetAccessCode -> showSetAccessCode()
|
||||
BackupStep.EnterAccessCode -> showEnterAccessCode(state)
|
||||
|
|
@ -140,65 +157,70 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
}
|
||||
}
|
||||
|
||||
private fun showBackupIntro(state: BackupState) {
|
||||
imv_first_backup_card.show()
|
||||
imv_second_backup_card.show()
|
||||
private fun showBackupIntro(state: BackupState) = with(binding) {
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
|
||||
cardsWidget.toWelcome()
|
||||
|
||||
tv_header.hide()
|
||||
tv_body.hide()
|
||||
view_pager_backup_info.show()
|
||||
tab_layout_backup_info.show()
|
||||
tvHeader.hide()
|
||||
tvBody.hide()
|
||||
viewPagerBackupInfo.show()
|
||||
tabLayoutBackupInfo.show()
|
||||
|
||||
with(layoutButtonsCommon) {
|
||||
btnMainAction.text = getText(R.string.onboarding_button_backup_now)
|
||||
btnAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
|
||||
btnAlternativeAction.show(state.canSkipBackup)
|
||||
btnMainAction.setOnClickListener { store.dispatch(BackupAction.StartBackup) }
|
||||
btnAlternativeAction.setOnClickListener { store.dispatch(BackupAction.DismissBackup) }
|
||||
}
|
||||
|
||||
btn_main_action.text = getText(R.string.onboarding_button_backup_now)
|
||||
btn_alternative_action.text = getText(R.string.onboarding_button_skip_backup)
|
||||
btn_alternative_action.show(state.canSkipBackup)
|
||||
btn_main_action.setOnClickListener { store.dispatch(BackupAction.StartBackup) }
|
||||
btn_alternative_action.setOnClickListener { store.dispatch(BackupAction.DismissBackup) }
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
|
||||
private fun showScanOriginCard(state: BackupState) {
|
||||
private fun showScanOriginCard() = with(binding) {
|
||||
prepareBackupView()
|
||||
|
||||
cardsWidget.toFolded()
|
||||
|
||||
tv_header.text = getText(R.string.onboarding_title_scan_origin_card)
|
||||
tv_body.text = getString(
|
||||
tvHeader.text = getText(R.string.onboarding_title_scan_origin_card)
|
||||
tvBody.text = getString(
|
||||
R.string.onboarding_subtitle_scan_origin_card,
|
||||
)
|
||||
|
||||
btn_main_action.text = getString(R.string.onboarding_button_scan_origin_card)
|
||||
btn_alternative_action.hide()
|
||||
btn_main_action.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
|
||||
with(layoutButtonsCommon) {
|
||||
btnMainAction.text = getString(R.string.onboarding_button_scan_origin_card)
|
||||
btnAlternativeAction.hide()
|
||||
btnMainAction.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareBackupView() {
|
||||
private fun prepareBackupView() = with(binding) {
|
||||
toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup)
|
||||
|
||||
tv_header.show()
|
||||
tv_body.show()
|
||||
view_pager_backup_info.hide()
|
||||
tab_layout_backup_info.hide()
|
||||
tvHeader.show()
|
||||
tvBody.show()
|
||||
viewPagerBackupInfo.hide()
|
||||
tabLayoutBackupInfo.hide()
|
||||
|
||||
imv_first_backup_card.show()
|
||||
imv_second_backup_card.show()
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
}
|
||||
|
||||
private fun showAddBackupCards(state: BackupState) {
|
||||
private fun showAddBackupCards(state: BackupState) = with(binding) {
|
||||
prepareBackupView()
|
||||
|
||||
accessCodeDialog?.dismiss()
|
||||
accessCodeDialog = null
|
||||
|
||||
layout_buttons_add_cards.show()
|
||||
layout_buttons_common.hide()
|
||||
btn_add_card.text = getText(R.string.onboarding_button_add_backup_card)
|
||||
layoutButtonsAddCards.root.show()
|
||||
layoutButtonsCommon.root.hide()
|
||||
layoutButtonsAddCards.btnAddCard.text = getText(R.string.onboarding_button_add_backup_card)
|
||||
if (state.backupCardsNumber < state.maxBackupCards) {
|
||||
btn_add_card.setOnClickListener { store.dispatch(BackupAction.AddBackupCard) }
|
||||
layoutButtonsAddCards.btnAddCard.setOnClickListener { store.dispatch(BackupAction.AddBackupCard) }
|
||||
} else {
|
||||
btn_add_card.isEnabled = false
|
||||
layoutButtonsAddCards.btnAddCard.isEnabled = false
|
||||
}
|
||||
|
||||
when (state.backupCardsNumber) {
|
||||
|
|
@ -207,27 +229,30 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
cardsWidget.getFirstBackupCardView().animate().alpha(0.6f).setDuration(200)
|
||||
cardsWidget.getSecondBackupCardView().animate().alpha(0.2f).setDuration(200)
|
||||
}
|
||||
tv_header.text = getText(R.string.onboarding_title_no_backup_cards)
|
||||
tv_body.text = getText(R.string.onboarding_subtitle_no_backup_cards)
|
||||
tvHeader.text = getText(R.string.onboarding_title_no_backup_cards)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards)
|
||||
}
|
||||
1 -> {
|
||||
tv_header.text = getText(R.string.onboarding_title_one_backup_card)
|
||||
tv_body.text = getText(R.string.onboarding_subtitle_one_backup_card)
|
||||
tvHeader.text = getText(R.string.onboarding_title_one_backup_card)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card)
|
||||
|
||||
cardsWidget.toFan(false)
|
||||
cardsWidget.getFirstBackupCardView().animate().alpha(1f).setDuration(400)
|
||||
cardsWidget.getSecondBackupCardView().alpha = 0.2f
|
||||
}
|
||||
2 -> {
|
||||
tv_header.text = getText(R.string.onboarding_title_two_backup_cards)
|
||||
tv_body.text = getText(R.string.onboarding_subtitle_two_backup_cards)
|
||||
tvHeader.text = getText(R.string.onboarding_title_two_backup_cards)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards)
|
||||
|
||||
cardsWidget.toFan(false)
|
||||
cardsWidget.getFirstBackupCardView().alpha = 1f
|
||||
cardsWidget.getSecondBackupCardView().animate().alpha(1f).setDuration(400)
|
||||
}
|
||||
}
|
||||
|
||||
btn_continue.text = getText(R.string.onboarding_button_finalize_backup)
|
||||
btn_continue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) }
|
||||
btn_continue.isEnabled = state.backupCardsNumber != 0
|
||||
layoutButtonsAddCards.btnContinue.text = getText(R.string.onboarding_button_finalize_backup)
|
||||
layoutButtonsAddCards.btnContinue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) }
|
||||
layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0
|
||||
}
|
||||
|
||||
private fun showSetAccessCode() {
|
||||
|
|
@ -258,7 +283,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
accessCodeDialog?.showError(state.accessCodeError)
|
||||
}
|
||||
|
||||
private fun showWritePrimaryCard(state: BackupState) {
|
||||
private fun showWritePrimaryCard(state: BackupState) = with(binding) {
|
||||
accessCodeDialog?.dismiss()
|
||||
|
||||
prepareViewForFinalizeStep()
|
||||
|
|
@ -270,33 +295,33 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
}
|
||||
|
||||
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
|
||||
tv_header.text = getText(R.string.onboarding_title_prepare_origin)
|
||||
tv_body.text = getString(
|
||||
tvHeader.text = getText(R.string.onboarding_title_prepare_origin)
|
||||
tvBody.text = getString(
|
||||
R.string.onboarding_subtitle_scan_primary_card_format,
|
||||
state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) }
|
||||
)
|
||||
btn_main_action.text = getText(R.string.onboarding_button_backup_origin)
|
||||
btn_main_action.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) }
|
||||
layoutButtonsCommon.btnMainAction.text = getText(R.string.onboarding_button_backup_origin)
|
||||
layoutButtonsCommon.btnMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) }
|
||||
|
||||
}
|
||||
|
||||
private fun prepareViewForFinalizeStep() {
|
||||
layout_buttons_add_cards.hide()
|
||||
layout_buttons_common.show()
|
||||
private fun prepareViewForFinalizeStep() = with(binding) {
|
||||
layoutButtonsAddCards.root.hide()
|
||||
layoutButtonsCommon.root.show()
|
||||
|
||||
imv_first_backup_card.show()
|
||||
imv_second_backup_card.show()
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
|
||||
toolbar.title = getText(R.string.onboarding_button_finalize_backup)
|
||||
|
||||
imv_card_background.hide()
|
||||
imvCardBackground.hide()
|
||||
|
||||
cardsWidget.toLeapfrog()
|
||||
|
||||
btn_alternative_action.hide()
|
||||
layoutButtonsCommon.btnAlternativeAction.hide()
|
||||
}
|
||||
|
||||
private fun showWriteBackupCard(state: BackupState) {
|
||||
private fun showWriteBackupCard(state: BackupState) = with(binding) {
|
||||
prepareViewForFinalizeStep()
|
||||
|
||||
cardsWidget.getSecondBackupCardView().show(state.backupCardsNumber == 2)
|
||||
|
|
@ -316,46 +341,47 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
}
|
||||
|
||||
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
|
||||
tv_header.text =
|
||||
tvHeader.text =
|
||||
getString(R.string.onboarding_title_backup_card_format, cardNumber)
|
||||
tv_body.text = getString(
|
||||
tvBody.text = getString(
|
||||
R.string.onboarding_subtitle_scan_backup_card_format,
|
||||
cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1])
|
||||
)
|
||||
btn_main_action.text = getString(
|
||||
layoutButtonsCommon.btnMainAction.text = getString(
|
||||
R.string.onboarding_button_backup_card_format,
|
||||
cardNumber
|
||||
)
|
||||
|
||||
btn_main_action.setOnClickListener { store.dispatch(BackupAction.WriteBackupCard(cardNumber)) }
|
||||
layoutButtonsCommon.btnMainAction.setOnClickListener {
|
||||
store.dispatch(BackupAction.WriteBackupCard(cardNumber))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSuccess() {
|
||||
tv_header.text = getText(R.string.onboarding_done_header)
|
||||
private fun showSuccess() = with(binding) {
|
||||
tvHeader.text = getText(R.string.onboarding_done_header)
|
||||
|
||||
tv_header.show()
|
||||
tv_body.show()
|
||||
view_pager_backup_info.hide()
|
||||
tab_layout_backup_info.hide()
|
||||
tvHeader.show()
|
||||
tvBody.show()
|
||||
viewPagerBackupInfo.hide()
|
||||
tabLayoutBackupInfo.hide()
|
||||
|
||||
tv_body.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
|
||||
btn_main_action.text = getText(R.string.onboarding_button_continue_wallet)
|
||||
btn_alternative_action.hide()
|
||||
btn_main_action.setOnClickListener {
|
||||
lav_confetti.cancelAnimation()
|
||||
lav_confetti.hide()
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
|
||||
layoutButtonsCommon.btnMainAction.text = getText(R.string.onboarding_button_continue_wallet)
|
||||
layoutButtonsCommon.btnAlternativeAction.hide()
|
||||
layoutButtonsCommon.btnMainAction.setOnClickListener {
|
||||
vConfetti.lavConfetti.cancelAnimation()
|
||||
vConfetti.lavConfetti.hide()
|
||||
store.dispatch(OnboardingWalletAction.FinishOnboarding)
|
||||
}
|
||||
|
||||
cardsWidget.leapfrogWidget.fold {
|
||||
fl_cards_container?.hide()
|
||||
imv_card_background?.hide()
|
||||
lav_confetti?.show()
|
||||
lav_confetti?.playAnimation()
|
||||
imv_success?.alpha = 0f
|
||||
imv_success?.show()
|
||||
flCardsContainer.hide()
|
||||
imvCardBackground.hide()
|
||||
vConfetti.lavConfetti.show()
|
||||
vConfetti.lavConfetti.playAnimation()
|
||||
imvSuccess.alpha = 0f
|
||||
imvSuccess.show()
|
||||
|
||||
imv_success?.animate()
|
||||
imvSuccess.animate()
|
||||
?.alpha(1f)
|
||||
?.setDuration(400)
|
||||
}
|
||||
|
|
@ -379,7 +405,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
|
|||
|
||||
val shopMenuShouldBeVisible =
|
||||
(backupStep == BackupStep.ScanOriginCard || backupStep == BackupStep.AddBackupCards) &&
|
||||
backupState.buyAdditionalCardsUrl != null
|
||||
backupState.buyAdditionalCardsUrl != null
|
||||
menu.getItem(0).isVisible = shopMenuShouldBeVisible
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
|
|||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
|
||||
class BackupCardsWidget(
|
||||
class WalletCardsWidget(
|
||||
val leapfrogWidget: LeapfrogWidget,
|
||||
private val deviceScaleFactor: Float = 1f,
|
||||
val getTopOfAnchorViewForActivateState: () -> Float,
|
||||
) {
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ class BackupCardsWidget(
|
|||
createAnimator(BackupCardType.FIRST_BACKUP, createLeapfrogProperties(BackupCardType.FIRST_BACKUP)),
|
||||
createAnimator(BackupCardType.SECOND_BACKUP, createLeapfrogProperties(BackupCardType.SECOND_BACKUP))
|
||||
)
|
||||
leapfrogWidget.fold { animator.start() }
|
||||
leapfrogWidget.fold(animate) { animator.start() }
|
||||
}
|
||||
|
||||
fun toFan(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
|
|
@ -98,26 +99,26 @@ class BackupCardsWidget(
|
|||
private fun createWelcomeProperties(cardType: BackupCardType): CardProperties {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 440f,
|
||||
yTranslation = 0f,
|
||||
rotation = 75f,
|
||||
elevation = 2f,
|
||||
scale = .9f,
|
||||
)
|
||||
xTranslation = 440f * deviceScaleFactor,
|
||||
yTranslation = 70f,
|
||||
rotation = 75f,
|
||||
elevation = 2f,
|
||||
scale = .85f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 10f,
|
||||
yTranslation = -100f,
|
||||
rotation = 100f,
|
||||
elevation = 1f,
|
||||
scale = .9f,
|
||||
)
|
||||
xTranslation = 10f * deviceScaleFactor,
|
||||
yTranslation = -30f,
|
||||
rotation = 100f,
|
||||
elevation = 1f,
|
||||
scale = .85f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = -440f,
|
||||
yTranslation = -100f,
|
||||
rotation = 70f,
|
||||
elevation = 0f,
|
||||
scale = .9f,
|
||||
)
|
||||
xTranslation = -440f * deviceScaleFactor,
|
||||
yTranslation = -30f,
|
||||
rotation = 70f,
|
||||
elevation = 0f,
|
||||
scale = .85f * deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -125,24 +126,24 @@ class BackupCardsWidget(
|
|||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
yTranslation = 30f,
|
||||
rotation = 5f,
|
||||
elevation = 2f,
|
||||
scale = 1f,
|
||||
scale = 1f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = -80f,
|
||||
yTranslation = -50f,
|
||||
rotation = -5f,
|
||||
elevation = 1f,
|
||||
scale = 0.9f,
|
||||
scale = 0.9f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = 10f,
|
||||
yTranslation = -170f,
|
||||
yTranslation = -120f,
|
||||
rotation = -20f,
|
||||
elevation = 0f,
|
||||
scale = 0.8f,
|
||||
scale = 0.8f * deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -154,21 +155,21 @@ class BackupCardsWidget(
|
|||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 2f,
|
||||
scale = 1f,
|
||||
scale = 1f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 1f,
|
||||
scale = 0.9f,
|
||||
scale = 0.9f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 0f,
|
||||
scale = 0.8f,
|
||||
scale = 0.8f * deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Amount> = listOf(Amount(BigDecimal.ZERO, blockchain))
|
||||
|
||||
suspend fun feeStandard(blockchain: Blockchain): List<Amount> = 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<Amount> = 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<Amount> = listOf(Amount(0.0015.toBigDecimal(), blockchain))
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.tap.common.analytics.Analytics
|
|||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
|
|
@ -22,6 +23,8 @@ import com.tangem.tap.domain.TapError
|
|||
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.extensions.minimalAmount
|
||||
import com.tangem.tap.features.demo.DemoTransactionSender
|
||||
import com.tangem.tap.features.demo.isDemoWallet
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
|
||||
import com.tangem.tap.features.send.redux.states.ButtonState
|
||||
|
|
@ -32,6 +35,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
|
|||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdk
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -164,9 +168,12 @@ private fun sendTransaction(
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
val sendResult = try {
|
||||
(walletManager as TransactionSender).send(txData, signer)
|
||||
if (walletManager.isDemoWallet()) {
|
||||
DemoTransactionSender(walletManager).send(txData, signer)
|
||||
} else {
|
||||
(walletManager as TransactionSender).send(txData, signer)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
FirebaseCrashlytics.getInstance().recordException(ex)
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
|
|
@ -238,6 +245,13 @@ private fun sendTransaction(
|
|||
message.contains("Target account is not created. To create account send 1+ XLM.") -> {
|
||||
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
|
||||
}
|
||||
message.contains(DemoTransactionSender.ID) -> {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatchDialogShow(AppDialog.SimpleOkDialogRes(
|
||||
R.string.common_done,
|
||||
R.string.alert_demo_feature_disabled
|
||||
) { dispatch(NavigationAction.PopBackTo()) })
|
||||
}
|
||||
else -> {
|
||||
(sendResult.error as? TangemSdkError)?.let { error ->
|
||||
store.state.globalState.analyticsHandlers?.logCardSdkError(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue