Updated on 2026-08-14
This commit is contained in:
commit
d30dd96d55
745 changed files with 18249 additions and 9148 deletions
2
.bundle/config
Normal file
2
.bundle/config
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
BUNDLE_PATH: "vendor/bundle"
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
[*.{kt,kts}]
|
||||
ktlint_ignore_back_ticked_identifier = true
|
||||
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -20,6 +20,9 @@ local.properties
|
|||
# LayoutInspector files
|
||||
/captures
|
||||
|
||||
# bundler
|
||||
/vendor
|
||||
|
||||
# OS-specific files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
|
|
|
|||
192
Gemfile.lock
192
Gemfile.lock
|
|
@ -1,80 +1,102 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.4)
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.519.0)
|
||||
aws-sdk-core (3.121.3)
|
||||
aws-partitions (1.702.0)
|
||||
aws-sdk-core (3.170.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.62.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.50.0)
|
||||
aws-sdk-core (~> 3, >= 3.121.2)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.104.0)
|
||||
aws-sdk-core (~> 3, >= 3.121.2)
|
||||
aws-sdk-s3 (1.119.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.4.0)
|
||||
aws-sigv4 (1.5.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.0.3)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander-fastlane (4.4.6)
|
||||
highline (~> 1.7.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
declarative-option (0.1.0)
|
||||
digest-crc (0.6.4)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (1.0.1)
|
||||
excon (0.87.0)
|
||||
faraday (0.17.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.98.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday_middleware (0.13.1)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
fastimage (2.2.5)
|
||||
fastlane (2.146.1)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.211.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.2, < 2.0.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 2.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 0.17)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 0.13.1)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-api-client (>= 0.29.2, < 0.37.0)
|
||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||
highline (>= 1.7.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
json (< 3.0.0)
|
||||
jwt (~> 2.1.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multi_xml (~> 0.5)
|
||||
multipart-post (~> 2.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
public_suffix (~> 2.0.0)
|
||||
rubyzip (>= 1.3.0, < 2.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
|
|
@ -83,74 +105,87 @@ GEM
|
|||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
fastlane-plugin-firebase_app_distribution (0.3.1)
|
||||
fastlane-plugin-firebase_app_distribution (0.4.2)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.32.1)
|
||||
google-apis-androidpublisher_v3 (0.33.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-core (0.10.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.5, < 0.10.0)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.10)
|
||||
google-cloud-core (1.3.2)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.16.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.12.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-storage_v1 (0.19.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-env (1.2.1)
|
||||
faraday (~> 0.11)
|
||||
google-cloud-storage (1.21.1)
|
||||
addressable (~> 2.5)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.3.0)
|
||||
google-cloud-storage (1.44.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.26)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (>= 0.6.2, < 0.10.0)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.9.0)
|
||||
faraday (~> 0.12)
|
||||
googleauth (1.3.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.7)
|
||||
highline (1.7.10)
|
||||
http-cookie (1.0.4)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.4.0)
|
||||
json (2.6.1)
|
||||
jwt (2.1.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
jwt (2.6.0)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.2)
|
||||
multi_json (1.15.0)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
os (1.1.1)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
public_suffix (2.0.5)
|
||||
public_suffix (5.0.1)
|
||||
rake (13.0.6)
|
||||
representable (3.0.4)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.5)
|
||||
rouge (2.0.7)
|
||||
rubyzip (1.3.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.11.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
signet (0.17.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
slack-notifier (2.4.0)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
|
|
@ -158,10 +193,11 @@ GEM
|
|||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8)
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.8.1)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.21.0)
|
||||
xcodeproj (1.22.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
|
|
@ -174,11 +210,11 @@ GEM
|
|||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
universal-darwin-18
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
fastlane
|
||||
fastlane-plugin-firebase_app_distribution
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.16
|
||||
1.17.2
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ plugins {
|
|||
id("com.android.application")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
kotlin("plugin.serialization")
|
||||
id("com.google.gms.google-services")
|
||||
id("com.google.firebase.crashlytics")
|
||||
id("com.google.dagger.hilt.android")
|
||||
|
|
@ -102,6 +103,7 @@ android {
|
|||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven(url = "https://jitpack.io")
|
||||
}
|
||||
|
||||
|
|
@ -200,6 +202,7 @@ dependencies {
|
|||
implementation(Library.googlePlayServicesWallet)
|
||||
implementation(Library.composeShimmer)
|
||||
implementation(Library.mviCoreWatcher)
|
||||
implementation(Library.kotlinSerialization)
|
||||
|
||||
/** Testing libraries */
|
||||
testImplementation(Test.junit)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.tangem.wallet">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.nfc"
|
||||
android:required="true" />
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
|
@ -19,6 +15,9 @@
|
|||
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.nfc"
|
||||
android:required="true" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
|
|
@ -51,47 +50,49 @@
|
|||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data
|
||||
android:pathPrefix="/ndef"
|
||||
android:host="www.tangem.com"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:pathPrefix="/ndef"
|
||||
android:host="www.tangem.com"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:pathPrefix="/ndef"
|
||||
android:host="tangem.com"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:pathPrefix="/ndef"
|
||||
android:host="tangem.com"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:pathPrefix="/ndef"
|
||||
android:host="app.tangem.com"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data
|
||||
android:host="ext"
|
||||
android:pathPrefix="/android.com:pkg"
|
||||
android:scheme="vnd.android.nfc" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.TECH_DISCOVERED" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.nfc.action.TECH_DISCOVERED"
|
||||
android:resource="@xml/nfc_tech_filter" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.TAG_DISCOVERED" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.google.android.material.snackbar.Snackbar
|
|||
import com.tangem.TangemSdk
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tangem_sdk_new.extensions.initWithBiometrics
|
||||
import com.tangem.tap.common.ActivityResultCallbackHolder
|
||||
import com.tangem.tap.common.DialogManager
|
||||
import com.tangem.tap.common.OnActivityResultCallback
|
||||
|
|
@ -20,8 +21,8 @@ import com.tangem.tap.common.extensions.dispatchOnMain
|
|||
import com.tangem.tap.common.redux.NotificationsHandler
|
||||
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.GooglePayService
|
||||
import com.tangem.tap.common.shop.googlepay.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.domain.userWalletList.UserWalletsListManager
|
||||
|
|
@ -77,9 +78,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
systemActions()
|
||||
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
|
||||
|
||||
tangemSdk = TangemSdk.init(this, TangemSdkManager.config)
|
||||
tangemSdk = TangemSdk.initWithBiometrics(this, TangemSdkManager.config)
|
||||
tangemSdkManager = TangemSdkManager(tangemSdk, this)
|
||||
appStateHolder.tangemSdkManager = tangemSdkManager
|
||||
appStateHolder.tangemSdk = tangemSdk
|
||||
backupService = BackupService.init(tangemSdk, this)
|
||||
userWalletsListManager = UserWalletsListManager.provideBiometricImplementation(
|
||||
context = applicationContext,
|
||||
|
|
@ -115,29 +117,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
super.onResume()
|
||||
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
|
||||
|
||||
val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
|
||||
val isScannedBefore = store.state.globalState.scanResponse != null
|
||||
val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted
|
||||
val shopOpened = store.state.shopState.total != null
|
||||
if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore && !shopOpened)) {
|
||||
if (userWalletsListManager.hasSavedUserWallets) {
|
||||
store.dispatchOnMain(WelcomeAction.HandleDeepLink(intent))
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome))
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
intentHandler.handleWalletConnectLink(intent)
|
||||
}
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
}
|
||||
intentHandler.handleBackgroundScan(intent)
|
||||
intentHandler.handleSellCurrencyCallback(intent)
|
||||
navigateToInitialScreenIfNeeded(intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
intentHandler.handleBackgroundScan(intent)
|
||||
intentHandler.handleSellCurrencyCallback(intent)
|
||||
intentHandler.handleWalletConnectLink(intent)
|
||||
intentHandler.handleIntent(intent, userWalletsListManager.hasSavedUserWallets)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
@ -160,7 +145,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
if (snackbar != null) return
|
||||
|
||||
snackbar = Snackbar.make(
|
||||
binding.fragmentContainer, getString(text), Snackbar.LENGTH_INDEFINITE,
|
||||
binding.fragmentContainer,
|
||||
getString(text),
|
||||
Snackbar.LENGTH_INDEFINITE,
|
||||
)
|
||||
if (buttonTitle != null && action != null) {
|
||||
snackbar?.setAction(getString(buttonTitle), action)
|
||||
|
|
@ -200,4 +187,30 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
|
||||
lockUserWalletsTimer?.restart()
|
||||
}
|
||||
|
||||
private fun navigateToInitialScreenIfNeeded(intent: Intent?) {
|
||||
val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
|
||||
val isNotScannedBefore = store.state.globalState.scanResponse == null
|
||||
val isOnboardingServiceNotActive = store.state.globalState.onboardingState.onboardingStarted
|
||||
val isShopNotOpened = store.state.shopState.total != null
|
||||
when {
|
||||
!backStackIsEmpty && isNotScannedBefore && isOnboardingServiceNotActive && isShopNotOpened -> {
|
||||
navigateToInitialScreen(intent)
|
||||
}
|
||||
backStackIsEmpty -> {
|
||||
navigateToInitialScreen(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToInitialScreen(intent: Intent?) {
|
||||
if (userWalletsListManager.hasSavedUserWallets) {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome))
|
||||
store.dispatchOnMain(WelcomeAction.HandleIntentIfNeeded(intent))
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
intentHandler.handleIntent(intent, hasSavedUserWallets = false)
|
||||
}
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
}
|
||||
}
|
||||
|
|
@ -36,4 +36,4 @@ class NavBarInsetsFragmentLifecycleCallback : FragmentLifecycleCallbacks() {
|
|||
windowInsets
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,13 +10,15 @@ import com.tangem.LogFormat
|
|||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.FeaturesLocalLoader
|
||||
import com.tangem.datasource.utils.AndroidAssetReader
|
||||
import com.tangem.datasource.utils.AssetReader
|
||||
import com.tangem.domain.DomainLayer
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.tap.common.AndroidAssetReader
|
||||
import com.tangem.tap.common.AssetReader
|
||||
import com.tangem.tap.common.IntentHandler
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
|
|
@ -29,14 +31,10 @@ import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
|
|||
import com.tangem.tap.common.feedback.FeedbackManager
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
import com.tangem.tap.common.log.TangemLogCollector
|
||||
import com.tangem.tap.common.moshi.BigDecimalAdapter
|
||||
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.Config
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator
|
||||
|
|
@ -51,7 +49,6 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
|||
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.persistence.CardBalanceStateAdapter
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
|
@ -117,6 +114,9 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
@Inject
|
||||
lateinit var appStateHolder: AppStateHolder
|
||||
|
||||
@Inject
|
||||
lateinit var configManager: ConfigManager
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
|
@ -136,14 +136,13 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
activityResultCaller = foregroundActivityObserver
|
||||
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
|
||||
|
||||
initMoshiConverter()
|
||||
DomainLayer.init()
|
||||
NetworkConnectivity.createInstance(store, this)
|
||||
preferencesStorage = PreferencesStorage(this)
|
||||
walletConnectRepository = WalletConnectRepository(this)
|
||||
|
||||
assetReader = AndroidAssetReader(this)
|
||||
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.INSTANCE.moshi)
|
||||
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
|
||||
initConfigManager(configLoader, ::initWithConfigDependency)
|
||||
initWarningMessagesManager()
|
||||
|
||||
|
|
@ -158,18 +157,6 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
appStateHolder.walletStoresManager = walletStoresManager
|
||||
}
|
||||
|
||||
//todo refactor: move to datasource and provide via DI
|
||||
private fun initMoshiConverter() {
|
||||
fun appAdapters(): List<Any> = listOf(
|
||||
BigDecimalAdapter(),
|
||||
CardBalanceStateAdapter(),
|
||||
)
|
||||
MoshiConverter.reInitInstance(
|
||||
adapters = appAdapters() + MoshiJsonConverter.getTangemSdkAdapters(),
|
||||
typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(),
|
||||
)
|
||||
}
|
||||
|
||||
override fun newImageLoader(): ImageLoader {
|
||||
return createCoilImageLoader(
|
||||
context = this,
|
||||
|
|
@ -178,7 +165,6 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
}
|
||||
|
||||
private fun initConfigManager(loader: FeaturesLocalLoader, onComplete: (Config) -> Unit) {
|
||||
val configManager = ConfigManager()
|
||||
configManager.load(loader) { config ->
|
||||
store.dispatch(GlobalAction.SetConfigManager(configManager))
|
||||
onComplete(config)
|
||||
|
|
@ -188,7 +174,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
private fun initWithConfigDependency(config: Config) {
|
||||
shopService = TangemShopService(this, config.shopify!!)
|
||||
initAnalytics(this, config)
|
||||
initFeedbackManager(this, preferencesStorage, config)
|
||||
initFeedbackManager(this, preferencesStorage)
|
||||
}
|
||||
|
||||
private fun initAnalytics(application: Application, config: Config) {
|
||||
|
|
@ -205,12 +191,12 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
config = config,
|
||||
isDebug = BuildConfig.DEBUG,
|
||||
logConfig = LogConfig.analyticsHandlers,
|
||||
jsonConverter = MoshiConverter.INSTANCE,
|
||||
jsonConverter = MoshiConverter.sdkMoshiConverter,
|
||||
)
|
||||
factory.build(Analytics, buildData)
|
||||
}
|
||||
|
||||
private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) {
|
||||
private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage) {
|
||||
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply {
|
||||
appVersion = try {
|
||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ package com.tangem.tap
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
val DELAY_SDK_DIALOG_CLOSE = 1400L
|
||||
const val DELAY_SDK_DIALOG_CLOSE = 1400L
|
||||
|
|
@ -9,8 +9,8 @@ import java.math.RoundingMode
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CurrencyConverter(
|
||||
private val rateValue: BigDecimal,
|
||||
private val decimals: Int
|
||||
private val rateValue: BigDecimal,
|
||||
private val decimals: Int
|
||||
) {
|
||||
private val roundingMode = RoundingMode.HALF_UP
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,9 @@ import com.tangem.tap.features.details.ui.walletconnect.dialogs.ClipboardOrScanQ
|
|||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionDialog
|
||||
import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.dialog.CreateWalletInterruptDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.dialog.TwinningProcessNotCompletedDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.InterruptOnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.NoFundsForActivationDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.PutVisaCardDialog
|
||||
|
|
@ -57,6 +56,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
override fun newState(state: GlobalState) {
|
||||
if (state.dialog == null) {
|
||||
dialog?.dismiss()
|
||||
|
|
@ -74,7 +74,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
|
||||
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
||||
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
|
||||
is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context)
|
||||
is OnboardingDialog.TwinningProcessNotCompleted -> TwinningProcessNotCompletedDialog.create(context)
|
||||
is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog)
|
||||
is WalletConnectDialog.UnsupportedCard ->
|
||||
SimpleAlertDialog.create(
|
||||
|
|
@ -135,13 +135,15 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
is SaltPayDialog.Activation.PutVisaCard -> PutVisaCardDialog.create(context)
|
||||
is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog)
|
||||
is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context)
|
||||
is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context)
|
||||
is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context, state.dialog)
|
||||
is WalletDialog.SelectAmountToSendDialog -> AmountToSendBottomSheetDialog(context, state.dialog)
|
||||
is WalletDialog.SignedHashesMultiWalletDialog -> SignedHashesWarningDialog.create(context)
|
||||
is WalletDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create(
|
||||
title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol),
|
||||
message = context.getString(
|
||||
state.dialog.messageRes, state.dialog.currencySymbol, state.dialog.currencyTitle,
|
||||
state.dialog.messageRes,
|
||||
state.dialog.currencySymbol,
|
||||
state.dialog.currencyTitle,
|
||||
),
|
||||
context = context,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import timber.log.Timber
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GlobalLayoutStateHandler<T: View>(
|
||||
private val view: T,
|
||||
attachImmediately: Boolean = true
|
||||
class GlobalLayoutStateHandler<T : View>(
|
||||
private val view: T,
|
||||
attachImmediately: Boolean = true
|
||||
) : ViewTreeObserver.OnGlobalLayoutListener {
|
||||
|
||||
var onStateChanged: ((T) -> Unit)? = null
|
||||
|
|
|
|||
|
|
@ -3,18 +3,33 @@ package com.tangem.tap.common
|
|||
import android.content.Intent
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import com.tangem.tap.common.extensions.removePrefixOrNull
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class IntentHandler {
|
||||
|
||||
private val nfcActions = arrayOf(
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED,
|
||||
NfcAdapter.ACTION_TECH_DISCOVERED,
|
||||
NfcAdapter.ACTION_TAG_DISCOVERED,
|
||||
)
|
||||
|
||||
fun handleIntent(intent: Intent?, hasSavedUserWallets: Boolean) {
|
||||
handleBackgroundScan(intent, hasSavedUserWallets)
|
||||
handleWalletConnectLink(intent)
|
||||
handleSellCurrencyCallback(intent)
|
||||
}
|
||||
|
||||
fun handleWalletConnectLink(intent: Intent?) {
|
||||
val wcUri = when (intent?.scheme) {
|
||||
WalletConnectManager.WC_SCHEME -> {
|
||||
|
|
@ -32,19 +47,29 @@ class IntentHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fun handleBackgroundScan(intent: Intent?) {
|
||||
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action ||
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action
|
||||
)
|
||||
) {
|
||||
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
|
||||
if (tag != null) {
|
||||
intent.action = null
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatch(HomeAction.ReadCard())
|
||||
}
|
||||
fun handleBackgroundScan(intent: Intent?, hasSavedUserWallets: Boolean): Boolean {
|
||||
if (intent == null || intent.action !in nfcActions) return false
|
||||
|
||||
val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
||||
}
|
||||
if (tag == null) return false
|
||||
|
||||
intent.action = null
|
||||
if (hasSavedUserWallets) {
|
||||
// TODO: Remove delay after [REDACTED_JIRA]
|
||||
scope.launch {
|
||||
delay(timeMillis = 200)
|
||||
store.dispatch(WelcomeAction.ProceedWithCard)
|
||||
}
|
||||
} else {
|
||||
store.dispatch(HomeAction.ReadCard())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun handleSellCurrencyCallback(intent: Intent?) {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class KeyboardObserver(activity: Activity) {
|
|||
onKeyboardListener?.invoke(isShow)
|
||||
}
|
||||
|
||||
private fun isSoftKeyChanged() = ((lastWindowHeight - getWindowHeight()).absoluteValue) == getSoftKeyButtonHeight()
|
||||
private fun isSoftKeyChanged() = (lastWindowHeight - getWindowHeight()).absoluteValue == getSoftKeyButtonHeight()
|
||||
|
||||
private fun getSoftKeyButtonHeight(): Int {
|
||||
val applicationDisplayHeight = DisplayMetrics().apply {
|
||||
|
|
@ -57,5 +57,4 @@ class KeyboardObserver(activity: Activity) {
|
|||
|
||||
return realDisplayHeight - applicationDisplayHeight
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -40,9 +40,10 @@ typealias TestAction = Pair<String, () -> Unit>
|
|||
|
||||
class TestActionsBottomSheetDialog(
|
||||
private val appDialog: AppDialog.TestActionsDialog,
|
||||
context: Context
|
||||
context: Context,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.analytics.api.ErrorEventLogger
|
||||
import com.tangem.analytics.api.EventLogger
|
||||
import com.tangem.core.analytics.api.ErrorEventLogger
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
import timber.log.Timber
|
||||
|
||||
class AnalyticsEventsLogger(
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ package com.tangem.tap.common.analytics
|
|||
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
|
||||
object TangemSdk {
|
||||
object TangemSdkErrorMapper {
|
||||
|
||||
// This mapping is performed to group errors in FirebaseCrashlytics.
|
||||
// At the moment, the errors in Crashlytics can only be grouped by their place of creation (class and line).
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun map(error: TangemSdkError): TangemSdkError {
|
||||
return when (error) {
|
||||
is TangemSdkError.TagLost -> TangemSdkError.TagLost()
|
||||
|
|
@ -13,12 +14,15 @@ object TangemSdk {
|
|||
is TangemSdkError.SerializeCommandError -> TangemSdkError.SerializeCommandError()
|
||||
is TangemSdkError.DeserializeApduFailed -> TangemSdkError.DeserializeApduFailed()
|
||||
is TangemSdkError.EncodingFailedTypeMismatch -> TangemSdkError.EncodingFailedTypeMismatch(
|
||||
error.customMessage)
|
||||
error.customMessage
|
||||
)
|
||||
is TangemSdkError.EncodingFailed -> TangemSdkError.EncodingFailed(error.customMessage)
|
||||
is TangemSdkError.DecodingFailedMissingTag -> TangemSdkError.DecodingFailedMissingTag(
|
||||
error.customMessage)
|
||||
error.customMessage
|
||||
)
|
||||
is TangemSdkError.DecodingFailedTypeMismatch -> TangemSdkError.DecodingFailedTypeMismatch(
|
||||
error.customMessage)
|
||||
error.customMessage
|
||||
)
|
||||
is TangemSdkError.DecodingFailed -> TangemSdkError.DecodingFailed(error.customMessage)
|
||||
is TangemSdkError.InvalidResponse -> TangemSdkError.InvalidResponse()
|
||||
is TangemSdkError.UnknownStatus -> TangemSdkError.UnknownStatus(error.statusWord)
|
||||
|
|
@ -90,7 +94,8 @@ object TangemSdk {
|
|||
is TangemSdkError.BackupFailedEmptyWallets -> TangemSdkError.BackupFailedEmptyWallets()
|
||||
is TangemSdkError.BackupFailedNotEmptyWallets -> TangemSdkError.BackupFailedNotEmptyWallets()
|
||||
is TangemSdkError.NoActiveBackup -> TangemSdkError.NoActiveBackup()
|
||||
is TangemSdkError.ResetBackupFailedHasBackupedWallets -> TangemSdkError.ResetBackupFailedHasBackupedWallets()
|
||||
is TangemSdkError.ResetBackupFailedHasBackupedWallets ->
|
||||
TangemSdkError.ResetBackupFailedHasBackupedWallets()
|
||||
is TangemSdkError.BackupServiceInvalidState -> TangemSdkError.BackupServiceInvalidState()
|
||||
is TangemSdkError.NoBackupCardForIndex -> TangemSdkError.NoBackupCardForIndex()
|
||||
is TangemSdkError.EmptyBackupCards -> TangemSdkError.EmptyBackupCards()
|
||||
|
|
@ -111,5 +116,4 @@ object TangemSdk {
|
|||
is TangemSdkError.BiometricsAuthenticationFailed -> error
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import android.app.Application
|
|||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.core.analytics.api.AnalyticsHandler
|
||||
import com.tangem.domain.common.AnalyticsHandlersLogConfig
|
||||
import com.tangem.tap.domain.configurable.config.Config
|
||||
import com.tangem.datasource.config.models.Config
|
||||
|
||||
interface AnalyticsHandlerBuilder {
|
||||
fun build(data: Data): AnalyticsHandler?
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.domain.common.ScanResponse
|
|||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.common.analytics.filters.BasicTopUpFilter
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
|
|
@ -28,13 +27,14 @@ class BasicEventsPreChecker {
|
|||
BasicTopUpEventConverter().convert(converterData)?.let { Analytics.send(it) }
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun isReadyToSend(data: BasicEventsSourceData): Boolean {
|
||||
val (scanResponse, walletState, biometricsWalletDataModels) = data
|
||||
if (walletState.derivationsCheckIsScheduled) {
|
||||
Timber.d("FAILED: derivationsCheckIsScheduled")
|
||||
return false
|
||||
}
|
||||
if (scanResponse.card.isMultiwalletAllowed && walletState.missingDerivations.isNotEmpty()) {
|
||||
if (scanResponse.cardTypesResolver.isMultiwalletAllowed() && walletState.missingDerivations.isNotEmpty()) {
|
||||
Timber.d("FAILED: isMultiwalletAllowed || missingDerivations.isNotEmpty")
|
||||
return false
|
||||
}
|
||||
|
|
@ -70,13 +70,14 @@ class BasicEventsPreChecker {
|
|||
return false
|
||||
}
|
||||
|
||||
if (biometricsWalletDataModels.any {
|
||||
it.status is WalletDataModel.Loading ||
|
||||
it.status is WalletDataModel.NoAccount ||
|
||||
it.status is WalletDataModel.Unreachable ||
|
||||
it.status is WalletDataModel.MissedDerivation ||
|
||||
it.status.isErrorStatus
|
||||
}) {
|
||||
val isCorrectStatus = biometricsWalletDataModels.any {
|
||||
it.status is WalletDataModel.Loading ||
|
||||
it.status is WalletDataModel.NoAccount ||
|
||||
it.status is WalletDataModel.Unreachable ||
|
||||
it.status is WalletDataModel.MissedDerivation ||
|
||||
it.status.isErrorStatus
|
||||
}
|
||||
if (isCorrectStatus) {
|
||||
Timber.d("FAILED: by status")
|
||||
return false
|
||||
}
|
||||
|
|
@ -106,7 +107,9 @@ data class BasicEventsSourceData(
|
|||
?.stringValue
|
||||
}
|
||||
|
||||
val paramCardCurrency: AnalyticsParam.CardCurrency? by lazy { ParamCardCurrencyConverter().convert(scanResponse) }
|
||||
val paramCardCurrency: AnalyticsParam.CardCurrency? by lazy {
|
||||
ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)
|
||||
}
|
||||
|
||||
val paramCardBalanceState: AnalyticsParam.CardBalanceState by lazy { calculateAmount().toCardBalanceState() }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,24 +2,20 @@ package com.tangem.tap.common.analytics.converters
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ParamCardCurrencyConverter : Converter<ScanResponse, AnalyticsParam.CardCurrency?> {
|
||||
class ParamCardCurrencyConverter : Converter<CardTypesResolver, AnalyticsParam.CardCurrency?> {
|
||||
|
||||
override fun convert(value: ScanResponse): AnalyticsParam.CardCurrency? {
|
||||
if (value.card.isMultiwalletAllowed) return AnalyticsParam.CardCurrency.MultiCurrency
|
||||
override fun convert(value: CardTypesResolver): AnalyticsParam.CardCurrency? {
|
||||
if (value.isMultiwalletAllowed()) return AnalyticsParam.CardCurrency.MultiCurrency
|
||||
|
||||
val type = when {
|
||||
value.isTangemNote() -> {
|
||||
value.card.getTangemNoteBlockchain()?.let { AnalyticsParam.CurrencyType.Blockchain(it) }
|
||||
}
|
||||
value.isTangemNote() -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
|
||||
value.isTangemTwins() -> AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin)
|
||||
value.isSaltPay() -> AnalyticsParam.CurrencyType.Token(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay))
|
||||
value.getBlockchain() != Blockchain.Unknown -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ class ShopOrderToEventConverter : Converter<Pair<Storefront.Checkout, ProductTyp
|
|||
ProductType.WALLET_3_CARDS -> "3"
|
||||
}
|
||||
val amount = "${checkout.totalPriceV2.amount} ${checkout.totalPriceV2.currencyCode.name}"
|
||||
val code = (checkout.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
|
||||
val code = (checkout.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)
|
||||
?.code
|
||||
|
||||
return Shop.Purchased(sku, count, amount, code)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ sealed class AnalyticsParam {
|
|||
class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol)
|
||||
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
|
||||
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
|
||||
class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.symbol)
|
||||
class FiatCurrency(
|
||||
fiatCurrency: com.tangem.tap.common.entities.FiatCurrency,
|
||||
) : CurrencyType(fiatCurrency.symbol)
|
||||
|
||||
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,4 +33,4 @@ sealed class Basic(
|
|||
event = "Scan",
|
||||
error = error,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ sealed class Onboarding(
|
|||
class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened")
|
||||
class ButtonClaim : Onboarding("Onboarding", "Button - Claim")
|
||||
class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim was successfully")
|
||||
class ButtonChat: Onboarding("Onboarding", "Button - Chat")
|
||||
class ButtonChat : Onboarding("Onboarding", "Button - Chat")
|
||||
|
||||
class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding(
|
||||
category = "Onboarding / Biometric",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import android.app.Application
|
|||
import com.amplitude.api.Amplitude
|
||||
import com.amplitude.api.AmplitudeClient
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.analytics.api.EventLogger
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer
|
|||
|
||||
import android.content.Context
|
||||
import com.appsflyer.AppsFlyerLib
|
||||
import com.tangem.analytics.api.EventLogger
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import com.google.firebase.analytics.FirebaseAnalytics
|
|||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.analytics.api.ErrorEventLogger
|
||||
import com.tangem.analytics.api.EventLogger
|
||||
import com.tangem.core.analytics.api.ErrorEventLogger
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ import androidx.compose.ui.text.TextStyle
|
|||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
fun TextAutoSize(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
textStyle: TextStyle = LocalTextStyle.current,
|
||||
fontSizeRange: FontSizeRange,
|
||||
modifier: Modifier = Modifier,
|
||||
textStyle: TextStyle = LocalTextStyle.current,
|
||||
) {
|
||||
val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) }
|
||||
val readyToDraw = remember { mutableStateOf(false) }
|
||||
|
|
@ -46,7 +47,7 @@ fun TextAutoSize(
|
|||
} else {
|
||||
readyToDraw.value = true
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ fun BlockchainSpinner(
|
|||
textFieldConverter: (Blockchain) -> String,
|
||||
dropdownItemView: @Composable ((Blockchain) -> Unit)? = null,
|
||||
closePopupTrigger: ClosePopupTrigger = ClosePopupTrigger(),
|
||||
onItemSelected: (Blockchain) -> Unit,
|
||||
onItemSelect: (Blockchain) -> Unit,
|
||||
) {
|
||||
OutlinedSpinner(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
@ -27,7 +27,7 @@ fun BlockchainSpinner(
|
|||
textFieldConverter = textFieldConverter,
|
||||
dropdownItemView = dropdownItemView,
|
||||
isEnabled = isEnabled,
|
||||
onItemSelected = onItemSelected,
|
||||
onItemSelected = onItemSelect,
|
||||
closePopupTrigger = closePopupTrigger,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,137 +1,8 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.ripple.LocalRippleTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.tap.common.compose.extensions.stringResourceDefault
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun RectangleButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String = "",
|
||||
textId: Int? = null,
|
||||
isEnabled: Boolean = true,
|
||||
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
|
||||
leadingView: @Composable RowScope.() -> Unit = {},
|
||||
middleView: @Composable RowScope.() -> Unit = { TextInButton(text = text, textId = textId) },
|
||||
trailingView: @Composable RowScope.() -> Unit = {},
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
enabled = isEnabled,
|
||||
onClick = onClick,
|
||||
) {
|
||||
leadingView()
|
||||
middleView()
|
||||
trailingView()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextInButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String = "",
|
||||
textId: Int? = null,
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = stringResourceDefault(textId, text),
|
||||
maxLines = 1,
|
||||
style = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 20.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PasteButton(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
dpSize: DpSize = DpSize(40.dp, 40.dp),
|
||||
onClick: () -> Unit,
|
||||
tint: Color? = null,
|
||||
content: @Composable (() -> Unit)? = null
|
||||
) {
|
||||
IconButton(
|
||||
modifier = modifier.size(dpSize),
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
) {
|
||||
when (content) {
|
||||
null -> {
|
||||
val tintColor = tint
|
||||
?: colorResource(id = if (enabled) R.color.accent else R.color.accent_disabled)
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_paste),
|
||||
contentDescription = "Paste",
|
||||
tint = tintColor,
|
||||
)
|
||||
}
|
||||
else -> content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ClearButton(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
dpSize: DpSize = DpSize(40.dp, 40.dp),
|
||||
onClick: () -> Unit,
|
||||
tint: Color? = null,
|
||||
content: @Composable (() -> Unit)? = null
|
||||
) {
|
||||
IconButton(
|
||||
modifier = modifier.size(dpSize),
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
) {
|
||||
when (content) {
|
||||
null -> {
|
||||
val tintColor = tint
|
||||
?: colorResource(id = if (enabled) R.color.accent else R.color.accent_disabled)
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_clear),
|
||||
contentDescription = "Clear",
|
||||
tint = tintColor,
|
||||
)
|
||||
}
|
||||
else -> content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for disable ripple if button is enable = false
|
||||
|
|
@ -143,34 +14,4 @@ fun ToggledRippleTheme(
|
|||
) {
|
||||
val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme()
|
||||
CompositionLocalProvider(theme) { content() }
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ButtonTest() {
|
||||
Scaffold {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
PreviewItem("Button") {
|
||||
RectangleButton(text = "Some button") {}
|
||||
}
|
||||
PreviewItem("PasteButton") {
|
||||
PasteButton(onClick = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PreviewItem(
|
||||
name: String,
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = name,
|
||||
)
|
||||
content()
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -11,19 +11,19 @@ import com.tangem.domain.common.util.ValueDebouncer
|
|||
@Composable
|
||||
fun <T> valueDebouncerAsState(
|
||||
initialValue: T,
|
||||
onValueChange: (T) -> Unit,
|
||||
debounce: Long = 600,
|
||||
onEmitValueReceived: (T) -> Unit = {},
|
||||
onValueChanged: (T) -> Unit,
|
||||
onEmitValueReceive: (T) -> Unit = {},
|
||||
): ValueDebouncer<T> {
|
||||
return remember {
|
||||
ValueDebouncer<T>(
|
||||
ValueDebouncer(
|
||||
initialValue = initialValue,
|
||||
debounceDuration = debounce,
|
||||
onEmitValueReceived = { emitValue ->
|
||||
emitValue?.let { onEmitValueReceived(it) }
|
||||
emitValue?.let { onEmitValueReceive(it) }
|
||||
},
|
||||
onValueChanged = { changedValue ->
|
||||
changedValue?.let { onValueChanged(it) }
|
||||
changedValue?.let { onValueChange(it) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -32,16 +32,16 @@ fun <T> valueDebouncerAsState(
|
|||
@Composable
|
||||
fun <T> valueDebouncerNullableAsState(
|
||||
initialValue: T?,
|
||||
onValueChange: (T?) -> Unit,
|
||||
debounce: Long = 400,
|
||||
onEmitValueReceived: (T?) -> Unit = {},
|
||||
onValueChanged: (T?) -> Unit,
|
||||
onEmitValueReceive: (T?) -> Unit = {},
|
||||
): ValueDebouncer<T?> {
|
||||
return remember {
|
||||
ValueDebouncer(
|
||||
initialValue = initialValue,
|
||||
debounceDuration = debounce,
|
||||
onEmitValueReceived = onEmitValueReceived,
|
||||
onValueChanged = onValueChanged,
|
||||
onEmitValueReceived = onEmitValueReceive,
|
||||
onValueChanged = onValueChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -80,6 +80,7 @@ private fun ShowTheDialog(dialogState: MutableState<DomainDialog?>) {
|
|||
onDismissRequest
|
||||
)
|
||||
is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -19,23 +18,20 @@ import androidx.compose.ui.unit.dp
|
|||
fun ErrorView(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = LocalTextStyle.current
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
) {
|
||||
Text(
|
||||
text,
|
||||
color = MaterialTheme.colors.error,
|
||||
modifier = modifier,
|
||||
style = style
|
||||
style = style,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ErrorViewTest() {
|
||||
Scaffold(
|
||||
) {
|
||||
Box(Modifier.padding(16.dp)) {
|
||||
ErrorView(text = "Some error description")
|
||||
}
|
||||
private fun ErrorViewTest() {
|
||||
Box(Modifier.padding(16.dp)) {
|
||||
ErrorView(text = "Some error description")
|
||||
}
|
||||
}
|
||||
|
|
@ -17,14 +17,15 @@ import com.tangem.tap.common.extensions.ValueCallback
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun <T> OutlinedSpinner(
|
||||
modifier: Modifier = Modifier,
|
||||
label: String,
|
||||
itemList: List<T>,
|
||||
selectedItem: Field.Data<T>,
|
||||
onItemSelected: ValueCallback<T>,
|
||||
modifier: Modifier = Modifier,
|
||||
textFieldConverter: (T) -> String = { it.toString() },
|
||||
dropdownItemView: @Composable ((T) -> Unit)? = null,
|
||||
isEnabled: Boolean = true,
|
||||
|
|
@ -66,6 +67,7 @@ fun <T> OutlinedSpinner(
|
|||
onValueChange = {},
|
||||
label = { Text(label) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = rIsExpanded.value) },
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
)
|
||||
|
||||
if (isEnabled) {
|
||||
|
|
@ -94,13 +96,11 @@ class ClosePopupTrigger {
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
fun TestSpinnerPreview() {
|
||||
Scaffold() {
|
||||
OutlinedSpinner(
|
||||
label = "Blockchain name",
|
||||
itemList = listOf(Blockchain.values()),
|
||||
selectedItem = Field.Data(Blockchain.Avalanche, false),
|
||||
onItemSelected = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
private fun TestSpinnerPreview() {
|
||||
OutlinedSpinner(
|
||||
label = "Blockchain name",
|
||||
itemList = listOf(Blockchain.values()),
|
||||
selectedItem = Field.Data(Blockchain.Avalanche, false),
|
||||
onItemSelected = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import androidx.compose.animation.fadeIn
|
|||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -13,8 +14,8 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextFieldColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -28,6 +29,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.common.module.ModuleError
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.tap.common.CompositionLogger
|
||||
import com.tangem.tap.common.compose.extensions.stringResourceDefault
|
||||
|
|
@ -38,7 +40,6 @@ import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
|
|||
*/
|
||||
@Composable
|
||||
fun OutlinedTextFieldWidget(
|
||||
modifier: Modifier = Modifier,
|
||||
fieldData: Field.Data<String>,
|
||||
labelId: Int? = null,
|
||||
label: String = "",
|
||||
|
|
@ -53,15 +54,12 @@ fun OutlinedTextFieldWidget(
|
|||
debounceTextChanges: Long = 400,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
onTextChanged: (String) -> Unit,
|
||||
onTextChange: (String) -> Unit,
|
||||
) {
|
||||
if (!isVisible) return
|
||||
|
||||
Column(
|
||||
modifier = modifier.animateContentSize(),
|
||||
) {
|
||||
Column(modifier = Modifier.animateContentSize()) {
|
||||
OutlinedProgressTextField(
|
||||
modifier = modifier,
|
||||
fieldData = fieldData,
|
||||
label = stringResourceDefault(labelId, label),
|
||||
placeholder = stringResourceDefault(placeholderId, placeholder),
|
||||
|
|
@ -72,15 +70,15 @@ fun OutlinedTextFieldWidget(
|
|||
debounce = debounceTextChanges,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
onTextChanged = onTextChanged,
|
||||
onTextChange = onTextChange,
|
||||
)
|
||||
errorConverter?.let { AnimatedErrorView(error, it) }
|
||||
errorConverter?.let { AnimatedErrorView(errorConverter = it, error = error) }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "NestedBlockDepth", "MagicNumber", "MaxLineLength")
|
||||
@Composable
|
||||
private fun OutlinedProgressTextField(
|
||||
modifier: Modifier = Modifier,
|
||||
fieldData: Field.Data<String>,
|
||||
label: String = "",
|
||||
placeholder: String = "",
|
||||
|
|
@ -90,8 +88,10 @@ private fun OutlinedProgressTextField(
|
|||
debounce: Long = 400,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
colors: TextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
onTextChanged: (String) -> Unit,
|
||||
onTextChange: (String) -> Unit,
|
||||
) {
|
||||
val logger = remember {
|
||||
CompositionLogger(label, "OutlinedProgressTextField", listOf("Символ токена"))
|
||||
|
|
@ -102,19 +102,19 @@ private fun OutlinedProgressTextField(
|
|||
val textDebouncer = valueDebouncerAsState(
|
||||
initialValue = fieldData.value,
|
||||
debounce = debounce,
|
||||
onEmitValueReceived = {
|
||||
onEmitValueReceive = {
|
||||
logger.log("DEBOUNCER: onEmitValueReceived: [$it]")
|
||||
logger.log("DEBOUNCER: start RECOMPOSE by new value for textValueState.value = [$it]")
|
||||
textValueState.value = it
|
||||
},
|
||||
onValueChanged = {
|
||||
onValueChange = {
|
||||
logger.log("DEBOUNCER: onValueChanged: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> dispatch.toStore([$it])")
|
||||
onTextChanged(it)
|
||||
onTextChange(it)
|
||||
},
|
||||
)
|
||||
|
||||
logger.log("RECOMPOSE ---------------------------------------------------------------START [${logger.count}]")
|
||||
logger.log("RECOMPOSE --data: fieldData.value: [${fieldData}]")
|
||||
logger.log("RECOMPOSE --data: fieldData.value: [$fieldData]")
|
||||
logger.log("RECOMPOSE --data: textValueState.value: [${textValueState.value}]")
|
||||
logger.log("RECOMPOSE --data: textDebouncer.emittedValue = [${textDebouncer.emittedValue}]")
|
||||
logger.log("RECOMPOSE --data: textDebouncer.debounced = [${textDebouncer.debounced}]")
|
||||
|
|
@ -127,21 +127,32 @@ private fun OutlinedProgressTextField(
|
|||
logger.log("$isNotUserInput: внешние данные ОДИНАКОВЫ с данными в поле")
|
||||
} else {
|
||||
logger.log("$isNotUserInput: внешние данные РАЗЛИЧАЮТСЯ с данными в поле")
|
||||
if ((textDebouncer.emittedValue != textDebouncer.debounced) || textDebouncer.emitsCountBeforeDebounce > 0) {
|
||||
if (textDebouncer.emittedValue != textDebouncer.debounced || textDebouncer.emitsCountBeforeDebounce > 0) {
|
||||
logger.log("$isNotUserInput: пользователь ВВОДИТ данные -> внешние данные игнорируем, ждем RECOMPOSE")
|
||||
} else {
|
||||
logger.log("$isNotUserInput: пользователь НЕ вводит данные -> пытаемся обработать внешние данные")
|
||||
if (textValueState.value != textDebouncer.emittedValue || textValueState.value != textDebouncer.debounced) {
|
||||
if (textValueState.value != textDebouncer.emittedValue ||
|
||||
textValueState.value != textDebouncer.debounced
|
||||
) {
|
||||
logger.log("$isNotUserInput: даннные в поле не соответствуют данным из textDebouncer")
|
||||
if (textDebouncer.emittedValue.isEmpty() && textDebouncer.debounced.isEmpty()) {
|
||||
logger.log("$isNotUserInput: даннные в textDebouncer ПУСТЫ -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]")
|
||||
logger.log(
|
||||
"$isNotUserInput: даннные в textDebouncer ПУСТЫ -> start RECOMPOSE новые данные для " +
|
||||
"textValueState.value = [${fieldData.value}]",
|
||||
)
|
||||
textValueState.value = fieldData.value
|
||||
} else {
|
||||
logger.log("$isNotUserInput: даннные в textDebouncer НЕ ПУСТЫ -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]")
|
||||
logger.log(
|
||||
"$isNotUserInput: даннные в textDebouncer НЕ ПУСТЫ -> start RECOMPOSE новые данные для " +
|
||||
"textValueState.value = [${fieldData.value}]",
|
||||
)
|
||||
textValueState.value = fieldData.value
|
||||
}
|
||||
} else {
|
||||
logger.log("$isNotUserInput: в пустое поле вставляются данные -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]")
|
||||
logger.log(
|
||||
"$isNotUserInput: в пустое поле вставляются данные -> start RECOMPOSE новые данные для " +
|
||||
"textValueState.value = [${fieldData.value}]",
|
||||
)
|
||||
textValueState.value = fieldData.value
|
||||
}
|
||||
}
|
||||
|
|
@ -159,10 +170,22 @@ private fun OutlinedProgressTextField(
|
|||
textDebouncer.emmit(it)
|
||||
},
|
||||
keyboardOptions = keyboardOptions,
|
||||
label = { Text(label) },
|
||||
label = {
|
||||
Text(
|
||||
text = label,
|
||||
style = TangemTheme.typography.caption,
|
||||
color = colors.labelColor(
|
||||
enabled = isEnabled,
|
||||
error = error != null,
|
||||
interactionSource = interactionSource,
|
||||
).value,
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = placeholder,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = colors.placeholderColor(enabled = isEnabled).value,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
|
@ -172,21 +195,27 @@ private fun OutlinedProgressTextField(
|
|||
enabled = isEnabled,
|
||||
isError = error != null,
|
||||
visualTransformation = visualTransformation,
|
||||
colors = colors,
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
modifier = modifier
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(start = 6.dp, top = 0.dp, end = 6.dp, bottom = 6.dp),
|
||||
visible = isLoading,
|
||||
) { LinearProgressIndicator() }
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AnimatedErrorView(
|
||||
error: ModuleError? = null,
|
||||
errorConverter: ModuleMessageConverter,
|
||||
error: ModuleError? = null,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = error != null,
|
||||
|
|
@ -204,7 +233,7 @@ private fun AnimatedErrorView(
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
fun OutlinedTextFieldWithErrorTest() {
|
||||
private fun OutlinedTextFieldWithErrorTest() {
|
||||
val context = LocalContext.current
|
||||
val converter = remember { ModuleMessageConverter(context) }
|
||||
|
||||
|
|
@ -217,41 +246,35 @@ fun OutlinedTextFieldWithErrorTest() {
|
|||
val modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
Scaffold {
|
||||
Column {
|
||||
OutlinedTextFieldWidget(
|
||||
modifier = modifier,
|
||||
fieldData = Field.Data("", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
modifier = modifier,
|
||||
fieldData = Field.Data("First", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
modifier = modifier,
|
||||
fieldData = Field.Data("First", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
isLoading = true,
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
modifier = modifier,
|
||||
fieldData = Field.Data("First", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = SimpleError(),
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
}
|
||||
Column {
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = Field.Data("", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = Field.Data("First", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = Field.Data("First", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
isLoading = true,
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
fieldData = Field.Data("First", false),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = SimpleError(),
|
||||
errorConverter = converter,
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ import androidx.core.text.isDigitsOnly
|
|||
@Composable
|
||||
fun PinCodeWidget(
|
||||
config: PinViewConfig = tangemPinConfig,
|
||||
onPinChanged: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> },
|
||||
onPinChange: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> },
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
|
@ -65,7 +65,7 @@ fun PinCodeWidget(
|
|||
|
||||
if (value.text.length <= config.pinsCount) {
|
||||
rTextFieldValue.value = value
|
||||
onPinChanged(value.text, isLastSymbolEntered())
|
||||
onPinChange(value.text, isLastSymbolEntered())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -125,10 +125,6 @@ private fun PinElement(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HiddenInputField() {
|
||||
}
|
||||
|
||||
private fun createPinSymbolsList(size: Int, text: String): List<String?> = List(size) {
|
||||
try {
|
||||
text[it].toString()
|
||||
|
|
@ -165,7 +161,7 @@ private val tangemPinConfig = PinViewConfig(
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PinCodeWidgetPreview() {
|
||||
private fun PinCodeWidgetPreview() {
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
|
||||
PinCodeWidget(tangemPinConfig)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.interaction.InteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.material.TextFieldColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
// Copy from com.tangem.core.ui.components
|
||||
// TODO: Delete this after fields has been moved to core-ui module
|
||||
internal object TangemTextFieldsDefault {
|
||||
val defaultTextFieldColors: TangemTextFieldColors
|
||||
@Composable @Stable get() = TangemTextFieldColors(
|
||||
textColor = TangemTheme.colors.text.primary1,
|
||||
disabledTextColor = TangemTheme.colors.text.disabled,
|
||||
backgroundColor = Color.Transparent,
|
||||
cursorColor = TangemTheme.colors.icon.primary1,
|
||||
errorCursorColor = TangemTheme.colors.icon.warning,
|
||||
focusedIndicatorColor = TangemTheme.colors.icon.primary1,
|
||||
unfocusedIndicatorColor = TangemTheme.colors.stroke.primary,
|
||||
disabledIndicatorColor = TangemTheme.colors.stroke.primary,
|
||||
errorIndicatorColor = TangemTheme.colors.icon.warning,
|
||||
leadingIconColor = TangemTheme.colors.icon.informative,
|
||||
disabledLeadingIconColor = Color.Transparent,
|
||||
errorLeadingIconColor = TangemTheme.colors.icon.warning,
|
||||
trailingIconColor = TangemTheme.colors.icon.informative,
|
||||
disabledTrailingIconColor = Color.Transparent,
|
||||
errorTrailingIconColor = TangemTheme.colors.icon.warning,
|
||||
focusedLabelColor = TangemTheme.colors.text.primary1,
|
||||
unfocusedLabelColor = TangemTheme.colors.text.secondary,
|
||||
disabledLabelColor = TangemTheme.colors.text.disabled,
|
||||
errorLabelColor = TangemTheme.colors.icon.warning,
|
||||
placeholderColor = TangemTheme.colors.text.secondary,
|
||||
disabledPlaceholderColor = TangemTheme.colors.text.disabled,
|
||||
captionColor = TangemTheme.colors.text.tertiary,
|
||||
disabledCaptionColor = TangemTheme.colors.text.disabled,
|
||||
errorCaptionColor = TangemTheme.colors.icon.warning,
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal data class TangemTextFieldColors(
|
||||
private val textColor: Color,
|
||||
private val disabledTextColor: Color,
|
||||
private val cursorColor: Color,
|
||||
private val errorCursorColor: Color,
|
||||
private val focusedIndicatorColor: Color,
|
||||
private val unfocusedIndicatorColor: Color,
|
||||
private val errorIndicatorColor: Color,
|
||||
private val disabledIndicatorColor: Color,
|
||||
private val leadingIconColor: Color,
|
||||
private val disabledLeadingIconColor: Color,
|
||||
private val errorLeadingIconColor: Color,
|
||||
private val trailingIconColor: Color,
|
||||
private val disabledTrailingIconColor: Color,
|
||||
private val errorTrailingIconColor: Color,
|
||||
private val backgroundColor: Color,
|
||||
private val focusedLabelColor: Color,
|
||||
private val unfocusedLabelColor: Color,
|
||||
private val disabledLabelColor: Color,
|
||||
private val errorLabelColor: Color,
|
||||
private val placeholderColor: Color,
|
||||
private val disabledPlaceholderColor: Color,
|
||||
private val captionColor: Color,
|
||||
private val disabledCaptionColor: Color,
|
||||
private val errorCaptionColor: Color,
|
||||
) : TextFieldColors {
|
||||
|
||||
@Composable
|
||||
override fun leadingIconColor(enabled: Boolean, isError: Boolean): State<Color> {
|
||||
return rememberUpdatedState(
|
||||
when {
|
||||
!enabled -> disabledLeadingIconColor
|
||||
isError -> errorLeadingIconColor
|
||||
else -> leadingIconColor
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun trailingIconColor(enabled: Boolean, isError: Boolean): State<Color> {
|
||||
return rememberUpdatedState(
|
||||
when {
|
||||
!enabled -> disabledTrailingIconColor
|
||||
isError -> errorTrailingIconColor
|
||||
else -> trailingIconColor
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun indicatorColor(
|
||||
enabled: Boolean,
|
||||
isError: Boolean,
|
||||
interactionSource: InteractionSource,
|
||||
): State<Color> {
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
|
||||
val targetValue = when {
|
||||
!enabled -> disabledIndicatorColor
|
||||
isError -> errorIndicatorColor
|
||||
focused -> focusedIndicatorColor
|
||||
else -> unfocusedIndicatorColor
|
||||
}
|
||||
return if (enabled) {
|
||||
animateColorAsState(targetValue, tween(durationMillis = 120))
|
||||
} else {
|
||||
rememberUpdatedState(targetValue)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun backgroundColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(backgroundColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun placeholderColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun labelColor(
|
||||
enabled: Boolean,
|
||||
error: Boolean,
|
||||
interactionSource: InteractionSource,
|
||||
): State<Color> {
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
|
||||
val targetValue = when {
|
||||
!enabled -> disabledLabelColor
|
||||
error -> errorLabelColor
|
||||
focused -> focusedLabelColor
|
||||
else -> unfocusedLabelColor
|
||||
}
|
||||
return rememberUpdatedState(targetValue)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun textColor(enabled: Boolean): State<Color> {
|
||||
return rememberUpdatedState(if (enabled) textColor else disabledTextColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun cursorColor(isError: Boolean): State<Color> {
|
||||
return rememberUpdatedState(if (isError) errorCursorColor else cursorColor)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ fun TitleSubtitle(
|
|||
title: String,
|
||||
subtitle: String
|
||||
) {
|
||||
Column() {
|
||||
Column {
|
||||
Text(text = title)
|
||||
Text(
|
||||
text = subtitle,
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ import com.tangem.wallet.R
|
|||
*/
|
||||
@Composable
|
||||
fun AddCustomTokenWarning(
|
||||
modifier: Modifier = Modifier,
|
||||
warning: ModuleMessage,
|
||||
converter: ModuleMessageConverter
|
||||
converter: ModuleMessageConverter,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
|
|
@ -49,6 +49,5 @@ fun AddCustomTokenWarning(
|
|||
lineHeight = 18.sp
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.core.graphics.alpha
|
||||
import androidx.core.graphics.blue
|
||||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun Color.toAndroidGraphicsColor(): Int {
|
||||
val argb = this.toArgb()
|
||||
return android.graphics.Color.argb(argb.alpha, argb.red, argb.green, argb.blue)
|
||||
}
|
||||
|
||||
fun Color.parse(hexColor: String): Color {
|
||||
return Color(hexColor.removePrefix("#").toInt(16))
|
||||
}
|
||||
|
|
@ -8,11 +8,13 @@ import com.tangem.tap.common.extensions.getFromClipboard
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("ComposableFunctionName")
|
||||
@Composable
|
||||
fun copyToClipboard(value: Any, label: String = "") {
|
||||
LocalContext.current.copyToClipboard(value, label)
|
||||
}
|
||||
|
||||
@Suppress("ComposableFunctionName")
|
||||
@Composable
|
||||
fun getFromClipboard(default: CharSequence? = null): CharSequence? {
|
||||
return LocalContext.current.getFromClipboard(default)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ import androidx.core.graphics.drawable.toBitmap
|
|||
*/
|
||||
@Composable
|
||||
fun asImageBitmap(@DrawableRes drawableId: Int): ImageBitmap {
|
||||
val drawable = AppCompatResources.getDrawable(LocalContext.current, drawableId)
|
||||
?: throw NullPointerException()
|
||||
val drawable = requireNotNull(AppCompatResources.getDrawable(LocalContext.current, drawableId)) {
|
||||
"drawable is null"
|
||||
}
|
||||
return drawable.toBitmap().asImageBitmap()
|
||||
}
|
||||
|
|
@ -1,12 +1,16 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import com.tangem.tap.common.extensions.hideKeyboard
|
||||
|
||||
@Composable
|
||||
fun LazyListState.OnBottomReached(loadMoreThreshold: Int, loadMore: () -> Unit) {
|
||||
fun LazyListState.OnBottomReached(loadMoreThreshold: Int, onLoadMore: () -> Unit) {
|
||||
require(loadMoreThreshold >= 0)
|
||||
val shouldLoadMore by remember {
|
||||
derivedStateOf {
|
||||
|
|
@ -17,7 +21,7 @@ fun LazyListState.OnBottomReached(loadMoreThreshold: Int, loadMore: () -> Unit)
|
|||
}
|
||||
|
||||
LaunchedEffect(shouldLoadMore) {
|
||||
if (shouldLoadMore) loadMore()
|
||||
if (shouldLoadMore) onLoadMore()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState
|
|||
open class Button(val enabled: Boolean)
|
||||
|
||||
open class IndeterminateProgressButton(
|
||||
val state: ButtonState
|
||||
val state: ButtonState
|
||||
) : Button(state != ButtonState.DISABLED) {
|
||||
|
||||
val progressState: ProgressState
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ fun Activity.sendEmail(
|
|||
) {
|
||||
fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent {
|
||||
val builder = ShareCompat.IntentBuilder.from(this)
|
||||
.setType("message/rfc822")
|
||||
.setEmailTo(arrayOf(recipient))
|
||||
.setSubject(subject)
|
||||
.setText(text)
|
||||
file?.let { builder.setStream(FileProvider.getUriForFile(this, "${packageName}.provider", it)) }
|
||||
.setType("message/rfc822")
|
||||
.setEmailTo(arrayOf(recipient))
|
||||
.setSubject(subject)
|
||||
.setText(text)
|
||||
file?.let { builder.setStream(FileProvider.getUriForFile(this, "$packageName.provider", it)) }
|
||||
return builder.intent
|
||||
}
|
||||
|
||||
|
|
@ -41,17 +41,17 @@ fun Activity.sendEmail(
|
|||
val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0)
|
||||
|
||||
val targetedIntents = originalIntentResults
|
||||
.filter { originalResult ->
|
||||
emailFilterIntentResults.any {
|
||||
originalResult.activityInfo.packageName == it.activityInfo.packageName
|
||||
}
|
||||
.filter { originalResult ->
|
||||
emailFilterIntentResults.any {
|
||||
originalResult.activityInfo.packageName == it.activityInfo.packageName
|
||||
}
|
||||
.map {
|
||||
createEmailShareIntent(email, subject, message, file).apply {
|
||||
setPackage(it.activityInfo.packageName)
|
||||
}
|
||||
}
|
||||
.map {
|
||||
createEmailShareIntent(email, subject, message, file).apply {
|
||||
setPackage(it.activityInfo.packageName)
|
||||
}
|
||||
.toMutableList()
|
||||
}
|
||||
.toMutableList()
|
||||
try {
|
||||
val chooserIntent = Intent.createChooser(targetedIntents.removeAt(0), "Send mail...")
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ package com.tangem.tap.common.extensions
|
|||
import android.content.res.AssetManager
|
||||
|
||||
fun AssetManager.readJsonFileToString(fileName: String): String =
|
||||
this.open("$fileName.json").bufferedReader().readText()
|
||||
this.open("$fileName.json").bufferedReader().readText()
|
||||
|
|
@ -6,8 +6,7 @@ import com.tangem.operations.backup.BackupService
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun BackupService.primaryCardIsSaltPayVisa(): Boolean {
|
||||
return primaryCardId?.slice(0..3)?.let {
|
||||
SaltPayWorkaround.isVisaBatchId(it)
|
||||
} ?: false
|
||||
return primaryCardId?.slice(0..3)?.let(SaltPayWorkaround::isVisaBatchId) ?: false
|
||||
}
|
||||
|
|
@ -4,12 +4,14 @@ import android.graphics.Bitmap
|
|||
import android.graphics.BitmapFactory
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun Bitmap.toByteArray(): ByteArray {
|
||||
val stream = ByteArrayOutputStream()
|
||||
this.compress(Bitmap.CompressFormat.JPEG, 20, stream)
|
||||
return stream.toByteArray()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun ByteArray.toBitmap(): Bitmap {
|
||||
return BitmapFactory.decodeByteArray(this, 0, this.size)
|
||||
}
|
||||
|
|
@ -5,49 +5,17 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.extensions.remove
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@DrawableRes
|
||||
fun Blockchain.getRoundIconRes(): Int {
|
||||
return when (this) {
|
||||
Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_round
|
||||
Blockchain.Ducatus -> R.drawable.ic_ducatus
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_round
|
||||
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_round
|
||||
Blockchain.Litecoin -> R.drawable.ic_litecoin_round
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_round
|
||||
Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_round
|
||||
Blockchain.RSK -> R.drawable.ic_rsk_round
|
||||
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_round
|
||||
Blockchain.Tezos -> R.drawable.ic_tezos_round
|
||||
Blockchain.XRP -> R.drawable.ic_xrp_round
|
||||
Blockchain.Stellar -> R.drawable.ic_stellar_round
|
||||
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> R.drawable.ic_avalanche_round
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_round
|
||||
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_round
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_round
|
||||
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_round
|
||||
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_round
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_round
|
||||
Blockchain.Gnosis -> R.drawable.ic_gnosis_round
|
||||
Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_round
|
||||
Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_round
|
||||
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_round
|
||||
Blockchain.Kusama -> R.drawable.ic_kusama_round
|
||||
Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism
|
||||
Blockchain.Dash -> R.drawable.ic_dash
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
@DrawableRes
|
||||
fun Blockchain.getGreyedOutIconRes(): Int {
|
||||
return when (this) {
|
||||
Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_no_color
|
||||
// Blockchain.Ducatus -> R.drawable.ic_ducatus
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_no_color
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> R.drawable.ic_bitcoin_no_color
|
||||
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_no_color
|
||||
Blockchain.Litecoin -> R.drawable.ic_litecoin_no_color
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_no_color
|
||||
Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet, -> R.drawable.ic_eth_no_color
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> R.drawable.ic_eth_no_color
|
||||
Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_no_color
|
||||
Blockchain.RSK -> R.drawable.ic_rsk_no_color
|
||||
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_no_color
|
||||
Blockchain.Tezos -> R.drawable.ic_tezos_no_color
|
||||
|
|
@ -57,7 +25,8 @@ fun Blockchain.getGreyedOutIconRes(): Int {
|
|||
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_no_color
|
||||
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_no_color
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_no_color
|
||||
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_no_color
|
||||
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet ->
|
||||
R.drawable.ic_bsc_no_color
|
||||
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color
|
||||
Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color
|
||||
|
|
@ -82,4 +51,4 @@ fun Blockchain.getNetworkName(): String {
|
|||
}
|
||||
|
||||
val Blockchain.fullNameWithoutTestnet
|
||||
get() = this.fullName.remove(" Testnet")
|
||||
get() = this.fullName.remove(" Testnet")
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import androidx.annotation.AnyRes
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
fun Context.readFile(fileName: String): String =
|
||||
|
|
@ -13,10 +17,22 @@ fun Context.rewriteFile(content: String, fileName: String) {
|
|||
}
|
||||
}
|
||||
|
||||
fun Context.readAssetAsString(fileName: String): String {
|
||||
return this.assets.open("$fileName.json").bufferedReader().readText()
|
||||
}
|
||||
|
||||
fun Context.isPermissionGranted(permission: String): Boolean {
|
||||
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uri to any resource type via given Resource Instance
|
||||
* @param resId - resource id
|
||||
* @throws Resources.NotFoundException if the given ID does not exist.
|
||||
* @return - Uri to resource by the given ID
|
||||
*/
|
||||
@Throws(Resources.NotFoundException::class)
|
||||
fun Context.resourceUri(@AnyRes resId: Int): Uri {
|
||||
return Uri.parse(
|
||||
ContentResolver.SCHEME_ANDROID_RESOURCE +
|
||||
"://" + resources.getResourcePackageName(resId) +
|
||||
'/' + resources.getResourceTypeName(resId) +
|
||||
'/' + resources.getResourceEntryName(resId),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.feature.referral.ReferralFragment
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
|
||||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
|
||||
|
|
@ -37,10 +37,12 @@ import timber.log.Timber
|
|||
fun FragmentActivity.openFragment(
|
||||
screen: AppScreen,
|
||||
addToBackstack: Boolean,
|
||||
bundle: Bundle? = null,
|
||||
fgShareTransition: FragmentShareTransition? = null,
|
||||
) {
|
||||
val transaction = this.supportFragmentManager.beginTransaction()
|
||||
val fragment = fragmentFactory(screen)
|
||||
fragment.arguments = bundle
|
||||
fgShareTransition?.apply {
|
||||
fragment.sharedElementEnterTransition = enterTransitionSet
|
||||
fragment.sharedElementReturnTransition = exitTransitionSet
|
||||
|
|
@ -80,21 +82,15 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? {
|
|||
} else {
|
||||
0
|
||||
}
|
||||
val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount)
|
||||
val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount) {
|
||||
this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name
|
||||
else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return tag?.let { AppScreen.valueOf(tag) }
|
||||
}
|
||||
|
||||
fun FragmentActivity.addOnBackPressedDispatcher(
|
||||
isEnabled: Boolean = true,
|
||||
onBackPressed: VoidCallback
|
||||
): OnBackPressedCallback = (object : OnBackPressedCallback(isEnabled) {
|
||||
override fun handleOnBackPressed() {
|
||||
onBackPressed()
|
||||
}
|
||||
}).also { this.onBackPressedDispatcher.addCallback(it) }
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun fragmentFactory(screen: AppScreen): Fragment {
|
||||
return when (screen) {
|
||||
AppScreen.Home -> HomeFragment()
|
||||
|
|
@ -117,6 +113,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
AppScreen.WalletConnectSessions -> WalletConnectFragment()
|
||||
AppScreen.QrScan -> QrScanFragment()
|
||||
AppScreen.ReferralProgram -> ReferralFragment()
|
||||
AppScreen.Swap -> SwapFragment()
|
||||
AppScreen.Welcome -> WelcomeFragment()
|
||||
AppScreen.SaveWallet -> SaveWalletBottomSheetFragment()
|
||||
AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment()
|
||||
|
|
|
|||
|
|
@ -11,8 +11,11 @@ import java.text.DecimalFormat
|
|||
import java.text.DecimalFormatSymbols
|
||||
import java.util.*
|
||||
|
||||
// TODO: move extensions to utils
|
||||
fun BigDecimal.toFormattedString(
|
||||
decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US
|
||||
decimals: Int,
|
||||
roundingMode: RoundingMode = RoundingMode.DOWN,
|
||||
locale: Locale = Locale.US,
|
||||
): String {
|
||||
val symbols = DecimalFormatSymbols(locale)
|
||||
val df = DecimalFormat()
|
||||
|
|
@ -24,9 +27,12 @@ fun BigDecimal.toFormattedString(
|
|||
return df.format(this)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun BigDecimal.toFormattedCurrencyString(
|
||||
decimals: Int, currency: String, roundingMode: RoundingMode = RoundingMode.DOWN,
|
||||
limitNumberOfDecimals: Boolean = true
|
||||
decimals: Int,
|
||||
currency: String,
|
||||
roundingMode: RoundingMode = RoundingMode.DOWN,
|
||||
limitNumberOfDecimals: Boolean = true,
|
||||
): String {
|
||||
val decimalsForRounding = if (limitNumberOfDecimals) {
|
||||
if (decimals > 8) 8 else decimals
|
||||
|
|
@ -34,13 +40,14 @@ fun BigDecimal.toFormattedCurrencyString(
|
|||
decimals
|
||||
}
|
||||
val formattedAmount = this.toFormattedString(
|
||||
decimals = decimalsForRounding, roundingMode = roundingMode
|
||||
decimals = decimalsForRounding,
|
||||
roundingMode = roundingMode,
|
||||
)
|
||||
return "$formattedAmount $currency"
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatRateString(
|
||||
fiatCurrencyName: String
|
||||
fiatCurrencyName: String,
|
||||
): String {
|
||||
val value = this
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
|
|
@ -51,7 +58,7 @@ fun BigDecimal.toFiatRateString(
|
|||
fun BigDecimal.toFiatString(
|
||||
rateValue: BigDecimal,
|
||||
fiatCurrencyName: String,
|
||||
formatWithSpaces: Boolean = false
|
||||
formatWithSpaces: Boolean = false,
|
||||
): String {
|
||||
val fiatValue = rateValue.multiply(this)
|
||||
return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces)
|
||||
|
|
@ -64,7 +71,7 @@ fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
|
|||
|
||||
fun BigDecimal.toFormattedFiatValue(
|
||||
fiatCurrencyName: String,
|
||||
formatWithSpaces: Boolean = false
|
||||
formatWithSpaces: Boolean = false,
|
||||
): String {
|
||||
val fiatValue = this.setScale(2, RoundingMode.HALF_UP)
|
||||
.let { if (formatWithSpaces) it.formatWithSpaces() else it }
|
||||
|
|
@ -81,9 +88,7 @@ fun BigDecimal.scaleToFiat(applyPrecision: Boolean = false): BigDecimal {
|
|||
if (this.isZero()) return this
|
||||
|
||||
val scaledFiat = this.setScale(2, RoundingMode.DOWN)
|
||||
return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1)
|
||||
else scaledFiat
|
||||
|
||||
return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1) else scaledFiat
|
||||
}
|
||||
|
||||
fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = RoundingMode.DOWN): BigDecimal {
|
||||
|
|
@ -108,7 +113,7 @@ fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean {
|
|||
|
||||
fun BigDecimal.formatAmountAsSpannedString(
|
||||
currencySymbol: String,
|
||||
reminderPartSizeProportion: Float = 0.7f
|
||||
reminderPartSizeProportion: Float = 0.7f,
|
||||
): SpannedString {
|
||||
val amount = this
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
|
|
@ -124,11 +129,12 @@ fun BigDecimal.formatAmountAsSpannedString(
|
|||
append(
|
||||
"$reminder $currencySymbol",
|
||||
RelativeSizeSpan(reminderPartSizeProportion),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun BigDecimal.formatWithSpaces(): String {
|
||||
val str = this.toString()
|
||||
var integerStr = str.substringBefore('.')
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ fun String.colorSegment(
|
|||
context: Context,
|
||||
color: Int,
|
||||
startIndex: Int = 0,
|
||||
endIndex: Int = this.length
|
||||
endIndex: Int = this.length,
|
||||
): Spannable {
|
||||
return this.toSpannable()
|
||||
.also { spannable ->
|
||||
|
|
@ -37,11 +37,12 @@ fun String.colorSegment(
|
|||
ForegroundColorSpan(ContextCompat.getColor(context, color)),
|
||||
startIndex,
|
||||
endIndex,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun String.toQrCode(): Bitmap {
|
||||
val hintMap = Hashtable<EncodeHintType, Any>()
|
||||
hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
|
||||
|
|
|
|||
|
|
@ -18,13 +18,12 @@ fun EditText.update(text: String?) {
|
|||
|
||||
val textLength = text?.length ?: 0
|
||||
|
||||
//prevent cursor jumping while editing a text
|
||||
// prevent cursor jumping while editing a text
|
||||
val cursorPosition = if (selectionEnd > textLength) textLength else selectionEnd
|
||||
this.setText(text)
|
||||
if (!isFocused || textLength == 0) return
|
||||
|
||||
if (cursorPosition == 0) setSelection(textLength)
|
||||
else setSelection(cursorPosition)
|
||||
if (cursorPosition == 0) setSelection(textLength) else setSelection(cursorPosition)
|
||||
}
|
||||
|
||||
fun EditText.setOnImeActionListener(action: Int, handler: (EditText) -> Unit) {
|
||||
|
|
@ -56,13 +55,4 @@ fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) {
|
|||
error = null
|
||||
isErrorEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
fun TextView.isEllipsized(): Boolean {
|
||||
val layout = this.layout
|
||||
if (layout != null) {
|
||||
val lines: Int = layout.lineCount
|
||||
return (lines > 0) && (layout.getEllipsisCount(lines - 1) > 0)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -6,19 +6,23 @@ import androidx.core.graphics.luminance
|
|||
import androidx.core.graphics.toColorInt
|
||||
import com.tangem.blockchain.common.Token
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ColorInt
|
||||
fun Token.getColor(isTestnet: Boolean = false): Int {
|
||||
val defaultColor = "#C7C7CC".toColorInt() // equivalent to R.color.lightGray4
|
||||
|
||||
return if (isTestnet)
|
||||
defaultColor
|
||||
else try {
|
||||
("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt()
|
||||
} catch (exception: Exception) {
|
||||
return if (isTestnet) {
|
||||
defaultColor
|
||||
} else {
|
||||
try {
|
||||
("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt()
|
||||
} catch (exception: Exception) {
|
||||
defaultColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ColorInt
|
||||
fun Token.getTextColor(isTestnet: Boolean = false): Int = when {
|
||||
isTestnet -> Color.WHITE
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
|
|
@ -25,11 +23,6 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
|
||||
fun Fragment.getDrawable(@DrawableRes drawableResId: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(requireContext(), drawableResId)
|
||||
}
|
||||
|
||||
fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(this, drawableResId)
|
||||
|
|
@ -62,17 +55,8 @@ fun View.getQuantityString(@PluralsRes id: Int, quantity: Int): String {
|
|||
return context.resources.getQuantityString(id, quantity, quantity)
|
||||
}
|
||||
|
||||
fun View.getResourceName(): String {
|
||||
return try {
|
||||
resources.getResourceEntryName(id)
|
||||
} catch (ex: Resources.NotFoundException) {
|
||||
"Not found"
|
||||
}
|
||||
}
|
||||
|
||||
fun View.show(show: Boolean, invokeBeforeStateChanged: (() -> Unit)? = null) {
|
||||
return if (show) this.show(invokeBeforeStateChanged)
|
||||
else this.hide(invokeBeforeStateChanged)
|
||||
return if (show) this.show(invokeBeforeStateChanged) else this.hide(invokeBeforeStateChanged)
|
||||
}
|
||||
|
||||
fun View.show(invokeBeforeStateChanged: (() -> Unit)? = null) {
|
||||
|
|
@ -102,7 +86,9 @@ fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> U
|
|||
|
||||
fun Context.dpToPixels(dp: Int): Int =
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dp.toFloat(),
|
||||
this.resources.displayMetrics,
|
||||
).toInt()
|
||||
|
||||
fun Context.pixelsToDp(pixels: Int): Int {
|
||||
|
|
@ -111,12 +97,6 @@ fun Context.pixelsToDp(pixels: Int): Int {
|
|||
.toInt()
|
||||
}
|
||||
|
||||
fun Context.dpToPixels(dp: Float): Float =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.resources.displayMetrics)
|
||||
|
||||
fun Context.pixelsToDp(pixels: Float): Float =
|
||||
(pixels / (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT))
|
||||
|
||||
tailrec fun Context?.getActivity(): Activity? = this as? Activity
|
||||
?: (this as? ContextWrapper)?.baseContext?.getActivity()
|
||||
|
||||
|
|
@ -136,19 +116,6 @@ fun MaterialCardView.setMargins(
|
|||
this.layoutParams = params
|
||||
}
|
||||
|
||||
fun Activity.setSystemBarTextColor(setTextDark: Boolean) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val flags = this.window.decorView.systemUiVisibility
|
||||
// Update the SystemUiVisibility dependening on whether we want a Light or Dark theme.
|
||||
this.window.decorView.systemUiVisibility =
|
||||
if (setTextDark) {
|
||||
flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
|
||||
} else {
|
||||
flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun View.hideKeyboard() {
|
||||
val inputMethodManager =
|
||||
context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
|
|
@ -185,21 +152,6 @@ fun Fragment.shareText(text: String) {
|
|||
requireContext().shareText(text)
|
||||
}
|
||||
|
||||
fun Context.safeStartActivity(
|
||||
intent: Intent,
|
||||
options: Bundle? = null,
|
||||
fallback: ((ActivityNotFoundException) -> Unit)? = null,
|
||||
finally: VoidCallback? = null,
|
||||
) {
|
||||
try {
|
||||
this.startActivity(intent, options)
|
||||
} catch (ex: ActivityNotFoundException) {
|
||||
fallback?.invoke(ex)
|
||||
} finally {
|
||||
finally?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
|
||||
return context.getString(resId, *formatArgs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import timber.log.Timber
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
|
||||
|
|
@ -72,8 +73,7 @@ fun WalletManager?.getAddressData(): AddressData? {
|
|||
val wallet = this?.wallet ?: return null
|
||||
|
||||
val addressDataList = wallet.createAddressesData()
|
||||
return if (addressDataList.isEmpty()) null
|
||||
else addressDataList[0]
|
||||
return if (addressDataList.isEmpty()) null else addressDataList[0]
|
||||
}
|
||||
|
||||
fun <T> WalletManager.Companion.stub(): T {
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@ package com.tangem.tap.common.feature
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Feature {
|
||||
fun featureIsSwitchedOn():Boolean
|
||||
fun featureIsSwitchedOn(): Boolean
|
||||
}
|
||||
|
|
@ -48,6 +48,9 @@ class AdditionalFeedbackInfo {
|
|||
var fee: String = ""
|
||||
var token: String = ""
|
||||
|
||||
private val Address.name: String
|
||||
get() = type.javaClass.simpleName
|
||||
|
||||
fun setCardInfo(data: ScanResponse) {
|
||||
cardId = data.card.cardId
|
||||
cardBlockchain = data.walletData?.blockchain ?: ""
|
||||
|
|
@ -108,6 +111,7 @@ class AdditionalFeedbackInfo {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String {
|
||||
return if (addresses.size == 1) {
|
||||
getExploreUrl(address)
|
||||
|
|
@ -117,7 +121,4 @@ class AdditionalFeedbackInfo {
|
|||
.joinToString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
private val Address.name: String
|
||||
get() = type.javaClass.simpleName
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ interface FeedbackData {
|
|||
|
||||
fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String {
|
||||
return StringBuilder().apply {
|
||||
append(context.getString(mainMessageResId))
|
||||
|
|
@ -42,6 +43,7 @@ class ScanFailsEmail : FeedbackData {
|
|||
override val subjectResId: Int = R.string.feedback_subject_scan_failed
|
||||
override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String = StringBuilder().apply {
|
||||
append(context.getString(mainMessageResId))
|
||||
breakLine(4)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.core.analytics.Analytics
|
|||
import com.tangem.domain.common.TapWorkarounds
|
||||
import com.tangem.tap.common.extensions.sendEmail
|
||||
import com.tangem.tap.common.log.TangemLogCollector
|
||||
import com.tangem.tap.common.zendesk.ZendeskConfig
|
||||
import com.tangem.datasource.config.models.ZendeskConfig
|
||||
import com.tangem.tap.foregroundActivityObserver
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
import com.tangem.tap.withForegroundActivity
|
||||
|
|
|
|||
|
|
@ -30,13 +30,11 @@ fun createCoilImageLoader(
|
|||
}
|
||||
)
|
||||
.build()
|
||||
|
||||
}
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
private class CoilTimberLogger : Logger {
|
||||
|
||||
override var level: Int = Log.DEBUG
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ 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)
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,11 +16,6 @@ import me.dm7.barcodescanner.zxing.ZXingScannerView
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
|
||||
companion object {
|
||||
val SCAN_QR_REQUEST_CODE = 1001
|
||||
val SCAN_RESULT = "scanResult"
|
||||
}
|
||||
|
||||
private lateinit var mScannerView: ZXingScannerView
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
|
|
@ -29,7 +24,6 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
|
|||
setContentView(mScannerView)
|
||||
|
||||
if (!permissionIsGranted()) requestPermission()
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
@ -66,4 +60,9 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
|
|||
private fun requestPermission() {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SCAN_QR_REQUEST_CODE = 1001
|
||||
const val SCAN_RESULT = "scanResult"
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware
|
|||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import org.rekotlin.Middleware
|
||||
import org.rekotlin.StateType
|
||||
|
||||
|
|
@ -68,6 +69,12 @@ data class AppState(
|
|||
val domainNetworks: NetworkServices
|
||||
get() = domainState.globalState.networkServices
|
||||
|
||||
val featureRepositoryProvider: FeatureRepositoryProvider
|
||||
get() = FeatureRepositoryProvider(
|
||||
tangemTechApi = domainNetworks.tangemTechService.api,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getMiddleware(): List<Middleware<AppState>> {
|
||||
return listOf(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.tap.common.redux
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.tap.features.home.data.HomeRepositoryImpl
|
||||
import com.tangem.tap.features.home.domain.HomeRepository
|
||||
import com.tangem.tap.features.wallet.data.WalletRepositoryImpl
|
||||
import com.tangem.tap.features.wallet.domain.WalletRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
||||
class FeatureRepositoryProvider(tangemTechApi: TangemTechApi, dispatchers: CoroutineDispatcherProvider) {
|
||||
|
||||
val walletRepository: WalletRepository = WalletRepositoryImpl(tangemTechApi, dispatchers)
|
||||
|
||||
val homeRepository: HomeRepository = HomeRepositoryImpl(tangemTechApi, dispatchers)
|
||||
}
|
||||
|
|
@ -88,6 +88,7 @@ val notificationsMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun handleNotificationAction(action: Action) {
|
||||
if (action is Debug && !BuildConfig.DEBUG) return
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ import com.tangem.tap.common.redux.ErrorAction
|
|||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.ToastNotificationAction
|
||||
import com.tangem.tap.common.zendesk.ZendeskConfig
|
||||
import com.tangem.datasource.config.models.ZendeskConfig
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
|
|
@ -47,7 +47,7 @@ sealed class GlobalAction : Action {
|
|||
* For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start.
|
||||
* See more Onboarding.Start, CheckForUnfinishedBackup
|
||||
*/
|
||||
object StartForUnfinishedBackup : Onboarding()
|
||||
data class StartForUnfinishedBackup(val addedBackupCardsCount: Int) : Onboarding()
|
||||
object Stop : Onboarding()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
|
|
@ -31,10 +30,8 @@ import org.rekotlin.DispatchFunction
|
|||
import org.rekotlin.Middleware
|
||||
import java.util.*
|
||||
|
||||
class GlobalMiddleware {
|
||||
companion object {
|
||||
val handler = globalMiddlewareHandler
|
||||
}
|
||||
object GlobalMiddleware {
|
||||
val handler = globalMiddlewareHandler
|
||||
}
|
||||
|
||||
private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState ->
|
||||
|
|
@ -46,6 +43,7 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleAction(action: Action, appState: () -> AppState?, dispatch: DispatchFunction) {
|
||||
when (action) {
|
||||
is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
|
||||
|
|
@ -74,7 +72,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
store.state.globalState.warningManager?.let {
|
||||
if (it.hideWarning(action.warning)) {
|
||||
if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
|
||||
//TODO: No appropriate warningMessage identification. Make it better later
|
||||
// TODO: No appropriate warningMessage identification. Make it better later
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +99,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
|
||||
// if config not set -> try to get it based on a scanResponse.productType
|
||||
val unsafeZendeskConfig = action.zendeskConfig ?: when {
|
||||
scanResponse?.isSaltPay() == true -> config.saltPayConfig?.zendesk
|
||||
scanResponse?.cardTypesResolver?.isSaltPay() == true -> config.saltPayConfig?.zendesk
|
||||
else -> config.zendesk
|
||||
}
|
||||
|
||||
|
|
@ -187,23 +185,20 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
}
|
||||
is GlobalAction.FetchUserCountry -> {
|
||||
scope.launch {
|
||||
val techService = store.state.domainNetworks.tangemTechService
|
||||
when (val result = techService.userCountry()) {
|
||||
is Result.Success -> {
|
||||
// TODO("After adding DI") get dependencies by DI
|
||||
runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() }
|
||||
.onSuccess {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.FetchUserCountry.Success(
|
||||
countryCode = result.data.code.lowercase(),
|
||||
),
|
||||
GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()),
|
||||
)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
.onFailure {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.FetchUserCountry.Success(
|
||||
countryCode = Locale.getDefault().country.lowercase(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,8 @@ import com.tangem.tap.preferencesStorage
|
|||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import org.rekotlin.Action
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolder): GlobalState {
|
||||
|
||||
if (action !is GlobalAction) return state.globalState
|
||||
|
||||
val globalState = state.globalState
|
||||
|
|
@ -39,6 +39,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
|||
globalState.copy(scanResponse = action.scanResponse)
|
||||
}
|
||||
is GlobalAction.ChangeAppCurrency -> {
|
||||
appStateHolder.appFiatCurrency = action.appCurrency
|
||||
globalState.copy(appCurrency = action.appCurrency)
|
||||
}
|
||||
is GlobalAction.RestoreAppCurrency.Success -> {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.tap.common.feedback.FeedbackManager
|
|||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingManager
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import java.lang.ref.WeakReference
|
|||
data class FragmentShareTransition(
|
||||
val shareElements: List<ShareElement>,
|
||||
val enterTransitionSet: TransitionSet,
|
||||
val exitTransitionSet: TransitionSet
|
||||
val exitTransitionSet: TransitionSet,
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -24,12 +24,12 @@ class ShareElement(view: View, name: String? = null) {
|
|||
init {
|
||||
name?.let { view.transitionName = it }
|
||||
elementName = view.transitionName
|
||||
?: throw UnsupportedOperationException("ShareElement require the name")
|
||||
?: throw UnsupportedOperationException("ShareElement require the name")
|
||||
wView = WeakReference(view)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val imvFrontCard = "imv_front_card"
|
||||
val imvBackCard = "imv_back_card"
|
||||
const val imvFrontCard = "imv_front_card"
|
||||
const val imvBackCard = "imv_back_card"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.common.redux.navigation
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.rekotlin.Action
|
||||
import java.lang.ref.WeakReference
|
||||
|
|
@ -10,6 +11,7 @@ sealed class NavigationAction : Action {
|
|||
val screen: AppScreen,
|
||||
val fragmentShareTransition: FragmentShareTransition? = null,
|
||||
val addToBackstack: Boolean = true,
|
||||
val bundle: Bundle? = null,
|
||||
) : NavigationAction()
|
||||
|
||||
data class PopBackTo(
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ val navigationMiddleware: Middleware<AppState> = { _, state ->
|
|||
screen = action.screen,
|
||||
addToBackstack = action.addToBackstack,
|
||||
fgShareTransition = action.fragmentShareTransition,
|
||||
bundle = action.bundle,
|
||||
)
|
||||
}
|
||||
is NavigationAction.PopBackTo -> {
|
||||
|
|
@ -33,8 +34,13 @@ val navigationMiddleware: Middleware<AppState> = { _, state ->
|
|||
AppScreen.Home,
|
||||
AppScreen.Welcome,
|
||||
-> {
|
||||
navState?.activity?.get()?.popBackTo(screen, inclusive = true)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen))
|
||||
if (navState?.backStack?.contains(screen) == false) {
|
||||
// Pop back to activity
|
||||
navState.activity?.get()?.popBackTo(screen = null, inclusive = true)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen))
|
||||
} else {
|
||||
navState?.activity?.get()?.popBackTo(screen, action.inclusive)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
navState?.activity?.get()?.popBackTo(screen, action.inclusive)
|
||||
|
|
|
|||
|
|
@ -4,14 +4,11 @@ import com.tangem.tap.common.extensions.getPreviousScreen
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
class NavigationReducer {
|
||||
companion object {
|
||||
fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state)
|
||||
}
|
||||
object NavigationReducer {
|
||||
fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, state: AppState): NavigationState {
|
||||
|
||||
val navigationAction = action as? NavigationAction ?: return state.navigationState
|
||||
val navState = state.navigationState
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ enum class AppScreen(
|
|||
WalletConnectSessions,
|
||||
QrScan,
|
||||
ReferralProgram,
|
||||
Swap,
|
||||
Welcome,
|
||||
SaveWallet(isDialogFragment = true),
|
||||
WalletSelector(isDialogFragment = true),
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ import com.tangem.tap.common.extensions.filterNotNull
|
|||
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.googlepay.GooglePayService
|
||||
import com.tangem.tap.common.shop.shopify.ShopifyService
|
||||
import com.tangem.tap.common.shop.shopify.ShopifyShop
|
||||
import com.tangem.datasource.config.models.ShopifyShop
|
||||
import com.tangem.tap.common.shop.shopify.data.CheckoutItem
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
|
@ -87,7 +88,8 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
fun buyWithGooglePay(productType: ProductType) {
|
||||
val totalPrice = checkouts[productType]!!.totalPriceV2.amount
|
||||
googlePayService.payWithGooglePay(
|
||||
totalPriceCents = totalPrice, currencyCode = checkouts[productType]!!.currencyCode.name,
|
||||
totalPriceCents = totalPrice,
|
||||
currencyCode = checkouts[productType]!!.currencyCode.name,
|
||||
merchantID = shopifyService.shop.merchantID,
|
||||
)
|
||||
}
|
||||
|
|
@ -188,7 +190,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
|
|||
appliedDiscount = it.getAppliedDiscount(),
|
||||
),
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
return Result.failure(result.exceptionOrNull()!!)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.common.shop
|
||||
package com.tangem.tap.common.shop.googlepay
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Activity.RESULT_CANCELED
|
||||
|
|
@ -6,9 +6,12 @@ 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.google.android.gms.wallet.AutoResolveHelper
|
||||
import com.google.android.gms.wallet.IsReadyToPayRequest
|
||||
import com.google.android.gms.wallet.PaymentData
|
||||
import com.google.android.gms.wallet.PaymentDataRequest
|
||||
import com.google.android.gms.wallet.PaymentsClient
|
||||
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
|
||||
|
|
@ -22,7 +25,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
|
|||
// 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())
|
||||
|
||||
|
|
@ -48,7 +50,7 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
|
|||
totalPriceCents,
|
||||
currencyCode = currencyCode,
|
||||
countryCode = "RU",
|
||||
merchantID = merchantID
|
||||
merchantID = merchantID,
|
||||
)
|
||||
if (paymentDataRequestJson == null) {
|
||||
Timber.e("RequestPayment: can't fetch payment data request")
|
||||
|
|
@ -57,7 +59,9 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
|
|||
val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())
|
||||
|
||||
AutoResolveHelper.resolveTask(
|
||||
paymentsClient.loadPaymentData(request), activity, LOAD_PAYMENT_DATA_REQUEST_CODE
|
||||
paymentsClient.loadPaymentData(request),
|
||||
activity,
|
||||
LOAD_PAYMENT_DATA_REQUEST_CODE,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +85,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
|
|||
} else {
|
||||
Result.failure(Exception("$statusCode"))
|
||||
}
|
||||
|
||||
}
|
||||
else -> Result.failure(Exception("Unknown Status"))
|
||||
}
|
||||
|
|
@ -117,7 +120,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
|
|||
.getString("token")
|
||||
|
||||
return GooglePayResponse(address, token)
|
||||
|
||||
} catch (e: JSONException) {
|
||||
Log.e("handlePaymentSuccess", "Error: " + e.toString())
|
||||
}
|
||||
|
|
@ -144,5 +146,5 @@ data class Address(
|
|||
val address3: String,
|
||||
val locality: String,
|
||||
val administrativeArea: String,
|
||||
val sortingCode: String
|
||||
val sortingCode: String,
|
||||
)
|
||||
|
|
@ -14,20 +14,7 @@ object GooglePayUtil {
|
|||
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
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private val allowedCardNetworks = JSONArray(
|
||||
listOf(
|
||||
"AMEX",
|
||||
|
|
@ -35,27 +22,46 @@ object GooglePayUtil {
|
|||
"INTERAC",
|
||||
"JCB",
|
||||
"MASTERCARD",
|
||||
"VISA"
|
||||
)
|
||||
"VISA",
|
||||
),
|
||||
)
|
||||
|
||||
private val allowedCardAuthMethods = JSONArray(
|
||||
listOf(
|
||||
"PAN_ONLY",
|
||||
"CRYPTOGRAM_3DS"
|
||||
)
|
||||
"CRYPTOGRAM_3DS",
|
||||
),
|
||||
)
|
||||
|
||||
private val merchantInfo: JSONObject = JSONObject().put("merchantName", "Example Merchant")
|
||||
|
||||
private fun gatewayTokenizationSpecification(merchantID: String): JSONObject {
|
||||
return JSONObject().apply {
|
||||
put("type", "PAYMENT_GATEWAY")
|
||||
put(
|
||||
"parameters",
|
||||
JSONObject(
|
||||
mapOf(
|
||||
"gateway" to "shopify",
|
||||
"gatewayMerchantId" to merchantID,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
"billingAddressParameters",
|
||||
JSONObject().apply {
|
||||
put("format", "FULL")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
put("type", "CARD")
|
||||
|
|
@ -83,7 +89,6 @@ object GooglePayUtil {
|
|||
baseRequest.apply {
|
||||
put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod()))
|
||||
}
|
||||
|
||||
} catch (e: JSONException) {
|
||||
null
|
||||
}
|
||||
|
|
@ -92,7 +97,7 @@ object GooglePayUtil {
|
|||
private fun getTransactionInfo(
|
||||
price: String,
|
||||
countryCode: String,
|
||||
currencyCode: String
|
||||
currencyCode: String,
|
||||
): JSONObject {
|
||||
return JSONObject().apply {
|
||||
put("totalPrice", price)
|
||||
|
|
@ -102,15 +107,11 @@ object GooglePayUtil {
|
|||
}
|
||||
}
|
||||
|
||||
private val merchantInfo: JSONObject =
|
||||
JSONObject().put("merchantName", "Example Merchant")
|
||||
|
||||
|
||||
fun getPaymentDataRequest(
|
||||
price: String,
|
||||
countryCode: String,
|
||||
currencyCode: String,
|
||||
merchantID: String
|
||||
merchantID: String,
|
||||
): JSONObject? {
|
||||
try {
|
||||
return baseRequest.apply {
|
||||
|
|
@ -133,5 +134,4 @@ object GooglePayUtil {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const val PAYMENTS_ENVIRONMENT = WalletConstants.ENVIRONMENT_TEST
|
||||
|
|
@ -7,6 +7,7 @@ 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.datasource.config.models.ShopifyShop
|
||||
import com.tangem.tap.common.shop.shopify.data.CheckoutItem
|
||||
import com.tangem.tap.common.shop.shopify.data.checkoutFieldsFragment
|
||||
import com.tangem.tap.common.shop.shopify.data.collectionFieldsFragment
|
||||
|
|
@ -16,11 +17,10 @@ import java.util.concurrent.TimeUnit
|
|||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
@Suppress("LargeClass")
|
||||
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
|
||||
|
|
@ -40,16 +40,15 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
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()
|
||||
}
|
||||
rootQuery.collections(
|
||||
{ arg -> arg.first(250).query(filter) },
|
||||
CollectionConnectionQuery::collectionFieldsFragment,
|
||||
)
|
||||
}
|
||||
return when (val result = queryAsync(query)) {
|
||||
is GraphCallResult.Success -> {
|
||||
|
|
@ -66,7 +65,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
}
|
||||
|
||||
suspend fun checkout(pollUntilOrder: Boolean, checkoutID: ID): Result<Checkout> {
|
||||
|
||||
val query = query { rootQuery: QueryRootQuery ->
|
||||
rootQuery
|
||||
.node(checkoutID) { query ->
|
||||
|
|
@ -78,7 +76,8 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
}
|
||||
}
|
||||
val retryHandler = RetryHandler.build<QueryRoot>(
|
||||
1, TimeUnit.SECONDS
|
||||
delay = 1,
|
||||
timeUnit = TimeUnit.SECONDS,
|
||||
) {
|
||||
this.retryWhen { result ->
|
||||
when (result) {
|
||||
|
|
@ -100,7 +99,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
} else {
|
||||
Result.failure(ShopifyError.Unknown)
|
||||
}
|
||||
|
||||
}
|
||||
is GraphCallResult.Failure -> {
|
||||
Result.failure(result.error)
|
||||
|
|
@ -110,10 +108,8 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
|
||||
suspend fun createCheckout(
|
||||
checkoutItems: List<CheckoutItem>,
|
||||
checkoutID: ID? = null
|
||||
checkoutID: ID? = null,
|
||||
): Result<Checkout> {
|
||||
|
||||
|
||||
val storefrontLineItems: MutableList<CheckoutLineItemInput> = checkoutItems
|
||||
.map { CheckoutLineItemInput(it.quantity, it.id) }.toMutableList()
|
||||
|
||||
|
|
@ -121,13 +117,14 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutLineItemsReplace(
|
||||
storefrontLineItems, checkoutID
|
||||
storefrontLineItems,
|
||||
checkoutID,
|
||||
) { payloadQuery: CheckoutLineItemsReplacePayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.userErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
.userErrors { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
|
|
@ -137,18 +134,18 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
} else {
|
||||
val input = CheckoutCreateInput()
|
||||
.setLineItemsInput(
|
||||
Input.value(storefrontLineItems)
|
||||
Input.value(storefrontLineItems),
|
||||
)
|
||||
mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutCreate(
|
||||
input
|
||||
input,
|
||||
) { payloadQuery: CheckoutCreatePayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
.checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
|
|
@ -163,13 +160,14 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutDiscountCodeApplyV2(
|
||||
discountCode, checkoutID
|
||||
discountCode,
|
||||
checkoutID,
|
||||
) { payloadQuery: CheckoutDiscountCodeApplyV2PayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
.checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
|
|
@ -183,44 +181,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutDiscountCodeRemove(
|
||||
checkoutID
|
||||
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()
|
||||
}
|
||||
|
|
@ -231,59 +194,18 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
checkoutID: ID,
|
||||
): Result<Checkout> {
|
||||
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutCompleteWithTokenizedPaymentV3(
|
||||
checkoutID, payment
|
||||
checkoutID,
|
||||
payment,
|
||||
) { payloadQuery: CheckoutCompleteWithTokenizedPaymentV3PayloadQuery ->
|
||||
payloadQuery
|
||||
.payment { paymentQuery: PaymentQuery ->
|
||||
|
|
@ -295,7 +217,7 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
checkoutQuery
|
||||
.ready()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
.checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
|
|
@ -305,10 +227,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
fun startGooglePaySession() {
|
||||
// PaySession()
|
||||
}
|
||||
|
||||
private suspend fun runCheckoutMutation(mutation: MutationQuery): Result<Checkout> {
|
||||
return when (val result = mutationQueryAsync(mutation)) {
|
||||
is GraphCallResult.Success -> {
|
||||
|
|
@ -322,13 +240,13 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
|
||||
Result.success(checkout)
|
||||
}
|
||||
is GraphCallResult.Failure -> Result.failure(result.error)
|
||||
is GraphCallResult.Failure -> Result.failure(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun queryAsync(
|
||||
query: QueryRootQuery,
|
||||
retryHandler: RetryHandler<QueryRoot>
|
||||
retryHandler: RetryHandler<QueryRoot>,
|
||||
): GraphCallResult<QueryRoot> =
|
||||
withContext(Dispatchers.IO) {
|
||||
suspendCoroutine { continuation ->
|
||||
|
|
@ -358,12 +276,11 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun initClient(): GraphClient {
|
||||
return GraphClient.build(
|
||||
application,
|
||||
shop.domain,
|
||||
shop.storefrontApiKey
|
||||
shop.storefrontApiKey,
|
||||
) {
|
||||
// httpCache(application.filesDir) {
|
||||
// cacheMaxSizeBytes = (1024 * 1024 * 10)
|
||||
|
|
@ -371,13 +288,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
// HttpCachePolicy.Default.CACHE_FIRST.expireAfter(20, TimeUnit.MINUTES)
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed class ShopifyError : Throwable() {
|
||||
object Unknown : ShopifyError()
|
||||
object GooglePayFailed : ShopifyError()
|
||||
class UserError(val errorMessage: String)
|
||||
}
|
||||
|
|
@ -2,23 +2,24 @@ package com.tangem.tap.common.shop.shopify.data
|
|||
|
||||
import com.shopify.buy3.Storefront
|
||||
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
fun Storefront.CheckoutQuery.checkoutFieldsFragment() {
|
||||
// id()
|
||||
ready()
|
||||
webUrl()
|
||||
currencyCode()
|
||||
lineItemsSubtotalPrice { it.amount() }
|
||||
totalPriceV2 {
|
||||
it.currencyCode()
|
||||
it.amount() }
|
||||
it.amount()
|
||||
}
|
||||
lineItems({ arg -> arg.first(250) }) {
|
||||
it.edges {
|
||||
it.node {
|
||||
// it.id()
|
||||
it.title()
|
||||
it.quantity()
|
||||
it.variant() {
|
||||
it.priceV2() { it.amount() }
|
||||
it.variant {
|
||||
it.priceV2 { it.amount() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ package com.tangem.tap.common.shop.shopify.data
|
|||
|
||||
import com.shopify.buy3.Storefront
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun Storefront.CollectionConnectionQuery.collectionFieldsFragment() {
|
||||
edges { collectionEdgeQuery ->
|
||||
collectionEdgeQuery
|
||||
.node { collectionQuery ->
|
||||
collectionQuery
|
||||
.title()
|
||||
.products({ arg -> arg.first(250) }
|
||||
.products(
|
||||
{ arg -> arg.first(250) },
|
||||
) { productConnectionQuery ->
|
||||
productConnectionQuery
|
||||
.edges { productEdgeQuery ->
|
||||
|
|
|
|||
|
|
@ -17,15 +17,15 @@ import com.tangem.wallet.R
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class MaxAmountSnackbar(
|
||||
parent: ViewGroup,
|
||||
content: MaxAmountSnackbarView
|
||||
parent: ViewGroup,
|
||||
content: MaxAmountSnackbarView
|
||||
) : BaseTransientBottomBar<MaxAmountSnackbar>(parent, content, content) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun make(view: View, onClick: () -> Unit): MaxAmountSnackbar {
|
||||
val parent = view.findSuitableParent() ?: throw IllegalArgumentException(
|
||||
"No suitable parent found from the given view. Please provide a valid view."
|
||||
"No suitable parent found from the given view. Please provide a valid view."
|
||||
)
|
||||
val inflater = LayoutInflater.from(view.context)
|
||||
val customView = inflater.inflate(R.layout.view_snackbar_max_amount, parent, false) as MaxAmountSnackbarView
|
||||
|
|
@ -57,14 +57,13 @@ class MaxAmountSnackbar(
|
|||
} while (view != null)
|
||||
return fallback
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class MaxAmountSnackbarView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr), ContentViewCallback {
|
||||
|
||||
init {
|
||||
|
|
@ -72,10 +71,9 @@ class MaxAmountSnackbarView @JvmOverloads constructor(
|
|||
clipToPadding = false
|
||||
}
|
||||
|
||||
|
||||
override fun animateContentIn(delay: Int, duration: Int) {
|
||||
}
|
||||
|
||||
override fun animateContentOut(delay: Int, duration: Int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,13 +8,23 @@ import java.util.regex.Pattern
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DecimalDigitsInputFilter(
|
||||
digitsBeforeDecimal: Int,
|
||||
digitsAfterDecimal: Int,
|
||||
private val decimalSeparator: String
|
||||
digitsBeforeDecimal: Int,
|
||||
digitsAfterDecimal: Int,
|
||||
private val decimalSeparator: String,
|
||||
) : InputFilter {
|
||||
private val pattern: Pattern = Pattern.compile("(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?")
|
||||
private val pattern: Pattern = Pattern.compile(
|
||||
"(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})" +
|
||||
"((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?",
|
||||
)
|
||||
|
||||
override fun filter(source: CharSequence, sourceStart: Int, sourceEnd: Int, destination: Spanned, destinationStart: Int, destinationEnd: Int): CharSequence? {
|
||||
override fun filter(
|
||||
source: CharSequence,
|
||||
sourceStart: Int,
|
||||
sourceEnd: Int,
|
||||
destination: Spanned,
|
||||
destinationStart: Int,
|
||||
destinationEnd: Int,
|
||||
): CharSequence? {
|
||||
val destString = destination.toString()
|
||||
val prefix = destString.substring(0, destinationStart)
|
||||
val suffix = destString.substring(destinationEnd, destString.length)
|
||||
|
|
@ -47,4 +57,4 @@ class DecimalDigitsInputFilter(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,9 +16,9 @@ interface Truncate {
|
|||
companion object {
|
||||
fun create(type: TruncateType): Truncate {
|
||||
return when (type) {
|
||||
TruncateType.START -> TruncateStart()
|
||||
TruncateType.MIDDLE -> TruncateMiddle()
|
||||
TruncateType.END -> TruncateEnd()
|
||||
TruncateType.START -> TruncateStart()
|
||||
TruncateType.MIDDLE -> TruncateMiddle()
|
||||
TruncateType.END -> TruncateEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -127,10 +127,10 @@ fun TextView.truncateWith(text: String, type: TruncateType, with: String = "..."
|
|||
}
|
||||
|
||||
fun TextView.truncateStartWith(text: String, with: String = "..."): String =
|
||||
this.truncateWith(text, TruncateType.START, with)
|
||||
this.truncateWith(text, TruncateType.START, with)
|
||||
|
||||
fun TextView.truncateMiddleWith(text: String, with: String = "..."): String =
|
||||
this.truncateWith(text, TruncateType.MIDDLE, with)
|
||||
this.truncateWith(text, TruncateType.MIDDLE, with)
|
||||
|
||||
fun TextView.truncateEndWith(text: String, with: String = "..."): String =
|
||||
this.truncateWith(text, TruncateType.END, with)
|
||||
this.truncateWith(text, TruncateType.END, with)
|
||||
|
|
@ -38,6 +38,7 @@ open class IndeterminateProgressButtonWidget(
|
|||
when (progressState) {
|
||||
ProgressState.Done, ProgressState.Error -> switchToNone()
|
||||
ProgressState.Loading -> switchToProgress()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.wallet.R
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
class RefreshBalanceWidget(
|
||||
private val root: ViewGroup,
|
||||
) : ViewStateWidget {
|
||||
|
|
@ -57,6 +58,7 @@ class RefreshBalanceWidget(
|
|||
viewSwitcher.showNext()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun animateState(state: ProgressState) {
|
||||
when (state) {
|
||||
ProgressState.Done, ProgressState.Error -> {
|
||||
|
|
@ -65,15 +67,25 @@ class RefreshBalanceWidget(
|
|||
}
|
||||
ProgressState.Loading -> {
|
||||
progressViewAnimation = RotateAnimation(
|
||||
0f, 360f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
/* fromDegrees = */
|
||||
0f,
|
||||
/* toDegrees = */
|
||||
360f,
|
||||
/* pivotXType = */
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
/* pivotXValue = */
|
||||
0.5f,
|
||||
/* pivotYType = */
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
/* pivotYValue = */
|
||||
0.5f,
|
||||
)
|
||||
progressViewAnimation?.duration = 700
|
||||
progressViewAnimation?.interpolator = AccelerateInterpolator()
|
||||
progressViewAnimation?.repeatCount = -1
|
||||
progressViewAnimation?.let { arrowView.startAnimation(it) }
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,14 +105,19 @@ class RefreshBalanceWidget(
|
|||
private fun getState(): ProgressState = if (isArrowRefreshActive()) ProgressState.Done else ProgressState.Loading
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class ShowAnimation : AnimationSet(true) {
|
||||
init {
|
||||
addAnimation(
|
||||
ScaleAnimation(
|
||||
0f, 1f,
|
||||
0f, 1f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
0f,
|
||||
1f,
|
||||
0f,
|
||||
1f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
),
|
||||
)
|
||||
addAnimation(AlphaAnimation(0f, 1f))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.common.toggleWidget
|
|||
|
||||
import android.view.View
|
||||
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,33 +1,13 @@
|
|||
package com.tangem.tap.common.transitions
|
||||
|
||||
import androidx.transition.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class FrontCardEnterTransition : TransitionSet() {
|
||||
init {
|
||||
ordering = ORDERING_TOGETHER;
|
||||
addTransition(Fade())
|
||||
addTransition(ChangeImageTransform()) // изменения внутри ImageView
|
||||
addTransition(ChangeTransform()) // изменение размеров, углов накона
|
||||
addTransition(ChangeBounds()) // изменение положения
|
||||
}
|
||||
}
|
||||
|
||||
class FrontCardExitTransition : TransitionSet() {
|
||||
init {
|
||||
ordering = ORDERING_TOGETHER;
|
||||
addTransition(Fade())
|
||||
addTransition(ChangeImageTransform())
|
||||
addTransition(ChangeTransform())
|
||||
addTransition(ChangeBounds())
|
||||
}
|
||||
}
|
||||
import androidx.transition.ChangeBounds
|
||||
import androidx.transition.ChangeTransform
|
||||
import androidx.transition.Fade
|
||||
import androidx.transition.TransitionSet
|
||||
|
||||
class HomeToOnboardingTransition : TransitionSet() {
|
||||
init {
|
||||
ordering = ORDERING_TOGETHER;
|
||||
ordering = ORDERING_TOGETHER
|
||||
addTransition(Fade())
|
||||
addTransition(ChangeTransform())
|
||||
addTransition(ChangeBounds())
|
||||
|
|
@ -36,7 +16,7 @@ class HomeToOnboardingTransition : TransitionSet() {
|
|||
|
||||
class InternalNoteLayoutTransition : TransitionSet() {
|
||||
init {
|
||||
ordering = ORDERING_TOGETHER;
|
||||
ordering = ORDERING_TOGETHER
|
||||
addTransition(ChangeTransform())
|
||||
addTransition(ChangeBounds())
|
||||
addTransition(Fade())
|
||||
|
|
|
|||
|
|
@ -6,54 +6,49 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class SimpleAlertDialog {
|
||||
companion object {
|
||||
fun create(
|
||||
titleRes: Int? = null,
|
||||
messageRes: Int? = null,
|
||||
title: String? = null,
|
||||
message: String? = null,
|
||||
primaryButtonRes: Int = R.string.common_ok,
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
return SimpleCancelableAlertDialog.create(
|
||||
titleRes = titleRes,
|
||||
messageRes = messageRes,
|
||||
title = title,
|
||||
message = message,
|
||||
primaryButtonRes = primaryButtonRes,
|
||||
secondaryButtonRes = null,
|
||||
context = context
|
||||
)
|
||||
}
|
||||
object SimpleAlertDialog {
|
||||
fun create(
|
||||
titleRes: Int? = null,
|
||||
messageRes: Int? = null,
|
||||
title: String? = null,
|
||||
message: String? = null,
|
||||
primaryButtonRes: Int = R.string.common_ok,
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
return SimpleCancelableAlertDialog.create(
|
||||
titleRes = titleRes,
|
||||
messageRes = messageRes,
|
||||
title = title,
|
||||
message = message,
|
||||
primaryButtonRes = primaryButtonRes,
|
||||
secondaryButtonRes = null,
|
||||
context = context,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleCancelableAlertDialog {
|
||||
companion object {
|
||||
fun create(
|
||||
titleRes: Int? = null,
|
||||
messageRes: Int? = null,
|
||||
title: String? = null,
|
||||
message: String? = null,
|
||||
primaryButtonRes: Int = R.string.common_ok,
|
||||
secondaryButtonRes: Int? = R.string.common_cancel,
|
||||
primaryButtonAction: () -> Unit = {},
|
||||
secondaryButtonAction: () -> Unit = {},
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(titleRes?.let { context.getString(it) } ?: title )
|
||||
setMessage(messageRes?.let { context.getString(it) } ?: message)
|
||||
setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() }
|
||||
if (secondaryButtonRes != null) {
|
||||
setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction()}
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
object SimpleCancelableAlertDialog {
|
||||
fun create(
|
||||
titleRes: Int? = null,
|
||||
messageRes: Int? = null,
|
||||
title: String? = null,
|
||||
message: String? = null,
|
||||
primaryButtonRes: Int = R.string.common_ok,
|
||||
secondaryButtonRes: Int? = R.string.common_cancel,
|
||||
primaryButtonAction: () -> Unit = {},
|
||||
secondaryButtonAction: () -> Unit = {},
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(titleRes?.let { context.getString(it) } ?: title)
|
||||
setMessage(messageRes?.let { context.getString(it) } ?: message)
|
||||
setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() }
|
||||
if (secondaryButtonRes != null) {
|
||||
setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction() }
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,11 @@ import java.util.*
|
|||
|
||||
class PayIdManager {
|
||||
|
||||
suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result<VerifyPayIdResponse> = withContext(Dispatchers.IO) {
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun verifyPayId(
|
||||
payId: String,
|
||||
blockchain: Blockchain,
|
||||
): Result<VerifyPayIdResponse> = withContext(Dispatchers.IO) {
|
||||
val splitPayId = payId.split("\$")
|
||||
val user = splitPayId[0]
|
||||
val baseUrl = "https://${splitPayId[1]}/"
|
||||
|
|
@ -22,25 +26,26 @@ class PayIdManager {
|
|||
Blockchain.XRP -> "XRPL"
|
||||
Blockchain.RSK -> "RSK"
|
||||
else -> this.currency
|
||||
}.toLowerCase()
|
||||
}.lowercase(Locale.getDefault())
|
||||
}
|
||||
|
||||
companion object {
|
||||
val payIdRegExp = "^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$".toRegex()
|
||||
private val payIdRegExp = ("^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9]" +
|
||||
"(?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$").toRegex()
|
||||
|
||||
val payIdSupported: EnumSet<Blockchain> = EnumSet.of(
|
||||
Blockchain.XRP,
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.Bitcoin,
|
||||
Blockchain.Litecoin,
|
||||
Blockchain.Stellar,
|
||||
Blockchain.Cardano,
|
||||
Blockchain.CardanoShelley,
|
||||
Blockchain.Ducatus,
|
||||
Blockchain.BitcoinCash,
|
||||
Blockchain.Binance,
|
||||
Blockchain.RSK,
|
||||
Blockchain.Tezos
|
||||
Blockchain.XRP,
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.Bitcoin,
|
||||
Blockchain.Litecoin,
|
||||
Blockchain.Stellar,
|
||||
Blockchain.Cardano,
|
||||
Blockchain.CardanoShelley,
|
||||
Blockchain.Ducatus,
|
||||
Blockchain.BitcoinCash,
|
||||
Blockchain.Binance,
|
||||
Blockchain.RSK,
|
||||
Blockchain.Tezos,
|
||||
)
|
||||
|
||||
fun isPayId(value: String?): Boolean = value?.contains(payIdRegExp) ?: false
|
||||
|
|
|
|||
|
|
@ -1,48 +1,49 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.common.ThrottlerWithValues
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
//TODO: refactoring: move to domain
|
||||
class RatesRepository {
|
||||
|
||||
private val tangemTechService: TangemTechService
|
||||
get() = store.state.domainNetworks.tangemTechService
|
||||
|
||||
class RatesRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
private val throttler = ThrottlerWithValues<Currency, Result<BigDecimal>?>(60000)
|
||||
|
||||
suspend fun loadFiatRate(currencyId: String, coinsList: List<Currency>): Result<RatesResult> {
|
||||
// get and submit previous result of equivalents.
|
||||
val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map {
|
||||
Pair(it, throttler.geValue(it))
|
||||
}
|
||||
|
||||
val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) }
|
||||
val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct()
|
||||
if (coinIds.isEmpty()) return handleFiatRatesResult(throttledResult.toMap())
|
||||
|
||||
return when (val result = tangemTechService.rates(currencyId, coinIds)) {
|
||||
is Result.Success -> {
|
||||
val ratesResultList: Map<String, Result<BigDecimal>> = result.data.rates.mapValues {
|
||||
Result.Success(it.value.toBigDecimal())
|
||||
}
|
||||
val updatedCurrencies = throttledResult.toMap().toMutableMap()
|
||||
coinsList.forEach { currency ->
|
||||
ratesResultList[currency.coinId]?.let {
|
||||
updatedCurrencies[currency] = it
|
||||
throttler.updateThrottlingTo(currency)
|
||||
throttler.setValue(currency, it)
|
||||
}
|
||||
}
|
||||
handleFiatRatesResult(updatedCurrencies)
|
||||
suspend fun loadFiatRate(currencyId: String, coinsList: List<Currency>): Result<RatesResult> =
|
||||
withContext(dispatchers.io) {
|
||||
// get and submit previous result of equivalents.
|
||||
val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map {
|
||||
Pair(it, throttler.geValue(it))
|
||||
}
|
||||
is Result.Failure -> Result.Failure(result.error)
|
||||
|
||||
val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) }
|
||||
val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct()
|
||||
if (coinIds.isEmpty()) return@withContext handleFiatRatesResult(throttledResult.toMap())
|
||||
|
||||
runCatching { tangemTechApi.getRates(currencyId.lowercase(), coinIds.joinToString(",")) }
|
||||
.onSuccess { response ->
|
||||
val ratesResultList: Map<String, Result<BigDecimal>> = response.rates.mapValues {
|
||||
Result.Success(it.value.toBigDecimal())
|
||||
}
|
||||
val updatedCurrencies = throttledResult.toMap().toMutableMap()
|
||||
coinsList.forEach { currency ->
|
||||
ratesResultList[currency.coinId]?.let {
|
||||
updatedCurrencies[currency] = it
|
||||
throttler.updateThrottlingTo(currency)
|
||||
throttler.setValue(currency, it)
|
||||
}
|
||||
}
|
||||
return@withContext handleFiatRatesResult(updatedCurrencies)
|
||||
}
|
||||
.onFailure { Result.Failure(it) }
|
||||
|
||||
error("Unreachable code because runCatching must return result")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFiatRatesResult(rates: Map<Currency, Result<BigDecimal>?>): Result.Success<RatesResult> {
|
||||
val success = mutableMapOf<Currency, BigDecimal>()
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.common.extensions.ByteArrayKey
|
|||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.common.map
|
||||
import com.tangem.common.usersCode.UserCodeRepository
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.operations.CommandResponse
|
||||
|
|
@ -30,7 +31,6 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
|||
import com.tangem.operations.pins.CheckUserCodesCommand
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.operations.pins.SetUserCodeCommand
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
||||
|
|
@ -85,7 +85,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
scanResponse: ScanResponse,
|
||||
): CompletionResult<CreateProductWalletTaskResponse> {
|
||||
return runTaskAsync(
|
||||
CreateProductWalletTask(scanResponse.productType),
|
||||
CreateProductWalletTask(scanResponse.cardTypesResolver),
|
||||
scanResponse.card.cardId,
|
||||
Message(context.getString(R.string.initial_message_create_wallet_body)),
|
||||
)
|
||||
|
|
@ -211,11 +211,12 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
return withContext(Dispatchers.Main) { result }
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) {
|
||||
tangemSdk.config.cardIdDisplayFormat = when {
|
||||
scanResponse == null -> CardIdDisplayFormat.Full
|
||||
scanResponse.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4)
|
||||
scanResponse.isSaltPay() -> CardIdDisplayFormat.None
|
||||
scanResponse.cardTypesResolver.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4)
|
||||
scanResponse.cardTypesResolver.isSaltPay() -> CardIdDisplayFormat.None
|
||||
else -> CardIdDisplayFormat.Full
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.domain
|
|||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechError
|
||||
import com.tangem.wallet.R
|
||||
|
||||
interface TapErrors
|
||||
|
|
@ -18,21 +17,20 @@ interface MultiMessageError : TapErrors {
|
|||
|
||||
sealed class TapError(
|
||||
@StringRes val messageResource: Int,
|
||||
override val args: List<Any>? = null
|
||||
override val args: List<Any>? = null,
|
||||
) : Throwable(), TapErrors, ArgError {
|
||||
|
||||
object UnknownError : TapError(R.string.send_error_unknown)
|
||||
open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage))
|
||||
object ScanCardError : TapError(R.string.scan_card_error)
|
||||
object PayIdAlreadyCreated : TapError(R.string.wallet_create_payid_error_already_created)
|
||||
object PayIdCreatingError : TapError(R.string.wallet_create_payid_error_message)
|
||||
object PayIdEmptyField : TapError(R.string.wallet_create_payid_empty)
|
||||
object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle)
|
||||
object NoInternetConnection : TapError(R.string.wallet_notification_no_internet)
|
||||
object InsufficientBalance : TapError(R.string.send_error_insufficient_balance)
|
||||
object BlockchainInternalError : TapError(R.string.send_error_blockchain_internal)
|
||||
object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance)
|
||||
data class AmountLowerExistentialDeposit(override val args: List<Any>) : TapError(R.string.send_error_minimum_balance_format)
|
||||
data class AmountLowerExistentialDeposit(
|
||||
override val args: List<Any>,
|
||||
) : TapError(R.string.send_error_minimum_balance_format)
|
||||
|
||||
object FeeExceedsBalance : TapError(R.string.send_validation_invalid_fee)
|
||||
object TotalExceedsBalance : TapError(R.string.send_validation_invalid_total)
|
||||
object InvalidAmountValue : TapError(R.string.send_validation_invalid_amount)
|
||||
|
|
@ -49,7 +47,6 @@ sealed class TapError(
|
|||
object CreationError : CustomError("Can't create wallet manager")
|
||||
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)
|
||||
class InternalError(message: String) : CustomError(message)
|
||||
object BlockchainIsUnreachable : TapError(R.string.wallet_balance_blockchain_unreachable)
|
||||
object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later)
|
||||
}
|
||||
|
||||
|
|
@ -78,13 +75,4 @@ fun TapErrors.assembleErrors(): MutableList<Pair<Int, List<Any>?>> {
|
|||
is TapError -> idList.add(Pair(this.messageResource, this.args))
|
||||
}
|
||||
return idList
|
||||
}
|
||||
|
||||
fun TangemTechError.toTapError(): TapError {
|
||||
return when (this.code) {
|
||||
404 -> NoDataError(this.description)
|
||||
else -> TapError.CustomError(customMessage = this.description)
|
||||
}
|
||||
}
|
||||
|
||||
class NoDataError(message: String) : TapError.CustomError(customMessage = message)
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@ import com.tangem.common.doOnSuccess
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.ThrottlerWithValues
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
|
|
@ -20,8 +19,7 @@ import com.tangem.operations.attestation.Attestation
|
|||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
|
|
@ -38,6 +36,7 @@ import com.tangem.tap.store
|
|||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userTokensRepository
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -46,7 +45,14 @@ import timber.log.Timber
|
|||
class TapWalletManager {
|
||||
val walletManagerFactory: WalletManagerFactory
|
||||
by lazy { WalletManagerFactory(blockchainSdkConfig) }
|
||||
val rates: RatesRepository = RatesRepository()
|
||||
|
||||
// TODO("After adding DI") get dependencies by DI
|
||||
val rates: RatesRepository by lazy {
|
||||
RatesRepository(
|
||||
tangemTechApi = store.state.domainNetworks.tangemTechService.api,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
)
|
||||
}
|
||||
|
||||
private val blockchainSdkConfig by lazy {
|
||||
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
||||
|
|
@ -157,12 +163,16 @@ class TapWalletManager {
|
|||
store.dispatch(WalletConnectAction.ResetState)
|
||||
store.dispatch(GlobalAction.SaveScanResponse(data))
|
||||
store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard))
|
||||
store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.SetIsMultiwalletAllowed(
|
||||
data.cardTypesResolver.isMultiwalletAllowed(),
|
||||
),
|
||||
)
|
||||
store.dispatch(WalletConnectAction.RestoreSessions(data))
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.ShowWalletBackupWarning(
|
||||
show = data.card.settings.isBackupAllowed
|
||||
&& data.card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
show = data.card.settings.isBackupAllowed &&
|
||||
data.card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
),
|
||||
)
|
||||
loadData(data)
|
||||
|
|
@ -171,18 +181,18 @@ class TapWalletManager {
|
|||
|
||||
fun updateConfigManager(data: ScanResponse) {
|
||||
val configManager = store.state.globalState.configManager
|
||||
val blockchain = data.getBlockchain()
|
||||
if (data.card.isStart2Coin) {
|
||||
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.turnOff(ConfigManager.isTopUpEnabled)
|
||||
} else if (blockchain == Blockchain.Bitcoin
|
||||
|| data.walletData?.blockchain == Blockchain.Bitcoin.id
|
||||
val blockchain = data.cardTypesResolver.getBlockchain()
|
||||
if (data.cardTypesResolver.isStart2Coin()) {
|
||||
configManager?.turnOff(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED)
|
||||
configManager?.turnOff(ConfigManager.IS_TOP_UP_ENABLED)
|
||||
} else if (blockchain == Blockchain.Bitcoin ||
|
||||
data.walletData?.blockchain == Blockchain.Bitcoin.id
|
||||
) {
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED)
|
||||
configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED)
|
||||
} else {
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED)
|
||||
configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,7 +203,7 @@ class TapWalletManager {
|
|||
return
|
||||
}
|
||||
|
||||
if (data.card.isMultiwalletAllowed) {
|
||||
if (data.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
dispatchOnMain(WalletAction.MultiWallet.ScheduleCheckForMissingDerivation)
|
||||
loadMultiWalletData(data)
|
||||
} else {
|
||||
|
|
@ -221,7 +231,7 @@ class TapWalletManager {
|
|||
}
|
||||
|
||||
private suspend fun loadSingleWalletData(data: ScanResponse) {
|
||||
val blockchain = data.getBlockchain()
|
||||
val blockchain = data.cardTypesResolver.getBlockchain()
|
||||
val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
|
||||
|
||||
if (blockchain != Blockchain.Unknown && primaryWalletManager != null) {
|
||||
|
|
@ -231,7 +241,7 @@ class TapWalletManager {
|
|||
blockchains = listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)),
|
||||
walletManagers = listOf(primaryWalletManager),
|
||||
),
|
||||
data.getPrimaryToken()?.let {
|
||||
data.cardTypesResolver.getPrimaryToken()?.let {
|
||||
primaryWalletManager.addToken(it)
|
||||
primaryWalletManager.wallet.setAmount(Amount(it))
|
||||
WalletAction.MultiWallet.AddToken(it, blockchainNetwork, false)
|
||||
|
|
@ -269,7 +279,7 @@ class TapWalletManager {
|
|||
}
|
||||
|
||||
suspend fun reloadData(data: ScanResponse) {
|
||||
if (data.card.isMultiwalletAllowed) {
|
||||
if (data.cardTypesResolver.isMultiwalletAllowed()) {
|
||||
loadUserCurrencies(data, walletManagerFactory)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
|
|
@ -289,10 +299,11 @@ class TapWalletManager {
|
|||
private fun getActionIfUnknownBlockchainOrEmptyWallet(data: ScanResponse): WalletAction? {
|
||||
return when {
|
||||
// check order is important
|
||||
data.isTangemTwins() && !data.twinsIsTwinned() -> {
|
||||
data.cardTypesResolver.isTangemTwins() && !data.twinsIsTwinned() -> {
|
||||
WalletAction.EmptyWallet
|
||||
}
|
||||
data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
|
||||
data.cardTypesResolver.getBlockchain() == Blockchain.Unknown &&
|
||||
!data.cardTypesResolver.isMultiwalletAllowed() -> {
|
||||
WalletAction.LoadData.Failure(TapError.UnknownBlockchain)
|
||||
}
|
||||
data.isDemoCard() -> {
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.tap.domain.configurable
|
||||
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Loader<T> {
|
||||
fun load(onComplete: (T) -> Unit)
|
||||
|
||||
companion object {
|
||||
const val featuresName = "features_${BuildConfig.ENVIRONMENT}"
|
||||
const val configValuesName = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}"
|
||||
const val warnings = "warnings_${BuildConfig.ENVIRONMENT}"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
package com.tangem.tap.domain.configurable.config
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.BlockchairCredentials
|
||||
import com.tangem.blockchain.common.GetBlockCredentials
|
||||
import com.tangem.blockchain.common.NowNodeCredentials
|
||||
import com.tangem.blockchain.common.QuickNodeCredentials
|
||||
import com.tangem.tap.common.shop.shopify.ShopifyShop
|
||||
import com.tangem.tap.common.zendesk.ZendeskConfig
|
||||
import com.tangem.tap.domain.configurable.Loader
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class Config(
|
||||
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
|
||||
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
|
||||
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
|
||||
val mercuryoWidgetId: String = "",
|
||||
val mercuryoSecret: String = "",
|
||||
val appsFlyerDevKey: String = "",
|
||||
val amplitudeApiKey: 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 shopify: ShopifyShop? = null,
|
||||
val zendesk: ZendeskConfig? = null,
|
||||
val saltPayConfig: SaltPayConfig? = null,
|
||||
)
|
||||
|
||||
class ConfigManager {
|
||||
|
||||
var config: Config = Config()
|
||||
private set
|
||||
|
||||
private var defaultConfig = Config()
|
||||
|
||||
fun load(configLoader: Loader<ConfigModel>, onComplete: ((config: Config) -> Unit)? = null) {
|
||||
configLoader.load { configModel ->
|
||||
setupFeature(configModel.features)
|
||||
setupKey(configModel.configValues)
|
||||
onComplete?.invoke(config)
|
||||
}
|
||||
}
|
||||
|
||||
fun turnOff(name: String) {
|
||||
when (name) {
|
||||
isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = false)
|
||||
isTopUpEnabled -> config = config.copy(isTopUpEnabled = false)
|
||||
isCreatingTwinCardsAllowed -> config = config.copy(isCreatingTwinCardsAllowed = false)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetToDefault(name: String) {
|
||||
when (name) {
|
||||
isSendingToPayIdEnabled -> config =
|
||||
config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
|
||||
isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
|
||||
isCreatingTwinCardsAllowed -> config =
|
||||
config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFeature(featureModel: FeatureModel?) {
|
||||
val model = featureModel ?: return
|
||||
|
||||
config = config.copy(
|
||||
isTopUpEnabled = model.isTopUpEnabled,
|
||||
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
|
||||
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed,
|
||||
)
|
||||
defaultConfig = defaultConfig.copy(
|
||||
isTopUpEnabled = model.isTopUpEnabled,
|
||||
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
|
||||
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed,
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupKey(configValues: ConfigValueModel?) {
|
||||
val values = configValues ?: return
|
||||
config = config.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
moonPayApiKey = values.moonPayApiKey,
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey,
|
||||
mercuryoWidgetId = values.mercuryoWidgetId,
|
||||
mercuryoSecret = values.mercuryoSecret,
|
||||
blockchainSdkConfig = BlockchainSdkConfig(
|
||||
blockchairCredentials = BlockchairCredentials(
|
||||
apiKey = values.blockchairApiKeys,
|
||||
authToken = values.blockchairAuthorizationToken,
|
||||
),
|
||||
blockcypherTokens = values.blockcypherTokens,
|
||||
quickNodeSolanaCredentials = QuickNodeCredentials(
|
||||
apiKey = values.quiknodeApiKey,
|
||||
subdomain = values.quiknodeSubdomain,
|
||||
),
|
||||
quickNodeBscCredentials = QuickNodeCredentials(
|
||||
apiKey = values.bscQuiknodeApiKey,
|
||||
subdomain = values.bscQuiknodeSubdomain,
|
||||
),
|
||||
nowNodeCredentials = NowNodeCredentials(values.nowNodesApiKey),
|
||||
getBlockCredentials = GetBlockCredentials(values.getBlockApiKey),
|
||||
infuraProjectId = values.infuraProjectId,
|
||||
tronGridApiKey = values.tronGridApiKey,
|
||||
saltPayAuthToken = values.saltPay.credentials.token,
|
||||
),
|
||||
appsFlyerDevKey = values.appsFlyer.appsFlyerDevKey,
|
||||
amplitudeApiKey = values.amplitudeApiKey,
|
||||
shopify = values.shopifyShop,
|
||||
zendesk = values.zendesk,
|
||||
saltPayConfig = values.saltPay,
|
||||
)
|
||||
defaultConfig = defaultConfig.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
moonPayApiKey = values.moonPayApiKey,
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey,
|
||||
mercuryoWidgetId = values.mercuryoWidgetId,
|
||||
mercuryoSecret = values.mercuryoSecret,
|
||||
blockchainSdkConfig = BlockchainSdkConfig(
|
||||
blockchairCredentials = BlockchairCredentials(
|
||||
apiKey = values.blockchairApiKeys,
|
||||
authToken = values.blockchairAuthorizationToken,
|
||||
),
|
||||
blockcypherTokens = values.blockcypherTokens,
|
||||
quickNodeSolanaCredentials = QuickNodeCredentials(
|
||||
apiKey = values.quiknodeApiKey,
|
||||
subdomain = values.quiknodeSubdomain,
|
||||
),
|
||||
quickNodeBscCredentials = QuickNodeCredentials(
|
||||
apiKey = values.bscQuiknodeApiKey,
|
||||
subdomain = values.bscQuiknodeSubdomain,
|
||||
),
|
||||
nowNodeCredentials = NowNodeCredentials(values.nowNodesApiKey),
|
||||
getBlockCredentials = GetBlockCredentials(values.getBlockApiKey),
|
||||
infuraProjectId = values.infuraProjectId,
|
||||
saltPayAuthToken = values.saltPay.credentials.token,
|
||||
),
|
||||
appsFlyerDevKey = values.appsFlyer.appsFlyerDevKey,
|
||||
amplitudeApiKey = values.amplitudeApiKey,
|
||||
shopify = values.shopifyShop,
|
||||
zendesk = values.zendesk,
|
||||
saltPayConfig = values.saltPay,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val isSendingToPayIdEnabled = "isSendingToPayIdEnabled"
|
||||
const val isCreatingTwinCardsAllowed = "isCreatingTwinCardsAllowed"
|
||||
const val isTopUpEnabled = "isTopUpEnabled"
|
||||
}
|
||||
}
|
||||
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