Updated on 2026-08-14
This commit is contained in:
commit
fb7aa02b29
155 changed files with 3893 additions and 2754 deletions
|
|
@ -3,11 +3,7 @@ apply plugin: 'kotlin-android'
|
|||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
|
||||
ext.configVar = [
|
||||
configEnvironment: "CONFIG_ENVIRONMENT",
|
||||
testActionEnabled: "TEST_ACTION_ENABLED",
|
||||
]
|
||||
apply from: '../dependencies.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
|
|
@ -19,7 +15,8 @@ android {
|
|||
targetSdkVersion 31
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
buildConfigField 'Boolean', configVar.testActionEnabled, 'false'
|
||||
buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false'
|
||||
buildConfigField 'Boolean', environmentConfig.logEnabled, 'false'
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
|
|
@ -31,8 +28,9 @@ android {
|
|||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
buildConfigField 'String', configVar.configEnvironment, '\"prod\"'
|
||||
buildConfigField 'Boolean', configVar.testActionEnabled, 'false'
|
||||
buildConfigField 'String', environmentConfig.environment, '\"prod\"'
|
||||
buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false'
|
||||
buildConfigField 'Boolean', environmentConfig.logEnabled, 'false'
|
||||
}
|
||||
debug {
|
||||
debuggable true
|
||||
|
|
@ -42,16 +40,18 @@ android {
|
|||
firebaseCrashlytics {
|
||||
mappingFileUploadEnabled false
|
||||
}
|
||||
buildConfigField 'String', configVar.configEnvironment, '\"dev\"'
|
||||
buildConfigField 'Boolean', configVar.testActionEnabled, 'true'
|
||||
buildConfigField 'String', environmentConfig.environment, '\"dev\"'
|
||||
buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'true'
|
||||
buildConfigField 'Boolean', environmentConfig.logEnabled, 'true'
|
||||
}
|
||||
debug_beta {
|
||||
initWith release
|
||||
debuggable false
|
||||
versionNameSuffix "-beta"
|
||||
applicationIdSuffix ".debug"
|
||||
buildConfigField 'String', configVar.configEnvironment, '\"prod\"'
|
||||
buildConfigField 'Boolean', configVar.testActionEnabled, 'false'
|
||||
buildConfigField 'String', environmentConfig.environment, '\"prod\"'
|
||||
buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false'
|
||||
buildConfigField 'Boolean', environmentConfig.logEnabled, 'false'
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -93,7 +93,8 @@ dependencies {
|
|||
implementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'com.tangem:blockchain:develop-77'
|
||||
implementation 'com.tangem:blockchain:develop-85'
|
||||
// implementation 'com.tangem:blockchain:0.0.1'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-142'
|
||||
|
||||
|
|
@ -169,6 +170,8 @@ dependencies {
|
|||
implementation 'androidx.compose.ui:ui-tooling:1.1.1'
|
||||
implementation "com.google.accompanist:accompanist-appcompat-theme:0.23.0"
|
||||
|
||||
implementation "com.github.skydoves:androidveil:1.1.2"
|
||||
|
||||
implementation("io.coil-kt:coil:2.0.0-rc01")
|
||||
implementation("io.coil-kt:coil-compose:2.0.0-rc01")
|
||||
|
||||
|
|
@ -179,4 +182,4 @@ dependencies {
|
|||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 64ae04d2086e5a605dd4da3cba4af32a60d46c79
|
||||
Subproject commit 7f058ec409b4eaaf8688ecd4bf944ef8d58d3564
|
||||
|
|
@ -1,371 +1,428 @@
|
|||
{
|
||||
"coins" : [
|
||||
{
|
||||
"id" : "matic-token",
|
||||
"symbol" : "MATIC",
|
||||
"name" : "Polygon",
|
||||
"networks" : [
|
||||
"coins" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x0000000000000000000000000000000000001010",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Dai Stablecoin-DAI",
|
||||
"symbol" : "DAI",
|
||||
"name" : "Dai Stablecoin",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xcB1e72786A6eb3b44C2a2429e317c8a2462CFeb1",
|
||||
"decimalCount" : 18
|
||||
"id" : "matic-token",
|
||||
"symbol" : "MATIC",
|
||||
"name" : "Polygon",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x0000000000000000000000000000000000001010",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Dummy ERC20-DERC20",
|
||||
"symbol" : "DERC20",
|
||||
"name" : "Dummy ERC20",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Ether-ETH",
|
||||
"symbol" : "ETH",
|
||||
"name" : "Ether",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x714550C2C1Ea08688607D86ed8EeF4f5E4F22323",
|
||||
"decimalCount" : 18
|
||||
"id" : "Dai Stablecoin-DAI",
|
||||
"symbol" : "DAI",
|
||||
"name" : "Dai Stablecoin",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xcB1e72786A6eb3b44C2a2429e317c8a2462CFeb1",
|
||||
"decimalCount" : 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867",
|
||||
"decimalCount" : 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x8a9424745056eb399fd19a0ec26a14316684e274",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Test Token-TST",
|
||||
"symbol" : "TST",
|
||||
"name" : "Test Token",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x2d7882bedcbfddce29ba99965dd3cdf7fcb10a1e",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Tether USD-USDT",
|
||||
"symbol" : "USDT",
|
||||
"name" : "Tether USD",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x3813e82e6f7098b9583FC0F33a962D02018B6803",
|
||||
"decimalCount" : 6
|
||||
"id" : "Dummy ERC20-DERC20",
|
||||
"symbol" : "DERC20",
|
||||
"name" : "Dummy ERC20",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Wrapped Ether-WETH",
|
||||
"symbol" : "WETH",
|
||||
"name" : "Wrapped Ether",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Wrapped Matic-WMATIC",
|
||||
"symbol" : "WMATIC",
|
||||
"name" : "Wrapped Matic",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xd0A1E359811322d97991E03f863a0C30C2cF029C",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "solana",
|
||||
"symbol" : "SOL",
|
||||
"name" : "Solana",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "solana/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Tangem Coin A-TCA",
|
||||
"symbol" : "TCA",
|
||||
"name" : "Tangem Coin A",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "solana/test",
|
||||
"contractAddress" : "22PTNbX31Zuztd6nD82fC8nQdT2hUfWv9XWXKuDkrFqR",
|
||||
"decimalCount" : 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Tangem Coin B-TCB",
|
||||
"symbol" : "TCB",
|
||||
"name" : "Tangem Coin B",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "solana/test",
|
||||
"contractAddress" : "HmSghNPg6KCk711YJA92aPejt8auyFkvmaED6jbHfUs4",
|
||||
"decimalCount" : 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "binancecoin",
|
||||
"symbol" : "BNB",
|
||||
"name" : "Binance",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "binancecoin/test"
|
||||
"id" : "Ether-ETH",
|
||||
"symbol" : "ETH",
|
||||
"name" : "Ether",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x714550C2C1Ea08688607D86ed8EeF4f5E4F22323",
|
||||
"decimalCount" : 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Hemster - 452-HEM",
|
||||
"symbol" : "HEM",
|
||||
"name" : "Hemster - 452",
|
||||
"networks" : [
|
||||
"id" : "Test Token-TST",
|
||||
"symbol" : "TST",
|
||||
"name" : "Test Token",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x2d7882bedcbfddce29ba99965dd3cdf7fcb10a1e",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "binancecoin/test",
|
||||
"contractAddress" : "HEM-452",
|
||||
"decimalCount" : 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Binance-Peg BTCB Token-BTCB",
|
||||
"symbol" : "BTCB",
|
||||
"name" : "Binance-Peg BTCB Token",
|
||||
"networks" : [
|
||||
"id" : "tether",
|
||||
"symbol" : "USDT",
|
||||
"name" : "Tether USD",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0x3813e82e6f7098b9583FC0F33a962D02018B6803",
|
||||
"decimalCount" : 6
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd",
|
||||
"decimalCount" : 18
|
||||
},
|
||||
{
|
||||
"networkId" : "tron/test",
|
||||
"contractAddress" : "TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj",
|
||||
"decimalCount" : 6
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x6ce8da28e2f864420840cf74474eff5fd80e65b8",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Binance-Peg BUSD Token-BUSD",
|
||||
"symbol" : "BUSD",
|
||||
"name" : "Binance-Peg BUSD Token",
|
||||
"networks" : [
|
||||
"id" : "just-gov",
|
||||
"symbol" : "JST",
|
||||
"name" : "JUST GOV",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "tron/test",
|
||||
"contractAddress" : "TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Binance-Peg USDC Token-USDC",
|
||||
"symbol" : "USDC",
|
||||
"name" : "Binance-Peg USDC Token",
|
||||
"networks" : [
|
||||
"id" : "Wrapped Ether-WETH",
|
||||
"symbol" : "WETH",
|
||||
"name" : "Wrapped Ether",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x64544969ed7ebf5f083679233325356ebe738930",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Binance-Peg XRP-XRP",
|
||||
"symbol" : "XRP",
|
||||
"name" : "Binance-Peg XRP",
|
||||
"networks" : [
|
||||
"id" : "Wrapped Matic-WMATIC",
|
||||
"symbol" : "WMATIC",
|
||||
"name" : "Wrapped Matic",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "polygon-pos/test",
|
||||
"contractAddress" : "0xd0A1E359811322d97991E03f863a0C30C2cF029C",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "ethereum",
|
||||
"symbol" : "ETH",
|
||||
"name" : "Ethereum",
|
||||
"networks" : [
|
||||
"id" : "solana",
|
||||
"symbol" : "SOL",
|
||||
"name" : "Solana",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "solana/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "ethereum/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Weenus-WEENUS",
|
||||
"symbol" : "WEENUS",
|
||||
"name" : "Weenus",
|
||||
"networks" : [
|
||||
"id" : "Tangem Coin A-TCA",
|
||||
"symbol" : "TCA",
|
||||
"name" : "Tangem Coin A",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "solana/test",
|
||||
"contractAddress" : "22PTNbX31Zuztd6nD82fC8nQdT2hUfWv9XWXKuDkrFqR",
|
||||
"decimalCount" : 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0xaFF4481D10270F50f203E0763e2597776068CBc5",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Xeenus-XEENUS",
|
||||
"symbol" : "XEENUS",
|
||||
"name" : "Xeenus",
|
||||
"networks" : [
|
||||
"id" : "Tangem Coin B-TCB",
|
||||
"symbol" : "TCB",
|
||||
"name" : "Tangem Coin B",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "solana/test",
|
||||
"contractAddress" : "HmSghNPg6KCk711YJA92aPejt8auyFkvmaED6jbHfUs4",
|
||||
"decimalCount" : 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Yeenus-YEENUS",
|
||||
"symbol" : "YEENUS",
|
||||
"name" : "Yeenus",
|
||||
"networks" : [
|
||||
"id" : "binancecoin",
|
||||
"symbol" : "BNB",
|
||||
"name" : "Binance",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "binancecoin/test"
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7",
|
||||
"decimalCount" : 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Zeenus-ZEENUS",
|
||||
"symbol" : "ZEENUS",
|
||||
"name" : "Zeenus",
|
||||
"networks" : [
|
||||
"id" : "Hemster - 452-HEM",
|
||||
"symbol" : "HEM",
|
||||
"name" : "Hemster - 452",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "binancecoin/test",
|
||||
"contractAddress" : "HEM-452",
|
||||
"decimalCount" : 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e",
|
||||
"decimalCount" : 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "avalanche-2",
|
||||
"symbol" : "AVAX",
|
||||
"name" : "Avalanche",
|
||||
"networks" : [
|
||||
"id" : "Binance-Peg BTCB Token-BTCB",
|
||||
"symbol" : "BTCB",
|
||||
"name" : "Binance-Peg BTCB Token",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x6ce8da28e2f864420840cf74474eff5fd80e65b8",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "avalanche/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "The Fuji stablecoin-FUJISTABLE",
|
||||
"symbol" : "FUJISTABLE",
|
||||
"name" : "The Fuji stablecoin",
|
||||
"networks" : [
|
||||
"id" : "Binance-Peg BUSD Token-BUSD",
|
||||
"symbol" : "BUSD",
|
||||
"name" : "Binance-Peg BUSD Token",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "avalanche/test",
|
||||
"contractAddress" : "0x2058ec2791dD28b6f67DB836ddf87534F4Bbdf22",
|
||||
"decimalCount" : 6
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "To the Moon-FUJIMOON",
|
||||
"symbol" : "FUJIMOON",
|
||||
"name" : "To the Moon",
|
||||
"networks" : [
|
||||
"id" : "Binance-Peg USDC Token-USDC",
|
||||
"symbol" : "USDC",
|
||||
"name" : "Binance-Peg USDC Token",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0x64544969ed7ebf5f083679233325356ebe738930",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "avalanche/test",
|
||||
"contractAddress" : "0x97132C109c6816525F7f338DCb7435E1412A7668",
|
||||
"decimalCount" : 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "fantom",
|
||||
"symbol" : "FTM",
|
||||
"name" : "Fantom",
|
||||
"networks" : [
|
||||
"id" : "Binance-Peg XRP-XRP",
|
||||
"symbol" : "XRP",
|
||||
"name" : "Binance-Peg XRP",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"contractAddress" : "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "fantom/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Fantom USD-FUSD",
|
||||
"symbol" : "FUSD",
|
||||
"name" : "Fantom USD",
|
||||
"networks" : [
|
||||
"id" : "ethereum",
|
||||
"symbol" : "ETH",
|
||||
"name" : "Ethereum",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "ethereum/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "fantom/test",
|
||||
"contractAddress" : "0x91ea991bd52EE3C40EdA2509701d905e1Ee54074",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Wrapped Fantom-WFTM",
|
||||
"symbol" : "WFTM",
|
||||
"name" : "Wrapped Fantom",
|
||||
"networks" : [
|
||||
"id" : "ethereum-classic",
|
||||
"symbol" : "ETC",
|
||||
"name" : "Ethereum Classic",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "ethereum-classic/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "fantom/test",
|
||||
"contractAddress" : "0xf1277d1Ed8AD466beddF92ef448A132661956621",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "bitcoin",
|
||||
"symbol" : "BTC",
|
||||
"name" : "Bitcoin",
|
||||
"networks" : [
|
||||
"id" : "Weenus-WEENUS",
|
||||
"symbol" : "WEENUS",
|
||||
"name" : "Weenus",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0xaFF4481D10270F50f203E0763e2597776068CBc5",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "bitcoin/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "stellar",
|
||||
"symbol" : "XLM",
|
||||
"name" : "Stellar",
|
||||
"networks" : [
|
||||
"id" : "Xeenus-XEENUS",
|
||||
"symbol" : "XEENUS",
|
||||
"name" : "Xeenus",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"networkId" : "stellar/test"
|
||||
"id" : "Yeenus-YEENUS",
|
||||
"symbol" : "YEENUS",
|
||||
"name" : "Yeenus",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7",
|
||||
"decimalCount" : 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Zeenus-ZEENUS",
|
||||
"symbol" : "ZEENUS",
|
||||
"name" : "Zeenus",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "ethereum/test",
|
||||
"contractAddress" : "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e",
|
||||
"decimalCount" : 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "avalanche-2",
|
||||
"symbol" : "AVAX",
|
||||
"name" : "Avalanche",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "avalanche/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "The Fuji stablecoin-FUJISTABLE",
|
||||
"symbol" : "FUJISTABLE",
|
||||
"name" : "The Fuji stablecoin",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "avalanche/test",
|
||||
"contractAddress" : "0x2058ec2791dD28b6f67DB836ddf87534F4Bbdf22",
|
||||
"decimalCount" : 6
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "To the Moon-FUJIMOON",
|
||||
"symbol" : "FUJIMOON",
|
||||
"name" : "To the Moon",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "avalanche/test",
|
||||
"contractAddress" : "0x97132C109c6816525F7f338DCb7435E1412A7668",
|
||||
"decimalCount" : 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "fantom",
|
||||
"symbol" : "FTM",
|
||||
"name" : "Fantom",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "fantom/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Fantom USD-FUSD",
|
||||
"symbol" : "FUSD",
|
||||
"name" : "Fantom USD",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "fantom/test",
|
||||
"contractAddress" : "0x91ea991bd52EE3C40EdA2509701d905e1Ee54074",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "Wrapped Fantom-WFTM",
|
||||
"symbol" : "WFTM",
|
||||
"name" : "Wrapped Fantom",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "fantom/test",
|
||||
"contractAddress" : "0xf1277d1Ed8AD466beddF92ef448A132661956621",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "bitcoin",
|
||||
"symbol" : "BTC",
|
||||
"name" : "Bitcoin",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "bitcoin/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "tron",
|
||||
"symbol" : "TRX",
|
||||
"name" : "Tron",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "tron/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "arbitrum-one",
|
||||
"symbol" : "ETH",
|
||||
"name" : "Arbitrum",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "arbitrum-one/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "stellar",
|
||||
"symbol" : "XLM",
|
||||
"name" : "Stellar",
|
||||
"networks" : [
|
||||
{
|
||||
"networkId" : "stellar/test"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
"total" : 0,
|
||||
"imageHost" : "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/"
|
||||
],
|
||||
|
||||
"total" : 0,
|
||||
"imageHost" : "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/"
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -7,6 +7,7 @@ import com.google.firebase.remoteconfig.ktx.remoteConfig
|
|||
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
|
||||
import com.tangem.Log
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.domain.DomainLayer
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
|
||||
import com.tangem.tap.common.images.PicassoHelper
|
||||
|
|
@ -46,6 +47,7 @@ class TapApplication : Application() {
|
|||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
DomainLayer.init()
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
|
||||
|
|
@ -60,7 +62,9 @@ class TapApplication : Application() {
|
|||
NetworkConnectivity.createInstance(store, this)
|
||||
preferencesStorage = PreferencesStorage(this)
|
||||
PicassoHelper.initPicassoWithCaching(this)
|
||||
currenciesRepository = CurrenciesRepository(this)
|
||||
currenciesRepository = CurrenciesRepository(
|
||||
this, store.state.domainNetworks.tangemTechService
|
||||
)
|
||||
walletConnectRepository = WalletConnectRepository(this)
|
||||
|
||||
initFeedbackManager()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ import android.content.Context
|
|||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.*
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.ApproveWcSessionDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.BnbTransactionDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.ClipboardOrScanQrDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.SimpleAlertDialog
|
||||
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
|
||||
|
|
@ -14,8 +19,13 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AddMoreBack
|
|||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInProgressDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
|
||||
import com.tangem.tap.features.wallet.redux.WalletDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendBottomSheetDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.ChooseTradeActionBottomSheetDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.SimpleOkDialog
|
||||
import com.tangem.tap.features.wallet.ui.wallet.CurrencySelectionDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -54,7 +64,8 @@ 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 TwinCardsAction.Wallet.ShowInterruptDialog ->
|
||||
CreateWalletInterruptDialog.create(state.dialog, context)
|
||||
is WalletConnectDialog.UnsupportedCard ->
|
||||
SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect,
|
||||
|
|
@ -96,6 +107,14 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
|
||||
is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context)
|
||||
is WalletDialog.CurrencySelectionDialog ->
|
||||
CurrencySelectionDialog.create(state.dialog, context)
|
||||
is WalletDialog.ChooseTradeActionDialog ->
|
||||
ChooseTradeActionBottomSheetDialog(context)
|
||||
is WalletDialog.SelectAmountToSendDialog ->
|
||||
AmountToSendBottomSheetDialog(context, state.dialog)
|
||||
is WalletDialog.SignedHashesMultiWalletDialog ->
|
||||
SignedHashesWarningDialog.create(context)
|
||||
else -> null
|
||||
}
|
||||
dialog?.show()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ fun BlockchainSpinner(
|
|||
isEnabled: Boolean = true,
|
||||
textFieldConverter: (Blockchain) -> String,
|
||||
dropdownItemView: @Composable ((Blockchain) -> Unit)? = null,
|
||||
closePopupTrigger: ClosePopupTrigger = ClosePopupTrigger(),
|
||||
onItemSelected: (Blockchain) -> Unit,
|
||||
) {
|
||||
OutlinedSpinner(
|
||||
|
|
@ -26,6 +27,7 @@ fun BlockchainSpinner(
|
|||
textFieldConverter = textFieldConverter,
|
||||
dropdownItemView = dropdownItemView,
|
||||
isEnabled = isEnabled,
|
||||
onItemSelected = onItemSelected
|
||||
onItemSelected = onItemSelected,
|
||||
closePopupTrigger = closePopupTrigger,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.core.os.postDelayed
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.common.form.Field
|
||||
|
|
@ -25,7 +28,8 @@ fun <T> OutlinedSpinner(
|
|||
textFieldConverter: (T) -> String = { it.toString() },
|
||||
dropdownItemView: @Composable ((T) -> Unit)? = null,
|
||||
isEnabled: Boolean = true,
|
||||
onClose: VoidCallback = {}
|
||||
onClose: VoidCallback = {},
|
||||
closePopupTrigger: ClosePopupTrigger = ClosePopupTrigger(),
|
||||
) {
|
||||
val rIsExpanded = remember { mutableStateOf(false) }
|
||||
val stateSelectedItem = remember { mutableStateOf(selectedItem.value) }
|
||||
|
|
@ -43,6 +47,13 @@ fun <T> OutlinedSpinner(
|
|||
onClose()
|
||||
}
|
||||
|
||||
closePopupTrigger.close = {
|
||||
onDismissRequest()
|
||||
Handler(Looper.getMainLooper()).postDelayed(100) {
|
||||
closePopupTrigger.onCloseComplete()
|
||||
}
|
||||
}
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = rIsExpanded.value,
|
||||
onExpandedChange = { rIsExpanded.value = !rIsExpanded.value },
|
||||
|
|
@ -57,16 +68,17 @@ fun <T> OutlinedSpinner(
|
|||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = rIsExpanded.value) },
|
||||
)
|
||||
|
||||
if (!isEnabled) return@ExposedDropdownMenuBox
|
||||
ExposedDropdownMenu(
|
||||
expanded = rIsExpanded.value,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
itemList.forEach { item ->
|
||||
DropdownMenuItem(onClick = { onDropDownItemSelectedInternal(item) }) {
|
||||
when (dropdownItemView) {
|
||||
null -> Text(textFieldConverter(item))
|
||||
else -> dropdownItemView(item)
|
||||
if (isEnabled) {
|
||||
ExposedDropdownMenu(
|
||||
expanded = rIsExpanded.value,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
itemList.forEach { item ->
|
||||
DropdownMenuItem(onClick = { onDropDownItemSelectedInternal(item) }) {
|
||||
when (dropdownItemView) {
|
||||
null -> Text(textFieldConverter(item))
|
||||
else -> dropdownItemView(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,6 +86,12 @@ fun <T> OutlinedSpinner(
|
|||
}
|
||||
}
|
||||
|
||||
class ClosePopupTrigger {
|
||||
var close: () -> Unit = {}
|
||||
var onCloseComplete: () -> Unit = {}
|
||||
var isTriggered = false
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun TestSpinnerPreview() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.*
|
||||
|
||||
@Composable
|
||||
fun LazyListState.OnBottomReached(loadMoreThreshold: Int, loadMore: () -> Unit) {
|
||||
require(loadMoreThreshold >= 0)
|
||||
val shouldLoadMore by remember {
|
||||
derivedStateOf {
|
||||
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
|
||||
?: return@derivedStateOf false
|
||||
lastVisibleItem.index >= layoutInfo.totalItemsCount - 1 - loadMoreThreshold
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(shouldLoadMore) {
|
||||
if (shouldLoadMore) loadMore()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.common.entities
|
||||
|
||||
data class FiatCurrency(
|
||||
val code: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
) {
|
||||
val displayName: String
|
||||
get() = "${this.name} (${this.code}) - ${this.symbol}"
|
||||
|
||||
companion object {
|
||||
val Default = FiatCurrency(
|
||||
symbol = "$",
|
||||
code = "USD",
|
||||
name = "US Dollar",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.common.entities
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TapCurrency {
|
||||
companion object{
|
||||
const val DEFAULT_FIAT_CURRENCY = "USD"
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ fun Blockchain.getRoundIconRes(): Int {
|
|||
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
|
||||
|
|
@ -24,6 +25,7 @@ fun Blockchain.getRoundIconRes(): Int {
|
|||
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
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +38,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
|
|||
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.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
|
||||
|
|
@ -47,6 +50,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
|
|||
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.Dogecoin -> R.drawable.ic_dogecoin_no_color
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.tangem.tap.features.tokens.ui.AddTokensFragment
|
|||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.wallet.R
|
||||
import timber.log.Timber
|
||||
|
||||
private class Navigation
|
||||
|
||||
|
|
@ -52,12 +53,18 @@ fun FragmentActivity.openFragment(
|
|||
|
||||
fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) {
|
||||
val inclusiveFlag = if (inclusive) FragmentManager.POP_BACK_STACK_INCLUSIVE else 0
|
||||
this.supportFragmentManager.popBackStack(screen?.name, inclusiveFlag)
|
||||
try {
|
||||
this.supportFragmentManager.popBackStack(screen?.name, inclusiveFlag)
|
||||
} catch (e: IllegalStateException) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun FragmentActivity.getPreviousScreen(): AppScreen? {
|
||||
val indexOfLastFragment = this.supportFragmentManager.backStackEntryCount - 1
|
||||
val tag = this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name
|
||||
val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount)
|
||||
this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name
|
||||
else null
|
||||
return tag?.let { AppScreen.valueOf(tag) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.text.Spanned
|
||||
import android.text.SpannedString
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import androidx.core.text.buildSpannedString
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
|
|
@ -37,10 +39,23 @@ fun BigDecimal.toFormattedCurrencyString(
|
|||
return "$formattedAmount $currency"
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatString(rateValue: BigDecimal, fiatCurrencyName: FiatCurrencyName): String {
|
||||
fun BigDecimal.toFiatRateString(
|
||||
fiatCurrencyName: String
|
||||
): String {
|
||||
val value = this
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
.formatWithSpaces()
|
||||
return "$value $fiatCurrencyName"
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatString(
|
||||
rateValue: BigDecimal,
|
||||
fiatCurrencyName: String,
|
||||
formatWithSpaces: Boolean = false
|
||||
): String {
|
||||
var fiatValue = rateValue.multiply(this)
|
||||
fiatValue = fiatValue.setScale(2, RoundingMode.HALF_UP)
|
||||
return "≈ ${fiatCurrencyName} $fiatValue"
|
||||
return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces)
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
|
||||
|
|
@ -48,12 +63,14 @@ fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
|
|||
return fiatValue.setScale(2, RoundingMode.HALF_UP)
|
||||
}
|
||||
|
||||
fun BigDecimal.toFormattedFiatValue(fiatCurrencyName: FiatCurrencyName): String {
|
||||
return "≈ ${fiatCurrencyName} $this"
|
||||
fun BigDecimal.toFormattedFiatValue(
|
||||
fiatCurrencyName: String,
|
||||
formatWithSpaces: Boolean = false
|
||||
): String {
|
||||
val fiatValue = if (formatWithSpaces) this.formatWithSpaces() else this
|
||||
return " ${fiatValue} $fiatCurrencyName"
|
||||
}
|
||||
|
||||
fun CurrenciesResponse.Currency.toFormattedString(): String = "${this.name} (${this.code}) - ${this.unit}"
|
||||
|
||||
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
|
||||
|
||||
// 0.00 -> 0.00
|
||||
|
|
@ -87,4 +104,54 @@ fun BigDecimal.isGreaterThanOrEqual(value: BigDecimal): Boolean {
|
|||
fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean {
|
||||
val compareResult = this.compareTo(value)
|
||||
return compareResult == -1 || compareResult == 0
|
||||
}
|
||||
|
||||
fun BigDecimal.formatAmountAsSpannedString(
|
||||
currencySymbol: String,
|
||||
reminderPartSizeProportion: Float = 0.7f
|
||||
): SpannedString {
|
||||
val amount = this
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
.formatWithSpaces()
|
||||
val integer = amount.substringBefore('.')
|
||||
val reminder = amount.substringAfter('.')
|
||||
|
||||
return buildSpannedString {
|
||||
append(integer)
|
||||
append('.')
|
||||
append(
|
||||
"$reminder $currencySymbol",
|
||||
RelativeSizeSpan(reminderPartSizeProportion),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun BigDecimal.formatWithSpaces(): String {
|
||||
val str = this.toString()
|
||||
var integerStr = str.substringBefore('.')
|
||||
val reminderStr = str.substringAfter('.')
|
||||
val packets = arrayListOf<String>()
|
||||
|
||||
var index: Int = integerStr.length
|
||||
while (0 < index) {
|
||||
if (index <= 3) {
|
||||
packets.add(0, integerStr)
|
||||
break
|
||||
}
|
||||
index -= 3
|
||||
packets.add(integerStr.substring(startIndex = index))
|
||||
integerStr = integerStr.substring(startIndex = 0, endIndex = index)
|
||||
}
|
||||
|
||||
return buildString {
|
||||
packets.forEachIndexed { index, packet ->
|
||||
append(packet)
|
||||
if (index != packets.lastIndex) append(' ')
|
||||
}
|
||||
if (reminderStr.isNotBlank()) {
|
||||
append('.')
|
||||
append(reminderStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import androidx.annotation.ColorRes
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
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
|
||||
|
|
@ -187,4 +188,29 @@ fun Context.safeStartActivity(
|
|||
|
||||
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
|
||||
return context.getString(resId, formatArgs)
|
||||
}
|
||||
}
|
||||
|
||||
fun View.animateVisibility(
|
||||
show: Boolean,
|
||||
durationMillis: Long = SHORT_ANIMATION_DURATION,
|
||||
hiddenVisibility: Int = View.GONE
|
||||
) {
|
||||
if (show) {
|
||||
this.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(durationMillis)
|
||||
.withStartAction {
|
||||
this.alpha = 0f
|
||||
this.isVisible = true
|
||||
}
|
||||
} else {
|
||||
this.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(durationMillis)
|
||||
.withStartAction {
|
||||
this.visibility = hiddenVisibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val SHORT_ANIMATION_DURATION = 80L
|
||||
|
|
@ -57,7 +57,7 @@ fun WalletManager?.getToUpUrl(): String? {
|
|||
action = CurrencyExchangeManager.Action.Buy,
|
||||
blockchain = wallet.blockchain,
|
||||
cryptoCurrencyName = wallet.blockchain.currency,
|
||||
fiatCurrency = globalState.appCurrency,
|
||||
fiatCurrencyName = globalState.appCurrency.code,
|
||||
walletAddress = defaultAddress,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ private class AddCustomTokenConverter(
|
|||
val rawMessage = when (customTokenError) {
|
||||
AddCustomTokenError.Warning.PotentialScamToken -> R.string.custom_token_validation_error_not_found
|
||||
AddCustomTokenError.Warning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added
|
||||
AddCustomTokenError.Warning.UnsupportedSolanaToken -> R.string.alert_manage_tokens_unsupported_message
|
||||
AddCustomTokenError.InvalidContractAddress -> R.string.custom_token_creation_error_invalid_contract_address
|
||||
AddCustomTokenError.NetworkIsNotSelected -> R.string.custom_token_creation_error_network_not_selected
|
||||
AddCustomTokenError.InvalidDerivationPath -> R.string.custom_token_creation_error_invalid_derivation_path
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
package com.tangem.tap.common.recyclerView
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
||||
|
||||
class SpaceItemDecoration(
|
||||
private val horizontalSpaceDp: Float,
|
||||
private val verticalSpaceDp: Float,
|
||||
) : RecyclerView.ItemDecoration() {
|
||||
|
||||
private lateinit var space: Space
|
||||
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
if (state.itemCount == 0) return
|
||||
if (!::space.isInitialized) {
|
||||
space = Space(
|
||||
view.dpToPx(horizontalSpaceDp).toInt(),
|
||||
view.dpToPx(verticalSpaceDp).toInt()
|
||||
)
|
||||
}
|
||||
|
||||
outRect.left = space.horizontal
|
||||
outRect.right = space.horizontal
|
||||
|
||||
when (state.itemCount) {
|
||||
1 -> {
|
||||
outRect.top = space.vertical
|
||||
outRect.bottom = space.vertical
|
||||
}
|
||||
else -> {
|
||||
val adapterPosition = parent.getChildAdapterPosition(view)
|
||||
if (adapterPosition == -1) return
|
||||
|
||||
when (adapterPosition) {
|
||||
0 -> {
|
||||
// first
|
||||
outRect.top = space.vertical
|
||||
outRect.bottom = space.vertical / 2
|
||||
}
|
||||
state.itemCount - 1 -> {
|
||||
// last
|
||||
outRect.top = space.vertical / 2
|
||||
outRect.bottom = space.vertical
|
||||
}
|
||||
else -> {
|
||||
// middle
|
||||
outRect.top = space.vertical / 2
|
||||
outRect.bottom = space.vertical / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class Space(
|
||||
val horizontal: Int,
|
||||
val vertical: Int,
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun all(dp: Float): SpaceItemDecoration = SpaceItemDecoration(dp, dp)
|
||||
fun vertical(dp: Float): SpaceItemDecoration = SpaceItemDecoration(0f, dp)
|
||||
fun horizontal(dp: Float): SpaceItemDecoration = SpaceItemDecoration(dp, 0f)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,12 @@ interface StateDialog
|
|||
|
||||
sealed class AppDialog : StateDialog {
|
||||
data class SimpleOkDialog(val header: String, val message: String, val onOk: VoidCallback? = null) : AppDialog()
|
||||
data class SimpleOkDialogRes(val headerId: Int, val messageId: Int, val onOk: VoidCallback? = null) : AppDialog()
|
||||
data class SimpleOkDialogRes(
|
||||
val headerId: Int,
|
||||
val messageId: Int,
|
||||
val onOk: VoidCallback? = null
|
||||
) : AppDialog()
|
||||
|
||||
object ScanFailsDialog : AppDialog()
|
||||
data class AddressInfoDialog(
|
||||
val currency: Currency,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.redux.*
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.config.ConfigManager
|
||||
|
|
@ -54,9 +55,9 @@ sealed class GlobalAction : Action {
|
|||
|
||||
data class SetIfCardVerifiedOnline(val verified: Boolean) : GlobalAction()
|
||||
|
||||
data class ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction()
|
||||
data class ChangeAppCurrency(val appCurrency: FiatCurrency) : GlobalAction()
|
||||
object RestoreAppCurrency : GlobalAction() {
|
||||
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()
|
||||
data class Success(val appCurrency: FiatCurrency) : GlobalAction()
|
||||
}
|
||||
|
||||
data class UpdateWalletSignedHashes(
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class GlobalMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState ->
|
||||
private val globalMiddlewareHandler: Middleware<AppState> = { _, appState ->
|
||||
{ nextDispatch ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
|
|
@ -45,7 +45,7 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
|
|||
}
|
||||
is GlobalAction.RestoreAppCurrency -> {
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency.Success(
|
||||
preferencesStorage.getAppCurrency()
|
||||
preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
|
||||
))
|
||||
}
|
||||
is GlobalAction.HideWarningMessage -> {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.common.redux.global
|
|||
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.analytics.AnalyticsHandler
|
||||
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
|
|
@ -22,7 +22,7 @@ data class GlobalState(
|
|||
val configManager: ConfigManager? = null,
|
||||
val warningManager: WarningMessagesManager? = null,
|
||||
val feedbackManager: FeedbackManager? = null,
|
||||
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
|
||||
val appCurrency: FiatCurrency = FiatCurrency.Default,
|
||||
val scanCardFailsCounter: Int = 0,
|
||||
val dialog: StateDialog? = null,
|
||||
val currencyExchangeManager: CurrencyExchangeManager? = null,
|
||||
|
|
@ -40,7 +40,6 @@ data class AndroidResources(
|
|||
)
|
||||
}
|
||||
typealias CryptoCurrencyName = String
|
||||
typealias FiatCurrencyName = String
|
||||
|
||||
data class OnboardingState(
|
||||
val onboardingStarted: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import com.tangem.tap.domain.configurable.config.ConfigManager
|
|||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
|
|
@ -34,17 +34,17 @@ class TapWalletManager {
|
|||
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
||||
}
|
||||
|
||||
private val walletManagersThrottler = ThrottlerWithValues<Blockchain, Result<Wallet>>(10000)
|
||||
private val walletManagersThrottler = ThrottlerWithValues<BlockchainNetwork, Result<Wallet>>(10000)
|
||||
|
||||
suspend fun loadWalletData(walletManager: WalletManager) {
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
val result = if (walletManagersThrottler.isStillThrottled(blockchain)) {
|
||||
walletManagersThrottler.geValue(blockchain)!!
|
||||
val result = if (walletManagersThrottler.isStillThrottled(blockchainNetwork)) {
|
||||
walletManagersThrottler.geValue(blockchainNetwork)!!
|
||||
} else {
|
||||
val safeUpdateResult = walletManager.safeUpdate()
|
||||
walletManagersThrottler.updateThrottlingTo(blockchain)
|
||||
walletManagersThrottler.setValue(blockchain, safeUpdateResult)
|
||||
walletManagersThrottler.updateThrottlingTo(blockchainNetwork)
|
||||
walletManagersThrottler.setValue(blockchainNetwork, safeUpdateResult)
|
||||
safeUpdateResult
|
||||
}
|
||||
when (result) {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ interface Loader<T> {
|
|||
fun load(onComplete: (T) -> Unit)
|
||||
|
||||
companion object {
|
||||
const val featuresName = "features_${BuildConfig.CONFIG_ENVIRONMENT}"
|
||||
const val configValuesName = "tangem-app-config/config_${BuildConfig.CONFIG_ENVIRONMENT}"
|
||||
const val warnings = "warnings_${BuildConfig.CONFIG_ENVIRONMENT}"
|
||||
const val featuresName = "features_${BuildConfig.ENVIRONMENT}"
|
||||
const val configValuesName = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}"
|
||||
const val warnings = "warnings_${BuildConfig.ENVIRONMENT}"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.tap.domain.extensions
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.extensions.isAboveZero
|
||||
|
||||
fun Map<AmountType, Amount>.toSendableAmounts(): List<Amount> {
|
||||
return this.toList().unzip().second
|
||||
.filter { it.type != AmountType.Reserve }
|
||||
.filter { it.isAboveZero() }
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import com.tangem.common.hdWallet.DerivationPath
|
|||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagerForApp(
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
|||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.extensions.getPrimaryCurve
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ScanProductTask(
|
||||
val card: Card? = null,
|
||||
|
|
@ -192,54 +194,56 @@ private class ScanWalletProcessor(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit
|
||||
) {
|
||||
val derivations = collectDerivations(card)
|
||||
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
primaryCard = primaryCard
|
||||
scope.launch {
|
||||
val derivations = collectDerivations(card)
|
||||
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
primaryCard = primaryCard
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
derivedKeys = result.data.entries,
|
||||
primaryCard = primaryCard
|
||||
)
|
||||
callback(CompletionResult.Success(response))
|
||||
return@launch
|
||||
}
|
||||
|
||||
DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
derivedKeys = result.data.entries,
|
||||
primaryCard = primaryCard
|
||||
)
|
||||
callback(CompletionResult.Success(response))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBlockchainsToDerive(card: Card): List<BlockchainNetwork> {
|
||||
private suspend fun getBlockchainsToDerive(card: Card): List<BlockchainNetwork> {
|
||||
val currenciesRepository = currenciesRepository ?: return emptyList()
|
||||
|
||||
val cardCurrencies = currenciesRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed).toMutableList()
|
||||
|
||||
val blockchainsToDerive = cardCurrencies.ifEmpty {
|
||||
mutableListOf(
|
||||
BlockchainNetwork(Blockchain.Bitcoin, card),
|
||||
BlockchainNetwork(Blockchain.Ethereum, card))
|
||||
BlockchainNetwork(Blockchain.Ethereum, card)
|
||||
)
|
||||
}
|
||||
|
||||
if (card.settings.isHDWalletAllowed) {
|
||||
blockchainsToDerive.addAll(
|
||||
listOf(
|
||||
BlockchainNetwork(Blockchain.Ethereum, card),
|
||||
BlockchainNetwork(Blockchain.Binance, card),
|
||||
BlockchainNetwork(Blockchain.EthereumTestnet, card)
|
||||
)
|
||||
)
|
||||
|
|
@ -261,7 +265,7 @@ private class ScanWalletProcessor(
|
|||
return blockchainsToDerive.distinct()
|
||||
}
|
||||
|
||||
private fun collectDerivations(card: Card): Map<ByteArrayKey, List<DerivationPath>> {
|
||||
private suspend fun collectDerivations(card: Card): Map<ByteArrayKey, List<DerivationPath>> {
|
||||
val blockchains = getBlockchainsToDerive(card)
|
||||
val derivations = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,26 +2,32 @@ package com.tangem.tap.domain.tokens
|
|||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.getTokens
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.extensions.appendIf
|
||||
import com.tangem.tap.common.extensions.readJsonFileToString
|
||||
import com.tangem.tap.domain.extensions.setCustomIconUrl
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.ObsoleteTokenDao
|
||||
import com.tangem.tap.domain.tokens.models.TokenDao
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
class CurrenciesRepository(val context: Application) {
|
||||
class CurrenciesRepository(
|
||||
private val context: Application,
|
||||
private val tangemNetworkService: TangemTechService
|
||||
) {
|
||||
|
||||
private val moshi = MoshiConverter.defaultMoshi()
|
||||
private val blockchainsAdapter: JsonAdapter<List<Blockchain>> = moshi.adapter(
|
||||
|
|
@ -41,7 +47,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
|
||||
fun saveUpdatedCurrency(cardId: String, blockchainNetwork: BlockchainNetwork) {
|
||||
var changed = false
|
||||
val currencies = loadSavedCurrencies(cardId).map {
|
||||
val currencies = loadSavedCurrenciesWithoutMigration(cardId).map {
|
||||
if (it == blockchainNetwork) {
|
||||
changed = true
|
||||
blockchainNetwork
|
||||
|
|
@ -54,7 +60,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
|
||||
fun removeToken(cardId: String, token: Token, blockchainNetwork: BlockchainNetwork) {
|
||||
val currencies = loadSavedCurrencies(cardId).map {
|
||||
val currencies = loadSavedCurrenciesWithoutMigration(cardId).map {
|
||||
if (it == blockchainNetwork) {
|
||||
it.copy(tokens = it.tokens.filterNot { it == token })
|
||||
} else {
|
||||
|
|
@ -65,7 +71,8 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
|
||||
fun removeBlockchain(cardId: String, blockchainNetwork: BlockchainNetwork) {
|
||||
val currencies = loadSavedCurrencies(cardId).filterNot { it == blockchainNetwork }
|
||||
val currencies = loadSavedCurrenciesWithoutMigration(cardId)
|
||||
.filterNot { it == blockchainNetwork }
|
||||
saveCurrencies(cardId, currencies)
|
||||
}
|
||||
|
||||
|
|
@ -98,12 +105,12 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
fun loadSavedCurrencies(
|
||||
suspend fun loadSavedCurrencies(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false
|
||||
): List<BlockchainNetwork> {
|
||||
if (DemoHelper.isDemoCardId(cardId)) {
|
||||
return loadDemoCurrencies(cardId)
|
||||
return loadDemoCurrencies()
|
||||
}
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
|
|
@ -113,7 +120,21 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadDemoCurrencies(cardId: String): List<BlockchainNetwork> {
|
||||
fun loadSavedCurrenciesWithoutMigration(
|
||||
cardId: String,
|
||||
): List<BlockchainNetwork> {
|
||||
if (DemoHelper.isDemoCardId(cardId)) {
|
||||
return loadDemoCurrencies()
|
||||
}
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDemoCurrencies(): List<BlockchainNetwork> {
|
||||
return DemoHelper.config.demoBlockchains.map {
|
||||
BlockchainNetwork(
|
||||
blockchain = it,
|
||||
|
|
@ -123,7 +144,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun tryToLoadPreviousFormatAndMigrate(
|
||||
private suspend fun tryToLoadPreviousFormatAndMigrate(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false
|
||||
): List<BlockchainNetwork> {
|
||||
|
|
@ -137,12 +158,12 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadSavedCurrenciesOldWay(
|
||||
private suspend fun loadSavedCurrenciesOldWay(
|
||||
cardId: String, isHdWalletSupported: Boolean = false
|
||||
): List<BlockchainNetwork> {
|
||||
val blockchains = loadSavedBlockchains(cardId)
|
||||
val tokens = loadSavedTokens(cardId)
|
||||
val currencies = getSupportedTokens()
|
||||
val ids = getTokensIds(tokens)
|
||||
val derivationStyle = if (isHdWalletSupported) DerivationStyle.LEGACY else null
|
||||
val blockchainNetworks = blockchains.map { blockchain ->
|
||||
BlockchainNetwork(
|
||||
|
|
@ -152,10 +173,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
.filter { it.blockchainDao.toBlockchain() == blockchain }
|
||||
.map {
|
||||
val token = it.toToken()
|
||||
val id = currencies
|
||||
.find { it.contracts.find { it.address == token.contractAddress } != null }
|
||||
?.id
|
||||
token.copy(id = id)
|
||||
token.copy(id = ids[token.contractAddress])
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -163,6 +181,21 @@ class CurrenciesRepository(val context: Application) {
|
|||
return blockchainNetworks
|
||||
}
|
||||
|
||||
private suspend fun getTokensIds(tokens: List<TokenDao>): Map<String, String> = coroutineScope {
|
||||
tokens.map {
|
||||
async {
|
||||
tangemNetworkService.getTokens(
|
||||
it.contractAddress,
|
||||
it.blockchainDao.toBlockchain().toNetworkId()
|
||||
)
|
||||
}
|
||||
}.map { it.await() }
|
||||
.map { (it as? Result.Success)?.data?.coins?.firstOrNull()?.id }
|
||||
.mapIndexedNotNull { index, id ->
|
||||
if (id == null) null else tokens[index].contractAddress to id
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
fun saveCurrencies(cardId: String, currencies: List<BlockchainNetwork>) {
|
||||
val json = blockchainNetworkAdapter.toJson(currencies)
|
||||
context.rewriteFile(json, getFileNameForBlockchains(cardId))
|
||||
|
|
@ -177,9 +210,8 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
fun getSupportedTokens(isTestNet: Boolean = false): List<Currency> {
|
||||
val fileName = if (isTestNet) "testnet_tokens" else "tokens"
|
||||
val json = context.assets.readJsonFileToString(fileName)
|
||||
fun getTestnetCoins(): List<Currency> {
|
||||
val json = context.assets.readJsonFileToString(FILE_NAME_TESTNET_COINS)
|
||||
return currenciesAdapter.fromJson(json)!!.coins
|
||||
.map { Currency.fromJsonObject(it) }
|
||||
}
|
||||
|
|
@ -232,6 +264,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
companion object {
|
||||
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
|
||||
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
|
||||
private const val FILE_NAME_TESTNET_COINS = "testnet_tokens"
|
||||
|
||||
fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
|
||||
fun getFileNameForBlockchains(cardId: String): String =
|
||||
|
|
@ -245,145 +278,3 @@ fun Blockchain.getTokensName(): String {
|
|||
else -> this.fullName
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenDao(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contractAddress: String,
|
||||
val decimalCount: Int,
|
||||
@Json(name = "blockchain")
|
||||
val blockchainDao: BlockchainDao,
|
||||
val customIconUrl: String? = null,
|
||||
val type: String? = null
|
||||
) {
|
||||
fun toToken(): Token {
|
||||
return Token(
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
contractAddress = contractAddress,
|
||||
decimals = decimalCount,
|
||||
).apply {
|
||||
customIconUrl?.let { this.setCustomIconUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BlockchainDao(
|
||||
@Json(name = "key")
|
||||
val name: String,
|
||||
@Json(name = "testnet")
|
||||
val isTestNet: Boolean
|
||||
) {
|
||||
fun toBlockchain(): Blockchain {
|
||||
val blockchain = Blockchain.values().find { it.name.lowercase() == name.lowercase() }
|
||||
?: throw Exception("Invalid BlockchainDao")
|
||||
return if (!isTestNet) blockchain else blockchain.getTestnetVersion()
|
||||
?: throw Exception("Invalid BlockchainDao")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromBlockchain(blockchain: Blockchain): BlockchainDao {
|
||||
val name = blockchain.name.removeSuffix("Testnet").lowercase()
|
||||
return BlockchainDao(name, blockchain.isTestnet())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ObsoleteTokenDao(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contractAddress: String,
|
||||
val decimalCount: Int,
|
||||
val customIconUrl: String?,
|
||||
) {
|
||||
fun toTokenDao(blockchain: Blockchain): TokenDao {
|
||||
return TokenDao(
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
contractAddress = contractAddress,
|
||||
decimalCount = decimalCount,
|
||||
blockchainDao = BlockchainDao.fromBlockchain(blockchain),
|
||||
customIconUrl = customIconUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//@JsonClass(generateAdapter = true)
|
||||
//data class CardCurrenciesDao(
|
||||
// val tokens: List<TokenDao>,
|
||||
// val blockchains: List<Blockchain>,
|
||||
//) {
|
||||
// fun toCardCurrencies(): CardCurrencies {
|
||||
// return CardCurrencies(
|
||||
// tokens = tokens.map { it.toToken() }.distinct(),
|
||||
// blockchains = blockchains
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// companion object {
|
||||
// fun fromCardCurrencies(cardCurrencies: CardCurrencies): CardCurrenciesDao {
|
||||
// return CardCurrenciesDao(
|
||||
// tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }.distinct(),
|
||||
// blockchains = cardCurrencies.blockchains
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
data class CardCurrencies(
|
||||
val tokens: List<Token>,
|
||||
val blockchains: List<Blockchain>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BlockchainNetwork(
|
||||
val blockchain: Blockchain,
|
||||
val derivationPath: String?,
|
||||
val tokens: List<Token>
|
||||
) {
|
||||
|
||||
constructor(blockchain: Blockchain, card: Card) : this(
|
||||
blockchain = blockchain,
|
||||
derivationPath = if (card.settings.isHDWalletAllowed) blockchain.derivationPath(card.derivationStyle)?.rawPath else null,
|
||||
tokens = emptyList()
|
||||
)
|
||||
|
||||
|
||||
fun updateTokens(tokens: List<Token>): BlockchainNetwork {
|
||||
return copy(
|
||||
tokens = (this.tokens + tokens).distinct()
|
||||
)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as BlockchainNetwork
|
||||
|
||||
if (blockchain != other.blockchain) return false
|
||||
if (derivationPath != other.derivationPath) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = blockchain.hashCode()
|
||||
result = 31 * result + (derivationPath?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromWalletManager(walletManager: WalletManager): BlockchainNetwork {
|
||||
return BlockchainNetwork(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
walletManager.cardTokens.toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.domain.tokens
|
|||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrencyFromJson(
|
||||
|
|
@ -44,10 +45,20 @@ data class Currency(
|
|||
id = currency.id,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
iconUrl = getIconUrl(currency.id),
|
||||
iconUrl = getIconUrl(currency.id, null),
|
||||
contracts = currency.networks?.toContracts() ?: emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
fun fromCoinResponse(currency: CoinsResponse.Coin, imageHost: String?): Currency {
|
||||
return Currency(
|
||||
id = currency.id,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
iconUrl = getIconUrl(currency.id, imageHost),
|
||||
contracts = currency.networks.mapNotNull { Contract.fromNetwork(it, imageHost) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,12 +78,26 @@ data class Contract(
|
|||
blockchain = blockchain,
|
||||
address = contract.contractAddress,
|
||||
decimalCount = contract.decimalCount,
|
||||
iconUrl = getIconUrl(contract.networkId)
|
||||
iconUrl = getIconUrl(contract.networkId, null)
|
||||
)
|
||||
}
|
||||
|
||||
fun fromNetwork(contract: CoinsResponse.Coin.Network, imageHost: String?): Contract? {
|
||||
val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: return null
|
||||
return Contract(
|
||||
networkId = contract.networkId,
|
||||
blockchain = blockchain,
|
||||
address = contract.contractAddress,
|
||||
decimalCount = contract.decimalCount?.toInt(),
|
||||
iconUrl = getIconUrl(contract.networkId, imageHost)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getIconUrl(id: String): String {
|
||||
return "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/$id.png"
|
||||
}
|
||||
fun getIconUrl(id: String, imageHost: String? = null): String {
|
||||
return "${imageHost ?: DEFAULT_IMAGE_HOST}large/$id.png"
|
||||
}
|
||||
|
||||
private const val DEFAULT_IMAGE_HOST =
|
||||
"https://s3.eu-central-1.amazonaws.com/tangem.api/coins/"
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.getListOfCoins
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
|
||||
class LoadAvailableCoinsService(
|
||||
private val networkService: TangemTechService,
|
||||
private val currenciesRepository: CurrenciesRepository
|
||||
) {
|
||||
|
||||
suspend fun getSupportedTokens(
|
||||
isTestNet: Boolean = false,
|
||||
supportedBlockchains: List<Blockchain>,
|
||||
page: Int,
|
||||
searchInput: String? = null
|
||||
): Result<LoadedCoins> {
|
||||
if (isTestNet) {
|
||||
return Result.Success(
|
||||
LoadedCoins(
|
||||
currencies = currenciesRepository.getTestnetCoins(),
|
||||
moreAvailable = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
val offset = page * LOAD_PER_PAGE
|
||||
val result = loadCoins(supportedBlockchains, offset, searchInput)
|
||||
|
||||
return when (result) {
|
||||
is Result.Success -> {
|
||||
val data = result.data
|
||||
Result.Success(
|
||||
LoadedCoins(
|
||||
currencies = data.coins.map { Currency.fromCoinResponse(it, data.imageHost) },
|
||||
moreAvailable = data.total > offset + LOAD_PER_PAGE,
|
||||
)
|
||||
)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
Result.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadCoins(
|
||||
supportedBlockchains: List<Blockchain>,
|
||||
offset: Int,
|
||||
searchInput: String? = null
|
||||
): Result<CoinsResponse> {
|
||||
val networkIds = supportedBlockchains.toSet().map { it.toNetworkId() }
|
||||
return networkService.getListOfCoins(
|
||||
networkIds = networkIds,
|
||||
offset = offset,
|
||||
limit = LOAD_PER_PAGE,
|
||||
searchText = searchInput
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LOAD_PER_PAGE = 100
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class LoadedCoins(
|
||||
val currencies: List<Currency>,
|
||||
val moreAvailable: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tap.domain.tokens.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
||||
@Deprecated("The class is used only for migration from older versions of the app")
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BlockchainDao(
|
||||
@Json(name = "key")
|
||||
val name: String,
|
||||
@Json(name = "testnet")
|
||||
val isTestNet: Boolean
|
||||
) {
|
||||
@Deprecated("The method is used only for migration from older versions of the app")
|
||||
fun toBlockchain(): Blockchain {
|
||||
val blockchain = Blockchain.values().find { it.name.lowercase() == name.lowercase() }
|
||||
?: throw Exception("Invalid BlockchainDao")
|
||||
return if (!isTestNet) blockchain else blockchain.getTestnetVersion()
|
||||
?: throw Exception("Invalid BlockchainDao")
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Deprecated("The method is used only for migration from older versions of the app")
|
||||
fun fromBlockchain(blockchain: Blockchain): BlockchainDao {
|
||||
val name = blockchain.name.removeSuffix("Testnet").lowercase()
|
||||
return BlockchainDao(name, blockchain.isTestnet())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.tap.domain.tokens.models
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.extensions.calculateHashCode
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BlockchainNetwork(
|
||||
val blockchain: Blockchain,
|
||||
val derivationPath: String?,
|
||||
val tokens: List<Token>
|
||||
) {
|
||||
|
||||
constructor(blockchain: Blockchain, card: Card) : this(
|
||||
blockchain = blockchain,
|
||||
derivationPath = if (card.settings.isHDWalletAllowed) blockchain.derivationPath(card.derivationStyle)?.rawPath else null,
|
||||
tokens = emptyList()
|
||||
)
|
||||
|
||||
|
||||
fun updateTokens(tokens: List<Token>): BlockchainNetwork {
|
||||
return copy(
|
||||
tokens = (this.tokens + tokens).distinct()
|
||||
)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as BlockchainNetwork
|
||||
|
||||
if (blockchain != other.blockchain) return false
|
||||
if (derivationPath != other.derivationPath) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = calculateHashCode(
|
||||
blockchain.hashCode(), derivationPath?.hashCode() ?: 0
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun fromWalletManager(walletManager: WalletManager): BlockchainNetwork {
|
||||
return BlockchainNetwork(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
walletManager.cardTokens.toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.tap.domain.tokens.models
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ObsoleteTokenDao(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contractAddress: String,
|
||||
val decimalCount: Int,
|
||||
val customIconUrl: String?,
|
||||
) {
|
||||
fun toTokenDao(blockchain: Blockchain): TokenDao {
|
||||
return TokenDao(
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
contractAddress = contractAddress,
|
||||
decimalCount = decimalCount,
|
||||
blockchainDao = BlockchainDao.fromBlockchain(blockchain),
|
||||
customIconUrl = customIconUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tap.domain.tokens.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.domain.extensions.setCustomIconUrl
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenDao(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contractAddress: String,
|
||||
val decimalCount: Int,
|
||||
@Json(name = "blockchain")
|
||||
val blockchainDao: BlockchainDao,
|
||||
val customIconUrl: String? = null,
|
||||
val type: String? = null
|
||||
) {
|
||||
fun toToken(): Token {
|
||||
return Token(
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
contractAddress = contractAddress,
|
||||
decimals = decimalCount,
|
||||
).apply {
|
||||
customIconUrl?.let { this.setCustomIconUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,11 +4,8 @@ import com.tangem.blockchain.common.Wallet
|
|||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.domain.termsOfUse.CardTou
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -19,9 +16,6 @@ sealed class DetailsAction : Action {
|
|||
val scanResponse: ScanResponse,
|
||||
val wallets: List<Wallet>,
|
||||
val cardTou: CardTou,
|
||||
val fiatCurrencyName: FiatCurrencyName,
|
||||
val fiatCurrencies: List<FiatCurrencyName>? = null,
|
||||
val tangemTechService: TangemTechService,
|
||||
) : DetailsAction()
|
||||
|
||||
object ShowDisclaimer : DetailsAction()
|
||||
|
|
@ -47,13 +41,6 @@ sealed class DetailsAction : Action {
|
|||
|
||||
object CreateBackup : DetailsAction()
|
||||
|
||||
sealed class AppCurrencyAction : DetailsAction() {
|
||||
data class SetCurrencies(val currencies: List<CurrenciesResponse.Currency>) : AppCurrencyAction()
|
||||
object ChooseAppCurrency : AppCurrencyAction()
|
||||
object Cancel : AppCurrencyAction()
|
||||
data class SelectAppCurrency(val fiatCurrencyName: FiatCurrencyName) : AppCurrencyAction()
|
||||
}
|
||||
|
||||
sealed class ManageSecurity : DetailsAction() {
|
||||
data class CheckCurrentSecurityOption(val card: Card) : ManageSecurity()
|
||||
data class SetCurrentOption(val userCodes: CheckUserCodesResponse) : ManageSecurity()
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ package com.tangem.tap.features.details.redux
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.AnalyticsParam
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
|
|
@ -14,11 +12,14 @@ import com.tangem.tap.common.redux.AppState
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -26,15 +27,12 @@ import org.rekotlin.Middleware
|
|||
|
||||
class DetailsMiddleware {
|
||||
private val eraseWalletMiddleware = EraseWalletMiddleware()
|
||||
private val appCurrencyMiddleware = AppCurrencyMiddleware()
|
||||
private val manageSecurityMiddleware = ManageSecurityMiddleware()
|
||||
val detailsMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
val detailsMiddleware: Middleware<AppState> = { _, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is DetailsAction.PrepareScreen -> prepareData(action)
|
||||
is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action)
|
||||
is DetailsAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action)
|
||||
is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action)
|
||||
is DetailsAction.ShowDisclaimer -> {
|
||||
store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer)
|
||||
|
|
@ -67,29 +65,7 @@ class DetailsMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun prepareData(action: DetailsAction.PrepareScreen) {
|
||||
val fiatCurrenciesPrefStorage = preferencesStorage.fiatCurrenciesPrefStorage
|
||||
val storedFiatCurrencies = fiatCurrenciesPrefStorage.restore()
|
||||
if (storedFiatCurrencies.isNotEmpty()) {
|
||||
store.dispatch(DetailsAction.AppCurrencyAction.SetCurrencies(storedFiatCurrencies))
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
val tangemTechService = action.tangemTechService
|
||||
when (val result = tangemTechService.currencies()) {
|
||||
is Result.Success -> {
|
||||
val currenciesList = result.data.currencies
|
||||
if (currenciesList.isNotEmpty() && currenciesList.toSet() != storedFiatCurrencies.toSet()) {
|
||||
fiatCurrenciesPrefStorage.save(currenciesList)
|
||||
dispatchOnMain(DetailsAction.AppCurrencyAction.SetCurrencies(currenciesList))
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EraseWalletMiddleware() {
|
||||
class EraseWalletMiddleware {
|
||||
fun handle(action: DetailsAction.ResetToFactory) {
|
||||
when (action) {
|
||||
is DetailsAction.ResetToFactory.Proceed -> {
|
||||
|
|
@ -100,6 +76,8 @@ class DetailsMiddleware {
|
|||
store.dispatch(DetailsAction.ResetToFactory.Proceed.NotAllowedByCard)
|
||||
EraseWalletState.NotEmpty ->
|
||||
store.dispatch(DetailsAction.ResetToFactory.Proceed.NotEmpty)
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
}
|
||||
is DetailsAction.ResetToFactory.Cancel -> {
|
||||
|
|
@ -128,18 +106,7 @@ class DetailsMiddleware {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppCurrencyMiddleware {
|
||||
fun handle(action: DetailsAction.AppCurrencyAction) {
|
||||
when (action) {
|
||||
is DetailsAction.AppCurrencyAction.SelectAppCurrency -> {
|
||||
store.state.globalState.tapWalletManager.rates.clear()
|
||||
preferencesStorage.saveAppCurrency(action.fiatCurrencyName)
|
||||
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrencyName))
|
||||
store.dispatch(WalletAction.LoadFiatRate())
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -211,18 +178,22 @@ class DetailsMiddleware {
|
|||
actionToLog = Analytics.ActionToLog.ChangeSecOptions,
|
||||
parameters = mapOf(
|
||||
AnalyticsParam.NEW_SECURITY_OPTION to
|
||||
(selectedOption?.name ?: "")
|
||||
(selectedOption?.name ?: "")
|
||||
),
|
||||
card = store.state.detailsState.scanResponse?.card
|
||||
)
|
||||
}
|
||||
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)
|
||||
}
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,7 @@ import com.tangem.domain.common.isTangemTwin
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.extensions.isWalletDataSupported
|
||||
import com.tangem.tap.domain.extensions.signedHashesCount
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.features.wallet.models.hasPendingTransactions
|
||||
import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions
|
||||
import org.rekotlin.Action
|
||||
import java.util.*
|
||||
|
||||
|
|
@ -29,9 +28,6 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
is DetailsAction.ResetToFactory -> {
|
||||
handleEraseWallet(action, detailsState)
|
||||
}
|
||||
is DetailsAction.AppCurrencyAction -> {
|
||||
handleAppCurrencyAction(action, detailsState)
|
||||
}
|
||||
is DetailsAction.ManageSecurity -> {
|
||||
handleSecurityAction(action, detailsState)
|
||||
}
|
||||
|
|
@ -48,10 +44,6 @@ private fun handlePrepareScreen(
|
|||
scanResponse = action.scanResponse,
|
||||
wallets = action.wallets,
|
||||
cardInfo = action.scanResponse.card.toCardInfo(),
|
||||
appCurrencyState = state.appCurrencyState.copy(
|
||||
fiatCurrencyName = action.fiatCurrencyName,
|
||||
showAppCurrencyDialog = false,
|
||||
),
|
||||
cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card),
|
||||
createBackupAllowed = action.scanResponse.card.backupStatus == Card.BackupStatus.NoBackup,
|
||||
)
|
||||
|
|
@ -69,9 +61,7 @@ private fun handleEraseWallet(
|
|||
(card?.isWalletDataSupported == true &&
|
||||
(!state.scanResponse.isTangemNote() && !state.scanResponse.supportsBackup()))
|
||||
|
||||
val notEmpty = state.wallets.any {
|
||||
it.hasPendingTransactions() || it.amounts.toSendableAmounts().isNotEmpty()
|
||||
}
|
||||
val notEmpty = state.wallets.any { it.hasSendableAmountsOrPendingTransactions() }
|
||||
val eraseWalletState = when {
|
||||
notAllowedByCard -> EraseWalletState.NotAllowedByCard
|
||||
notEmpty -> EraseWalletState.NotEmpty
|
||||
|
|
@ -93,30 +83,6 @@ private fun handleEraseWallet(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleAppCurrencyAction(
|
||||
action: DetailsAction.AppCurrencyAction, state: DetailsState,
|
||||
): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.AppCurrencyAction.SetCurrencies -> {
|
||||
state.copy(appCurrencyState = state.appCurrencyState.copy(fiatCurrencies = action.currencies))
|
||||
}
|
||||
DetailsAction.AppCurrencyAction.ChooseAppCurrency -> {
|
||||
state.copy(appCurrencyState = state.appCurrencyState.copy(showAppCurrencyDialog = true))
|
||||
}
|
||||
DetailsAction.AppCurrencyAction.Cancel -> {
|
||||
state.copy(appCurrencyState = state.appCurrencyState.copy(showAppCurrencyDialog = false))
|
||||
}
|
||||
is DetailsAction.AppCurrencyAction.SelectAppCurrency -> {
|
||||
state.copy(
|
||||
appCurrencyState = state.appCurrencyState.copy(
|
||||
fiatCurrencyName = action.fiatCurrencyName, showAppCurrencyDialog = false
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecurityAction(
|
||||
action: DetailsAction.ManageSecurity, state: DetailsState,
|
||||
): DetailsState {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@ package com.tangem.tap.features.details.redux
|
|||
import android.net.Uri
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.tap.common.entities.Button
|
||||
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.StateType
|
||||
|
|
@ -17,7 +14,6 @@ data class DetailsState(
|
|||
val scanResponse: ScanResponse? = null,
|
||||
val wallets: List<Wallet> = emptyList(),
|
||||
val cardInfo: CardInfo? = null,
|
||||
val appCurrencyState: AppCurrencyState = AppCurrencyState(),
|
||||
val eraseWalletState: EraseWalletState? = null,
|
||||
val confirmScreenState: ConfirmScreenState? = null,
|
||||
val securityScreenState: SecurityScreenState? = null,
|
||||
|
|
@ -50,10 +46,4 @@ data class SecurityScreenState(
|
|||
val buttonProceed: Button = Button(true),
|
||||
)
|
||||
|
||||
enum class SecurityOption { LongTap, PassCode, AccessCode }
|
||||
|
||||
data class AppCurrencyState(
|
||||
val fiatCurrencyName: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
|
||||
val showAppCurrencyDialog: Boolean = false,
|
||||
val fiatCurrencies: List<CurrenciesResponse.Currency>? = null,
|
||||
)
|
||||
enum class SecurityOption { LongTap, PassCode, AccessCode }
|
||||
|
|
@ -16,15 +16,17 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.domain.walletconnect.BnbHelper
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
|
|
@ -257,14 +259,16 @@ class WalletConnectMiddleware {
|
|||
blockchainToMake,
|
||||
card.derivationStyle?.let { DerivationParams.Default(it) }
|
||||
)
|
||||
if (currenciesRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
||||
.find { it.blockchain == blockchainToMake } != null
|
||||
) {
|
||||
walletManager?.let {
|
||||
currenciesRepository.saveUpdatedCurrency(
|
||||
card.cardId,
|
||||
BlockchainNetwork.fromWalletManager(walletManager)
|
||||
)
|
||||
scope.launch {
|
||||
if (currenciesRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
||||
.find { it.blockchain == blockchainToMake } != null
|
||||
) {
|
||||
walletManager?.let {
|
||||
currenciesRepository.saveUpdatedCurrency(
|
||||
card.cardId,
|
||||
BlockchainNetwork.fromWalletManager(walletManager)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return walletManager
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class CurrencySelectionDialog {
|
||||
|
||||
var dialog: AlertDialog? = null
|
||||
|
||||
fun show(currenciesList: List<CurrenciesResponse.Currency>, currentAppCurrency: FiatCurrencyName, context: Context) {
|
||||
|
||||
if (dialog == null) {
|
||||
val currenciesToShow = currenciesList.map { it.toFormattedString() }.toTypedArray()
|
||||
var currentSelection = currenciesList.indexOfFirst { it.code == currentAppCurrency }
|
||||
|
||||
dialog = AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.details_row_title_currency))
|
||||
.setNegativeButton(context.getString(R.string.common_cancel)) { _, _ ->
|
||||
store.dispatch(DetailsAction.AppCurrencyAction.Cancel)
|
||||
}
|
||||
.setPositiveButton(context.getString(R.string.common_done)) { _, _ ->
|
||||
val selectedCurrency = currenciesList[currentSelection]
|
||||
store.dispatch(DetailsAction.AppCurrencyAction.SelectAppCurrency(selectedCurrency.code))
|
||||
}
|
||||
.setOnDismissListener {
|
||||
store.dispatch(DetailsAction.AppCurrencyAction.Cancel)
|
||||
}
|
||||
.setSingleChoiceItems(currenciesToShow, currentSelection) { _, which ->
|
||||
currentSelection = which
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
dialog = null
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ import org.rekotlin.StoreSubscriber
|
|||
|
||||
class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<DetailsState> {
|
||||
|
||||
private var currencySelectionDialog = CurrencySelectionDialog()
|
||||
private val binding: FragmentDetailsBinding by viewBinding(FragmentDetailsBinding::bind)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -115,11 +114,6 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
store.dispatch(DetailsAction.CreateBackup)
|
||||
}
|
||||
|
||||
tvAppCurrency.text = state.appCurrencyState.fiatCurrencyName
|
||||
|
||||
tvAppCurrencyTitle.setOnClickListener {
|
||||
store.dispatch(DetailsAction.AppCurrencyAction.ChooseAppCurrency)
|
||||
}
|
||||
tvSendFeedback.setOnClickListener {
|
||||
store.dispatch(GlobalAction.SendFeedback(FeedbackEmail()))
|
||||
}
|
||||
|
|
@ -140,17 +134,6 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
null -> null
|
||||
}
|
||||
currentSecurity?.let { tvSecurity.text = getString(it) }
|
||||
|
||||
if (state.appCurrencyState.showAppCurrencyDialog &&
|
||||
!state.appCurrencyState.fiatCurrencies.isNullOrEmpty()) {
|
||||
currencySelectionDialog.show(
|
||||
state.appCurrencyState.fiatCurrencies,
|
||||
state.appCurrencyState.fiatCurrencyName,
|
||||
requireContext()
|
||||
)
|
||||
} else {
|
||||
currencySelectionDialog.clear()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -8,7 +8,11 @@ import androidx.compose.animation.core.tween
|
|||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import com.tangem.tap.domain.TangemSigner
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.extensions.minimalAmount
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoTransactionSender
|
||||
import com.tangem.tap.features.demo.isDemoWallet
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package com.tangem.tap.features.send.redux.reducers
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.extensions.isAboveZero
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
|
||||
import com.tangem.tap.features.send.redux.FeeAction
|
||||
import com.tangem.tap.features.send.redux.FeeActionUi
|
||||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.states.FeePrecision
|
||||
import com.tangem.tap.features.send.redux.states.FeeState
|
||||
import com.tangem.tap.features.send.redux.states.FeeType
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
|
|
@ -58,7 +61,8 @@ class FeeReducer : SendInternalReducer {
|
|||
selectedFeeType = feeType,
|
||||
feeList = fees,
|
||||
currentFee = currentFee,
|
||||
error = null
|
||||
error = null,
|
||||
feePrecision = getFeePrecision(sendState)
|
||||
)
|
||||
} else {
|
||||
val feeType = getCurrentFeeType(state)
|
||||
|
|
@ -68,7 +72,8 @@ class FeeReducer : SendInternalReducer {
|
|||
selectedFeeType = feeType,
|
||||
feeList = fees,
|
||||
currentFee = currentFee,
|
||||
error = null
|
||||
error = null,
|
||||
feePrecision = getFeePrecision(sendState)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -88,8 +93,6 @@ class FeeReducer : SendInternalReducer {
|
|||
if (list == null || list.isEmpty()) return null
|
||||
|
||||
return if (list.size == 1) {
|
||||
if (!list[0].isAboveZero()) return null
|
||||
|
||||
list[0]
|
||||
} else {
|
||||
when (feeType) {
|
||||
|
|
@ -101,6 +104,17 @@ class FeeReducer : SendInternalReducer {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getFeePrecision(sendState: SendState): FeePrecision {
|
||||
val blockchain = sendState.walletManager?.wallet?.blockchain
|
||||
return if (blockchain?.fullNameWithoutTestnet == Blockchain.Tron.fullName &&
|
||||
sendState.amountState.typeOfAmount is AmountType.Token) {
|
||||
FeePrecision.CAN_BE_LOWER
|
||||
} else {
|
||||
FeePrecision.PRECISE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getCurrentFeeType(state: FeeState): FeeType {
|
||||
return if (state.selectedFeeType == FeeType.SINGLE) FeeType.NORMAL else state.selectedFeeType
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,14 +160,14 @@ class ReceiptReducer : SendInternalReducer {
|
|||
|
||||
ReceiptTokenCrypto(
|
||||
amountToken = tokensToSend.stripZeroPlainString(),
|
||||
feeCoin = feeCoin.stripZeroPlainString(),
|
||||
feeCoin = feeCoin.stripZeroPlainString().addPrecisionSign(),
|
||||
totalFiat = totalFiat.scaleToFiat(true).stripZeroPlainString(),
|
||||
symbols = symbols
|
||||
)
|
||||
} else {
|
||||
ReceiptTokenCrypto(
|
||||
amountToken = tokensToSend.stripZeroPlainString(),
|
||||
feeCoin = feeCoin.stripZeroPlainString(),
|
||||
feeCoin = feeCoin.stripZeroPlainString().addPrecisionSign(),
|
||||
totalFiat = EMPTY,
|
||||
symbols = symbols
|
||||
)
|
||||
|
|
@ -177,7 +177,7 @@ class ReceiptReducer : SendInternalReducer {
|
|||
|
||||
private fun determineSymbols(wallet: Wallet, amountType: AmountType): ReceiptSymbols {
|
||||
return ReceiptSymbols(
|
||||
fiat = store.state.globalState.appCurrency,
|
||||
fiat = store.state.globalState.appCurrency.code,
|
||||
crypto = wallet.blockchain.currency,
|
||||
token = when (amountType) {
|
||||
is AmountType.Token -> amountType.token.symbol
|
||||
|
|
@ -208,4 +208,7 @@ class ReceiptReducer : SendInternalReducer {
|
|||
else -> EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.addPrecisionSign(): String =
|
||||
("${feeState.feePrecision.symbol} $this").trim()
|
||||
}
|
||||
|
|
@ -11,6 +11,10 @@ enum class FeeType {
|
|||
SINGLE, LOW, NORMAL, PRIORITY
|
||||
}
|
||||
|
||||
enum class FeePrecision(val symbol: String) {
|
||||
PRECISE(""), CAN_BE_LOWER("<")
|
||||
}
|
||||
|
||||
data class FeeState(
|
||||
val selectedFeeType: FeeType = FeeType.NORMAL,
|
||||
val feeList: List<Amount>? = null,
|
||||
|
|
@ -21,6 +25,7 @@ data class FeeState(
|
|||
val feeChipGroupIsVisible: Boolean = true,
|
||||
val includeFeeSwitcherIsEnabled: Boolean = true,
|
||||
val error: FeeAction.Error? = null,
|
||||
val feePrecision: FeePrecision = FeePrecision.PRECISE
|
||||
) : SendScreenState {
|
||||
|
||||
override val stateId: StateId = StateId.FEE
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import com.tangem.blockchain.common.AmountType
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.CurrencyConverter
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
import com.tangem.tap.common.entities.TapCurrency
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.text.DecimalDigitsInputFilter
|
||||
import com.tangem.tap.domain.TapError
|
||||
|
|
@ -123,18 +123,18 @@ enum class ButtonState {
|
|||
}
|
||||
|
||||
data class AmountState(
|
||||
val amountToExtract: Amount? = null,
|
||||
val typeOfAmount: AmountType = AmountType.Coin,
|
||||
val viewAmountValue: InputViewValue = InputViewValue(BigDecimal.ZERO.toPlainString()),
|
||||
val viewBalanceValue: String = BigDecimal.ZERO.toPlainString(),
|
||||
val mainCurrency: MainCurrency = MainCurrency(MainCurrencyType.FIAT, TapCurrency.DEFAULT_FIAT_CURRENCY),
|
||||
val amountToSendCrypto: BigDecimal = BigDecimal.ZERO,
|
||||
val balanceCrypto: BigDecimal = BigDecimal.ZERO,
|
||||
val cursorAtTheSamePosition: Boolean = true,
|
||||
val maxLengthOfAmount: Int = 2,
|
||||
val decimalSeparator: String = ".",
|
||||
val error: TapError? = null,
|
||||
val inputIsEnabled: Boolean = true,
|
||||
val amountToExtract: Amount? = null,
|
||||
val typeOfAmount: AmountType = AmountType.Coin,
|
||||
val viewAmountValue: InputViewValue = InputViewValue(BigDecimal.ZERO.toPlainString()),
|
||||
val viewBalanceValue: String = BigDecimal.ZERO.toPlainString(),
|
||||
val mainCurrency: MainCurrency = MainCurrency(MainCurrencyType.FIAT, FiatCurrency.Default.code),
|
||||
val amountToSendCrypto: BigDecimal = BigDecimal.ZERO,
|
||||
val balanceCrypto: BigDecimal = BigDecimal.ZERO,
|
||||
val cursorAtTheSamePosition: Boolean = true,
|
||||
val maxLengthOfAmount: Int = 2,
|
||||
val decimalSeparator: String = ".",
|
||||
val error: TapError? = null,
|
||||
val inputIsEnabled: Boolean = true,
|
||||
) : SendScreenState {
|
||||
|
||||
override val stateId: StateId = StateId.AMOUNT
|
||||
|
|
@ -146,7 +146,7 @@ data class AmountState(
|
|||
fun createMainCurrency(type: MainCurrencyType, canSwitched: Boolean): MainCurrency {
|
||||
return if (!canSwitched) MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE", false)
|
||||
else when (type) {
|
||||
MainCurrencyType.FIAT -> MainCurrency(type, store.state.globalState.appCurrency)
|
||||
MainCurrencyType.FIAT -> MainCurrency(type, store.state.globalState.appCurrency.code)
|
||||
MainCurrencyType.CRYPTO -> MainCurrency(type, amountToExtract?.currencySymbol ?: "NONE")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.tangem.Message
|
||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
||||
import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard
|
||||
import com.tangem.tap.common.KeyboardObserver
|
||||
import com.tangem.tap.common.entities.TapCurrency
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
import com.tangem.tap.common.extensions.setOnImeActionListener
|
||||
import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.common.snackBar.MaxAmountSnackbar
|
||||
import com.tangem.tap.common.text.truncateMiddleWith
|
||||
|
|
@ -36,7 +36,6 @@ import com.tangem.tap.features.send.redux.states.FeeType
|
|||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.ui.adapters.SpacesItemDecoration
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -244,7 +243,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
warningsAdapter = WarningMessagesAdapter()
|
||||
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||
rvWarningMessages.layoutManager = layoutManager
|
||||
rvWarningMessages.addItemDecoration(SpacesItemDecoration(rvWarningMessages.dpToPx(16f).toInt()))
|
||||
rvWarningMessages.addItemDecoration(SpaceItemDecoration.all(16f))
|
||||
rvWarningMessages.adapter = warningsAdapter
|
||||
|
||||
store.dispatch(SendAction.Warnings.Update)
|
||||
|
|
@ -261,11 +260,10 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
|
||||
private fun restoreMainCurrency(): MainCurrencyType {
|
||||
val sp = requireContext().getSharedPreferences("SendScreen", Context.MODE_PRIVATE)
|
||||
val mainCurrency = sp.getString("mainCurrency", TapCurrency.DEFAULT_FIAT_CURRENCY)
|
||||
val foundType = MainCurrencyType.values()
|
||||
.firstOrNull { it.name.equals(mainCurrency!!, ignoreCase = true) }
|
||||
?: MainCurrencyType.CRYPTO
|
||||
return foundType
|
||||
val mainCurrency = sp.getString("mainCurrency", FiatCurrency.Default.code)
|
||||
return MainCurrencyType.values()
|
||||
.firstOrNull { it.name.equals(mainCurrency!!, ignoreCase = true) }
|
||||
?: MainCurrencyType.CRYPTO
|
||||
}
|
||||
|
||||
fun saveMainCurrency(type: MainCurrencyType) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ import androidx.compose.ui.platform.ComposeView
|
|||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.ClosePopupTrigger
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.AddCustomTokenScreen
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -48,16 +50,25 @@ class AddCustomTokenFragment : BaseStoreFragment(R.layout.view_compose_fragment)
|
|||
it.setTitle(R.string.add_custom_token_title)
|
||||
}
|
||||
|
||||
val closePopupTrigger = initClosingPopupTriggerEvent()
|
||||
view.findViewById<ComposeView>(R.id.view_compose)?.setContent {
|
||||
AppCompatTheme(requireContext()) {
|
||||
Box(modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
AddCustomTokenScreen(state)
|
||||
AddCustomTokenScreen(state, closePopupTrigger)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
addBackPressHandler(this)
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun initClosingPopupTriggerEvent(): ClosePopupTrigger = ClosePopupTrigger().apply {
|
||||
onCloseComplete = ::handleOnBackPressed
|
||||
addBackPressHandler(object : FragmentOnBackPressedHandler {
|
||||
override fun handleOnBackPressed() = close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -21,21 +21,69 @@ import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
|||
import com.tangem.domain.features.addCustomToken.redux.ScreenState
|
||||
import com.tangem.domain.features.addCustomToken.redux.ViewStates
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.AddCustomTokenWarning
|
||||
import com.tangem.tap.common.compose.ComposeDialogManager
|
||||
import com.tangem.tap.common.compose.ToggledRippleTheme
|
||||
import com.tangem.tap.common.compose.keyboardAsState
|
||||
import com.tangem.tap.common.compose.*
|
||||
import com.tangem.tap.common.moduleMessage.ModuleMessageConverter
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestAddCustomTokenActions
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCase
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCasesList
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
private class AddCustomTokenScreen {} // for simple search
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
||||
fun AddCustomTokenScreen(
|
||||
state: MutableState<AddCustomTokenState>,
|
||||
closePopupTrigger: ClosePopupTrigger,
|
||||
) {
|
||||
val selectedTestCase = remember { mutableStateOf(TestCase.ContractAddress) }
|
||||
|
||||
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed)
|
||||
)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val toggleBottomSheet = { coroutineScope.launch { bottomSheetScaffoldState.toggle() } }
|
||||
|
||||
BottomSheetScaffold(
|
||||
scaffoldState = bottomSheetScaffoldState,
|
||||
sheetContent = {
|
||||
Surface(color = colorResource(id = R.color.lightGray5)) {
|
||||
selectedTestCase.value.content(toggleBottomSheet)
|
||||
}
|
||||
},
|
||||
sheetPeekHeight = 0.dp,
|
||||
) {
|
||||
Column() {
|
||||
TestCasesList(onItemClick = {
|
||||
selectedTestCase.value = it
|
||||
toggleBottomSheet()
|
||||
})
|
||||
ScreenContent(state, closePopupTrigger)
|
||||
}
|
||||
}
|
||||
|
||||
ComposeDialogManager()
|
||||
LaunchedEffect(key1 = Unit, block = { domainStore.dispatch(AddCustomTokenAction.OnCreate) })
|
||||
DisposableEffect(key1 = Unit, effect = { onDispose { domainStore.dispatch(AddCustomTokenAction.OnDestroy) } })
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
private suspend fun BottomSheetScaffoldState.toggle() {
|
||||
if (bottomSheetState.isCollapsed) {
|
||||
bottomSheetState.expand()
|
||||
} else {
|
||||
bottomSheetState.collapse()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScreenContent(
|
||||
state: MutableState<AddCustomTokenState>,
|
||||
closePopupTrigger: ClosePopupTrigger,
|
||||
) {
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
|
||||
Scaffold(
|
||||
|
|
@ -50,9 +98,8 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
|||
) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = 90.dp)
|
||||
contentPadding = PaddingValues(bottom = 90.dp),
|
||||
) {
|
||||
item { TestAddCustomTokenActions() }
|
||||
item {
|
||||
Surface(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
|
|
@ -64,22 +111,21 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
|||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
FormFields(state)
|
||||
FormFields(state, closePopupTrigger)
|
||||
}
|
||||
}
|
||||
}
|
||||
item { Warnings(state.value.warnings.toList()) }
|
||||
}
|
||||
}
|
||||
ComposeDialogManager()
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = Unit, block = { domainStore.dispatch(AddCustomTokenAction.OnCreate) })
|
||||
DisposableEffect(key1 = Unit, effect = { onDispose { domainStore.dispatch(AddCustomTokenAction.OnDestroy) } })
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FormFields(state: MutableState<AddCustomTokenState>) {
|
||||
private fun FormFields(
|
||||
state: MutableState<AddCustomTokenState>,
|
||||
closePopupTrigger: ClosePopupTrigger,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val errorConverter = remember { ModuleMessageConverter(context) }
|
||||
|
||||
|
|
@ -88,11 +134,11 @@ private fun FormFields(state: MutableState<AddCustomTokenState>) {
|
|||
val data = ScreenFieldData.fromState(field, stateValue, errorConverter)
|
||||
when (field.id) {
|
||||
ContractAddress -> TokenContractAddressView(data)
|
||||
Network -> TokenNetworkView(data, stateValue)
|
||||
Network -> TokenNetworkView(data, stateValue, closePopupTrigger)
|
||||
Name -> TokenNameView(data)
|
||||
Symbol -> TokenSymbolView(data)
|
||||
Decimals -> TokenDecimalsView(data)
|
||||
DerivationPath -> TokenDerivationPathView(data, stateValue)
|
||||
DerivationPath -> TokenDerivationPathView(data, stateValue, closePopupTrigger)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,11 @@ fun TokenNameView(screenFieldData: ScreenFieldData) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun TokenNetworkView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
|
||||
fun TokenNetworkView(
|
||||
screenFieldData: ScreenFieldData,
|
||||
state: AddCustomTokenState,
|
||||
closePopupTrigger: ClosePopupTrigger,
|
||||
) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_network_input_not_selected)
|
||||
|
|
@ -98,6 +102,7 @@ fun TokenNetworkView(screenFieldData: ScreenFieldData, state: AddCustomTokenStat
|
|||
selectedItem = networkField.data,
|
||||
isEnabled = screenFieldData.viewState.isEnabled,
|
||||
textFieldConverter = { state.blockchainToName(it) ?: notSelected },
|
||||
closePopupTrigger = closePopupTrigger,
|
||||
) { domainStore.dispatch(OnTokenNetworkChanged(Field.Data(it, true))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -138,7 +143,11 @@ fun TokenDecimalsView(screenFieldData: ScreenFieldData) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun TokenDerivationPathView(screenFieldData: ScreenFieldData, state: AddCustomTokenState) {
|
||||
fun TokenDerivationPathView(
|
||||
screenFieldData: ScreenFieldData,
|
||||
state: AddCustomTokenState,
|
||||
closePopupTrigger: ClosePopupTrigger,
|
||||
) {
|
||||
if (!screenFieldData.viewState.isVisible) return
|
||||
|
||||
val notSelected = stringResource(id = R.string.custom_token_derivation_path_default)
|
||||
|
|
@ -154,7 +163,8 @@ fun TokenDerivationPathView(screenFieldData: ScreenFieldData, state: AddCustomTo
|
|||
val derivationPathName = state.blockchainToName(blockchain, true) ?: notSelected
|
||||
val blockchainName = state.blockchainToName(blockchain) ?: notSelected
|
||||
TitleSubtitle(derivationPathName, blockchainName)
|
||||
}
|
||||
},
|
||||
closePopupTrigger = closePopupTrigger,
|
||||
) { domainStore.dispatch(OnTokenDerivationPathChanged(Field.Data(it, true))) }
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose.test
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.redux.domainStore
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun ContractAddressTests(
|
||||
onItemClick: VoidCallback
|
||||
) {
|
||||
val casesInfo = listOf(
|
||||
"USDC on ETH" to "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||
"BUSD on ETH" to "0x4fabb145d64652a948d72533023f6e7a623c7c53",
|
||||
"ETH on AVALANCHE" to "0xf20d962a6c8f70c731bd838a3a388d7d48fa6e15",
|
||||
"USDC on ETH (invalid - cut address)" to "0xa0b86991c6218b36c1d1",
|
||||
"Custom EVM" to "0x1111111111111111112111111111111111111113",
|
||||
"Supported by several networks" to "0xa1faa113cbe53436df28ff0aee54275c13b40975",
|
||||
"Invalid" to "!@#_ _-%%^&&*((){P P2iOWsdfFQLA",
|
||||
)
|
||||
CasesListContent(casesInfo, onItemClick)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SolanaAddressTests(
|
||||
onItemClick: VoidCallback
|
||||
) {
|
||||
val casesInfo = listOf(
|
||||
"USDT (full)" to "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
|
||||
"USDT (valid - 2/3 of address)" to "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8Ben",
|
||||
"USDT (invalid - 1/3 of address)" to "Es9vMFrzaCERmJ",
|
||||
"ETH (full)" to "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk",
|
||||
)
|
||||
CasesListContent(casesInfo, onItemClick)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CasesListContent(
|
||||
casesList: List<Pair<String, String>>,
|
||||
onItemClick: VoidCallback,
|
||||
) {
|
||||
LazyColumn(content = {
|
||||
item {
|
||||
Row() {
|
||||
ResetContractAddressButton(onItemClick)
|
||||
Text("", modifier = Modifier.weight(1f))
|
||||
ResetAllFieldsButton(onItemClick)
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
items(casesList.size) {
|
||||
val (info, address) = casesList[it]
|
||||
ContractAddressButton(info, address, onItemClick)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ResetAllFieldsButton(
|
||||
onItemClick: VoidCallback
|
||||
) {
|
||||
ActionButton(name = "Reset") {
|
||||
onItemClick()
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(Blockchain.Unknown, false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data("", false)))
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenDerivationPathChanged(Field.Data(Blockchain.Unknown, false)))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ResetContractAddressButton(
|
||||
onItemClick: VoidCallback
|
||||
) {
|
||||
ActionButton(name = "Set empty address") {
|
||||
onItemClick()
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data("", false)))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContractAddressButton(
|
||||
name: String,
|
||||
address: String,
|
||||
onItemClick: VoidCallback
|
||||
) {
|
||||
ActionButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
name = name,
|
||||
) {
|
||||
onItemClick()
|
||||
domainStore.dispatch(AddCustomTokenAction.OnTokenContractAddressChanged(Field.Data(address, false)))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActionButton(
|
||||
modifier: Modifier = Modifier,
|
||||
name: String,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier.padding(horizontal = 8.dp),
|
||||
onClick = onClick
|
||||
) { Text(name, fontSize = 12.sp) }
|
||||
}
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose.test
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.*
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TestAddCustomTokenActions() {
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
|
||||
Column() {
|
||||
// deep test
|
||||
// ActionRow("Invalid field value") { InvalidFieldValue() }
|
||||
// ActionRow("Active(true)") { ActiveTrue() }
|
||||
// ActionRow("Active(false) && decimalCount != null") { ActiveFalseDecimals() }
|
||||
// ActionRow("In several networks") { InSeveralNetworks() }
|
||||
// ActionRow("Address not found") { UnknownContracts() }
|
||||
|
||||
// test
|
||||
ActionRow("All in one") { AllInOne() }
|
||||
|
||||
// Any action
|
||||
// ActionRow("CustomActions - find coins active=false, decimals != null") { CustomActions() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AllInOne() {
|
||||
// validation error
|
||||
// ContractAddressButton(
|
||||
// name = "invalid",
|
||||
// address = "unk"
|
||||
// )
|
||||
// active = true
|
||||
ContractAddressButton(
|
||||
name = "true",
|
||||
address = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
||||
)
|
||||
// more than one network
|
||||
ContractAddressButton(
|
||||
name = ">1 network",
|
||||
address = "0xa1faa113cbe53436df28ff0aee54275c13b40975"
|
||||
)
|
||||
// unknown
|
||||
ContractAddressButton(
|
||||
name = "unknown",
|
||||
address = "0x1111111111111111112111111111111111111113"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "full custom",
|
||||
address = "0x3019BF2a2eF8040C242C9a4c5c4BD4C81678b2A1",
|
||||
tokenNetwork = Blockchain.Ethereum,
|
||||
tokenName = "Test unknown",
|
||||
tokenSymbol = "TU",
|
||||
tokenDecimals = "5",
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InvalidFieldValue() {
|
||||
ContractAddressButton(
|
||||
name = "unk",
|
||||
address = "unk"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "someText",
|
||||
address = "someText"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "alskdml...fa s",
|
||||
address = "alskdmlasd asdln alsdknflasd flasd fa s"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActiveTrue() {
|
||||
ContractAddressButton(
|
||||
name = "USDC- Ethereum",
|
||||
address = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "USDT- Avalanche",
|
||||
address = "0xc7198437980c041c805a1edcba50c1ce5db95118"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "VID- Fantom",
|
||||
address = "0x922d641a426dcffaef11680e5358f34d97d112e1"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActiveFalseDecimals() {
|
||||
ContractAddressButton(
|
||||
name = "ALPHA- avalanche",
|
||||
address = "0x2147efff675e4a4ee1c2f918d181cdbd7a8e208f"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "BETA- avalanche",
|
||||
address = "0x511d35c52a3c244e7b8bd92c0c297755fbd89212"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InSeveralNetworks() {
|
||||
ContractAddressButton(
|
||||
name = "ALPHA- Eth,Bsc",
|
||||
address = "0xa1faa113cbe53436df28ff0aee54275c13b40975"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "BETA- Eth,Bsc",
|
||||
address = "0xbe1a001fe942f96eea22ba08783140b9dcc09d28"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnknownContracts() {
|
||||
ContractAddressButton(
|
||||
name = "0x11...113",
|
||||
address = "0x1111111111111111112111111111111111111113"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "0xc7...111",
|
||||
address = "0xc7198437980c041c805a1edcba50c1ce5db95111"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CustomActions() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionRow(
|
||||
name: String,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Column() {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = name,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) { content() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionButton(
|
||||
name: String,
|
||||
onClick: VoidCallback,
|
||||
) {
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
onClick = onClick
|
||||
) { Text(name, fontSize = 8.sp) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContractAddressButton(
|
||||
name: String,
|
||||
address: String,
|
||||
tokenNetwork: Blockchain? = null,
|
||||
tokenName: String? = null,
|
||||
tokenSymbol: String? = null,
|
||||
tokenDecimals: String? = null,
|
||||
) {
|
||||
ActionButton(name = name) {
|
||||
resetTokenValues()
|
||||
domainStore.dispatch(OnTokenContractAddressChanged(Field.Data(address, false)))
|
||||
tokenNetwork?.let {
|
||||
domainStore.dispatch(OnTokenNetworkChanged(Field.Data(tokenNetwork, false)))
|
||||
}
|
||||
tokenName?.let {
|
||||
domainStore.dispatch(OnTokenNameChanged(Field.Data(tokenName, false)))
|
||||
}
|
||||
tokenSymbol?.let {
|
||||
domainStore.dispatch(OnTokenSymbolChanged(Field.Data(tokenSymbol, false)))
|
||||
}
|
||||
tokenDecimals?.let {
|
||||
domainStore.dispatch(OnTokenDecimalsChanged(Field.Data(tokenDecimals, false)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CustomActionButton(
|
||||
name: String,
|
||||
action: suspend () -> Unit
|
||||
) {
|
||||
val startValue = 0
|
||||
val anyValue = remember { mutableStateOf(startValue) }
|
||||
LaunchedEffect(key1 = anyValue.value, block = {
|
||||
if (anyValue.value != startValue) {
|
||||
action()
|
||||
}
|
||||
})
|
||||
|
||||
ActionButton(name) { anyValue.value = anyValue.value + 1 }
|
||||
}
|
||||
|
||||
private fun resetTokenValues() {
|
||||
domainStore.dispatch(OnTokenNetworkChanged(Field.Data(Blockchain.Unknown, false)))
|
||||
// domainStore.dispatch(AddCustomTokenAction.OnTokenNameChanged(Field.Data("", false)))
|
||||
// domainStore.dispatch(AddCustomTokenAction.OnTokenSymbolChanged(Field.Data("", false)))
|
||||
// domainStore.dispatch(AddCustomTokenAction.OnTokenDecimalsChanged(Field.Data("", false)))
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose.test
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun TestCasesList(
|
||||
onItemClick: (TestCase) -> Unit
|
||||
) {
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
|
||||
Surface(
|
||||
color = colorResource(id = R.color.lightGray5)
|
||||
) {
|
||||
Column(
|
||||
Modifier.padding(horizontal = 16.dp)
|
||||
) {
|
||||
listOf(
|
||||
TestCase.ContractAddress,
|
||||
TestCase.SolanaTokens,
|
||||
).map { TestCaseListItem(it, onItemClick) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TestCaseListItem(
|
||||
testCase: TestCase,
|
||||
onItemClick: (TestCase) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = testCase.description,
|
||||
)
|
||||
Button(
|
||||
onClick = { onItemClick(testCase) }
|
||||
) { Text("Start") }
|
||||
}
|
||||
}
|
||||
|
||||
enum class TestCase(val description: String, val content: @Composable (VoidCallback) -> Unit) {
|
||||
ContractAddress("Test contract address field", { ContractAddressTests(it) }),
|
||||
Auto("Test contract address field", { ContractAddressTests(it) }),
|
||||
SolanaTokens("Test Solana contract addresses", { SolanaAddressTests(it) }), ;
|
||||
}
|
||||
|
|
@ -17,7 +17,8 @@ sealed class TokensAction : Action {
|
|||
val supportedBlockchains: List<Blockchain>? = null,
|
||||
val scanResponse: ScanResponse? = null
|
||||
) : TokensAction() {
|
||||
data class Success(val currencies: List<Currency>) : TokensAction()
|
||||
data class Success(val currencies: List<Currency>, val loadMore: Boolean) : TokensAction()
|
||||
object Failure : TokensAction()
|
||||
}
|
||||
|
||||
data class SetAddedCurrencies(
|
||||
|
|
@ -32,4 +33,6 @@ sealed class TokensAction : Action {
|
|||
) : TokensAction()
|
||||
|
||||
object PrepareAndNavigateToAddCustomToken : TokensAction()
|
||||
|
||||
data class SetSearchInput(val searchInput: String) : TokensAction()
|
||||
}
|
||||
|
|
@ -8,11 +8,13 @@ import com.tangem.common.card.EllipticCurve
|
|||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.DomainWrapped
|
||||
import com.tangem.domain.common.KeyWalletPublicKey
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.redux.domainStore
|
||||
|
|
@ -26,10 +28,10 @@ import com.tangem.tap.common.redux.navigation.AppScreen
|
|||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -40,27 +42,65 @@ class TokensMiddleware {
|
|||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action.scanResponse)
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
is TokensAction.PrepareAndNavigateToAddCustomToken -> handleAddingCustomToken(action)
|
||||
is TokensAction.PrepareAndNavigateToAddCustomToken -> handleAddingCustomToken(
|
||||
action
|
||||
)
|
||||
is TokensAction.SetSearchInput -> {
|
||||
handleLoadCurrencies(
|
||||
scanResponse = store.state.globalState.scanResponse,
|
||||
action.searchInput
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) {
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
private fun handleLoadCurrencies(scanResponse: ScanResponse?, newSearchInput: String? = null) {
|
||||
val tokensState = store.state.tokensState
|
||||
|
||||
val isTestcard = scanResponse?.card?.isTestCard ?: false
|
||||
|
||||
val supportedBlockchains: List<Blockchain> =
|
||||
scanResponse?.card?.supportedBlockchains() ?: Blockchain.values().toList()
|
||||
.filter { !it.isTestnet() }
|
||||
|
||||
val loadCoinsService = LoadAvailableCoinsService(
|
||||
store.state.domainNetworks.tangemTechService,
|
||||
currenciesRepository
|
||||
)
|
||||
|
||||
scope.launch {
|
||||
val currencies = async {
|
||||
currenciesRepository.getSupportedTokens(isTestcard)
|
||||
.filter(action.supportedBlockchains?.toSet())
|
||||
|
||||
val loadCoinsResult = if (newSearchInput == null) {
|
||||
loadCoinsService.getSupportedTokens(
|
||||
isTestcard,
|
||||
supportedBlockchains,
|
||||
tokensState.pageToLoad,
|
||||
tokensState.searchInput
|
||||
)
|
||||
} else {
|
||||
loadCoinsService.getSupportedTokens(
|
||||
isTestcard, supportedBlockchains, 0, newSearchInput.ifBlank { null }
|
||||
)
|
||||
}
|
||||
val delay = async { delay(600) }
|
||||
delay.await()
|
||||
store.dispatchOnMain(TokensAction.LoadCurrencies.Success(currencies.await()))
|
||||
when (loadCoinsResult) {
|
||||
is Result.Success -> {
|
||||
val currencies = loadCoinsResult.data.currencies
|
||||
.filter(supportedBlockchains.toSet())
|
||||
store.dispatchOnMain(
|
||||
TokensAction.LoadCurrencies.Success(
|
||||
currencies, loadCoinsResult.data.moreAvailable
|
||||
)
|
||||
)
|
||||
}
|
||||
is Result.Failure -> store.dispatchOnMain(TokensAction.LoadCurrencies.Failure)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,19 +115,22 @@ class TokensMiddleware {
|
|||
)
|
||||
|
||||
val blockchainsToAdd = action.addedBlockchains.filter { !currentBlockchains.contains(it) }
|
||||
val blockchainsToRemove = currentBlockchains.filter { !action.addedBlockchains.contains(it) }
|
||||
val blockchainsToRemove =
|
||||
currentBlockchains.filter { !action.addedBlockchains.contains(it) }
|
||||
|
||||
val tokensToAdd = action.addedTokens.filter { !currentTokens.contains(it) }
|
||||
val tokensToRemove = currentTokens.filter {
|
||||
token -> !action.addedTokens.any { it.token == token.token }
|
||||
val tokensToRemove = currentTokens.filter { token ->
|
||||
!action.addedTokens.any { it.token == token.token }
|
||||
}
|
||||
val derivationStyle = scanResponse.card.derivationStyle
|
||||
|
||||
removeCurrenciesIfNeeded(convertToCurrencies(
|
||||
blockchains = blockchainsToRemove,
|
||||
tokens = tokensToRemove,
|
||||
derivationStyle = derivationStyle
|
||||
))
|
||||
removeCurrenciesIfNeeded(
|
||||
convertToCurrencies(
|
||||
blockchains = blockchainsToRemove,
|
||||
tokens = tokensToRemove,
|
||||
derivationStyle = derivationStyle
|
||||
)
|
||||
)
|
||||
|
||||
if (tokensToAdd.isEmpty() && blockchainsToAdd.isEmpty()) {
|
||||
store.dispatchDebugErrorNotification("Nothing to save")
|
||||
|
|
@ -247,7 +290,8 @@ class TokensMiddleware {
|
|||
val rawDerivationPath = currency.derivationPath
|
||||
?: currency.blockchain.derivationPath(derivationStyle)?.rawPath
|
||||
|
||||
val blockchainNetwork = BlockchainNetwork(currency.blockchain, rawDerivationPath, emptyList())
|
||||
val blockchainNetwork =
|
||||
BlockchainNetwork(currency.blockchain, rawDerivationPath, emptyList())
|
||||
WalletAction.MultiWallet.AddToken(currency.token, blockchainNetwork)
|
||||
}
|
||||
}
|
||||
|
|
@ -298,8 +342,15 @@ class TokensMiddleware {
|
|||
walletStore.walletsData.map { walletData -> walletData.currency }
|
||||
}.flatten().map {
|
||||
when (it) {
|
||||
is Currency.Blockchain -> DomainWrapped.Currency.Blockchain(it.blockchain, it.derivationPath)
|
||||
is Currency.Token -> DomainWrapped.Currency.Token(it.token, it.blockchain, it.derivationPath)
|
||||
is Currency.Blockchain -> DomainWrapped.Currency.Blockchain(
|
||||
it.blockchain,
|
||||
it.derivationPath
|
||||
)
|
||||
is Currency.Token -> DomainWrapped.Currency.Token(
|
||||
it.token,
|
||||
it.blockchain,
|
||||
it.derivationPath
|
||||
)
|
||||
}
|
||||
}
|
||||
domainStore.dispatch(AddCustomTokenAction.Init.SetAddedCurrencies(addedCurrencies))
|
||||
|
|
|
|||
|
|
@ -15,9 +15,24 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
|
|||
val tokensState = state.tokensState
|
||||
return when (action) {
|
||||
is TokensAction.ResetState -> TokensState()
|
||||
is TokensAction.LoadCurrencies -> tokensState.copy(scanResponse = action.scanResponse)
|
||||
is TokensAction.LoadCurrencies -> {
|
||||
val loadingState = if (tokensState.currencies.isEmpty()) {
|
||||
LoadCoinsState.LOADING
|
||||
} else {
|
||||
LoadCoinsState.LOADED
|
||||
}
|
||||
tokensState.copy(
|
||||
scanResponse = action.scanResponse,
|
||||
loadCoinsState = loadingState
|
||||
)
|
||||
}
|
||||
is TokensAction.LoadCurrencies.Success -> {
|
||||
tokensState.copy(currencies = action.currencies)
|
||||
tokensState.copy(
|
||||
currencies = tokensState.currencies + action.currencies,
|
||||
needToLoadMore = action.loadMore,
|
||||
pageToLoad = tokensState.pageToLoad + 1,
|
||||
loadCoinsState = LoadCoinsState.LOADED
|
||||
)
|
||||
}
|
||||
is TokensAction.SetAddedCurrencies -> {
|
||||
|
||||
|
|
@ -38,6 +53,15 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
|
|||
is TokensAction.AllowToAddTokens -> {
|
||||
tokensState.copy(allowToAdd = action.allow)
|
||||
}
|
||||
is TokensAction.SetSearchInput -> {
|
||||
tokensState.copy(
|
||||
searchInput = action.searchInput.ifBlank { null },
|
||||
needToLoadMore = true,
|
||||
currencies = emptyList(),
|
||||
pageToLoad = 0,
|
||||
loadCoinsState = LoadCoinsState.LOADING
|
||||
)
|
||||
}
|
||||
else -> tokensState
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
|
|
@ -16,10 +17,19 @@ data class TokensState(
|
|||
val nonRemovableTokens: List<ContractAddress> = emptyList(),
|
||||
val nonRemovableBlockchains: List<Blockchain> = emptyList(),
|
||||
val currencies: List<Currency> = emptyList(),
|
||||
val searchInput: String? = null,
|
||||
val allowToAdd: Boolean = true,
|
||||
val derivationStyle: DerivationStyle? = null,
|
||||
val scanResponse: ScanResponse? = null
|
||||
) : StateType
|
||||
val scanResponse: ScanResponse? = null,
|
||||
val needToLoadMore: Boolean = true,
|
||||
val pageToLoad: Int = 0,
|
||||
val loadCoinsState: LoadCoinsState = LoadCoinsState.LOADING,
|
||||
) : StateType {
|
||||
|
||||
fun canHandleToken(token: TokenWithBlockchain): Boolean {
|
||||
return scanResponse?.card?.canHandleToken(token.blockchain) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
typealias ContractAddress = String
|
||||
|
||||
|
|
@ -62,10 +72,14 @@ fun List<Currency>.filter(supportedBlockchains: Set<Blockchain>?): List<Currency
|
|||
it.copy(contracts =
|
||||
it.contracts.filter {
|
||||
supportedBlockchains.contains(it.blockchain) &&
|
||||
(it.blockchain.canHandleTokens() || it.address == null)
|
||||
(it.blockchain.canHandleTokens() || it.address == null)
|
||||
}
|
||||
)
|
||||
}.filterNot {
|
||||
it.contracts.isNullOrEmpty() && !supportedBlockchains.contains(Blockchain.fromNetworkId(it.id))
|
||||
}
|
||||
}
|
||||
|
||||
enum class LoadCoinsState {
|
||||
LOADING, LOADED, ERROR
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import com.google.accompanist.appcompattheme.AppCompatTheme
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
|
|
@ -46,6 +47,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(TokensAction.ResetState)
|
||||
}
|
||||
})
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
|
|
@ -81,13 +83,17 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
store.dispatchNotification(R.string.contract_address_copied_message)
|
||||
}
|
||||
|
||||
val onLoadMore = {
|
||||
store.dispatch(TokensAction.LoadCurrencies(scanResponse = store.state.globalState.scanResponse))
|
||||
}
|
||||
|
||||
cvCurrencies.setContent {
|
||||
AppCompatTheme {
|
||||
CurrenciesScreen(
|
||||
tokensState = tokensState,
|
||||
searchInput = searchInput,
|
||||
onSaveChanges = onSaveChanges,
|
||||
onNetworkItemClicked = onNetworkItemClicked
|
||||
onNetworkItemClicked = onNetworkItemClicked,
|
||||
onLoadMore = onLoadMore
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -123,10 +129,11 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
searchView.queryHint = searchView.getString(R.string.add_token_search_hint)
|
||||
searchView.maxWidth = android.R.attr.width
|
||||
searchView.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.debounce(800)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
searchInput.value = it.lowercase()
|
||||
dispatchOnMain(TokensAction.SetSearchInput(searchInput.value))
|
||||
}
|
||||
.launchIn(mainScope)
|
||||
return super.onCreateOptionsMenu(menu, inflater)
|
||||
|
|
|
|||
|
|
@ -22,9 +22,12 @@ import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
|||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.common.compose.Keyboard
|
||||
import com.tangem.tap.common.compose.keyboardAsState
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.pixelsToDp
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.LoadCoinsState
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -33,11 +36,11 @@ import com.tangem.wallet.R
|
|||
@Composable
|
||||
fun CurrenciesScreen(
|
||||
tokensState: MutableState<TokensState> = mutableStateOf(store.state.tokensState),
|
||||
searchInput: MutableState<String>,
|
||||
onSaveChanges: (List<TokenWithBlockchain>, List<Blockchain>) -> Unit,
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit,
|
||||
onLoadMore: () -> Unit
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val addedTokensState = remember { mutableStateOf(tokensState.value.addedTokens) }
|
||||
val addedBlockchainsState = remember { mutableStateOf(tokensState.value.addedBlockchains) }
|
||||
|
||||
|
|
@ -74,10 +77,10 @@ fun CurrenciesScreen(
|
|||
) {
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = tokensState.value.currencies.isEmpty(),
|
||||
visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADING,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
){
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
|
|
@ -88,22 +91,35 @@ fun CurrenciesScreen(
|
|||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = tokensState.value.currencies.isNotEmpty(),
|
||||
visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADED,
|
||||
enter = fadeIn(animationSpec = tween(1000)),
|
||||
exit = fadeOut(animationSpec = tween(1000))
|
||||
) {
|
||||
Column {
|
||||
if (tokensState.value.scanResponse?.card?.useOldStyleDerivation == true) CurrenciesWarning()
|
||||
val showHeader = tokensState.value.scanResponse?.card?.useOldStyleDerivation == true
|
||||
ListOfCurrencies(
|
||||
header = { if (showHeader) CurrenciesWarning() },
|
||||
currencies = tokensState.value.currencies,
|
||||
nonRemovableTokens = tokensState.value.nonRemovableTokens,
|
||||
nonRemovableBlockchains = tokensState.value.nonRemovableBlockchains,
|
||||
addedTokens = addedTokensState.value,
|
||||
addedBlockchains = addedBlockchainsState.value,
|
||||
searchInput = searchInput.value,
|
||||
allowToAdd = tokensState.value.allowToAdd,
|
||||
onAddCurrencyToggled = onAddCurrencyToggleClick,
|
||||
onNetworkItemClicked = onNetworkItemClicked
|
||||
onAddCurrencyToggled = { currency, token ->
|
||||
onAddCurrencyToggleClick(currency, token)
|
||||
token?.let {
|
||||
if (!tokensState.value.canHandleToken(it)) {
|
||||
val dialog = AppDialog.SimpleOkDialog(
|
||||
header = context.getString(R.string.common_warning),
|
||||
message = context.getString(R.string.alert_manage_tokens_unsupported_message)
|
||||
) { onAddCurrencyToggleClick(currency, it) }
|
||||
store.dispatchDialogShow(dialog)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
onNetworkItemClicked = onNetworkItemClicked,
|
||||
onLoadMore = onLoadMore
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@ package com.tangem.tap.features.tokens.ui.compose
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.compose.extensions.OnBottomReached
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
|
|
@ -17,15 +19,16 @@ import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
|||
|
||||
@Composable
|
||||
fun ListOfCurrencies(
|
||||
header: @Composable () -> Unit,
|
||||
currencies: List<Currency>,
|
||||
nonRemovableTokens: List<ContractAddress>,
|
||||
nonRemovableBlockchains: List<Blockchain>,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
searchInput: String,
|
||||
allowToAdd: Boolean,
|
||||
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit,
|
||||
onLoadMore: () -> Unit
|
||||
) {
|
||||
|
||||
val expandedCurrencies = remember { mutableStateOf(listOf("")) }
|
||||
|
|
@ -40,21 +43,16 @@ fun ListOfCurrencies(
|
|||
expandedCurrencies.value = mutableList
|
||||
}
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 90.dp)
|
||||
) {
|
||||
|
||||
val filteredCurrencies = if (searchInput.isBlank()) {
|
||||
currencies
|
||||
} else {
|
||||
currencies.asSequence().filter {
|
||||
it.name.lowercase().contains(searchInput) || it.symbol.lowercase()
|
||||
.contains(searchInput)
|
||||
}.toList()
|
||||
}
|
||||
items(filteredCurrencies) { currency ->
|
||||
item { header() }
|
||||
itemsIndexed(currencies) { index, currency ->
|
||||
CurrencyItem(
|
||||
currency = currency,
|
||||
nonRemovableTokens = nonRemovableTokens,
|
||||
|
|
@ -68,8 +66,8 @@ fun ListOfCurrencies(
|
|||
onNetworkItemClicked = onNetworkItemClicked
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
listState.OnBottomReached(loadMoreThreshold = 40) { onLoadMore() }
|
||||
}
|
||||
|
||||
val Currency.fullName: String
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
package com.tangem.tap.features.wallet.models
|
||||
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.isAboveZero
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class PendingTransaction(
|
||||
val address: String?,
|
||||
val amount: BigDecimal?,
|
||||
val amountUi: String?,
|
||||
val currency: String,
|
||||
val type: PendingTransactionType
|
||||
)
|
||||
val transactionData: TransactionData,
|
||||
val type: PendingTransactionType,
|
||||
) {
|
||||
val address: String? = when (type) {
|
||||
PendingTransactionType.Incoming -> transactionData.destinationAddress
|
||||
PendingTransactionType.Outgoing -> transactionData.sourceAddress
|
||||
PendingTransactionType.Unknown -> null
|
||||
}
|
||||
|
||||
val amountValue: BigDecimal? = transactionData.amount.value
|
||||
|
||||
val amountValueUi: String? = amountValue?.toFormattedString(transactionData.amount.decimals)
|
||||
|
||||
val currency: String = transactionData.amount.currencySymbol
|
||||
}
|
||||
|
||||
enum class PendingTransactionType { Incoming, Outgoing, Unknown }
|
||||
|
||||
|
|
@ -22,29 +28,11 @@ fun TransactionData.toPendingTransaction(walletAddress: String): PendingTransact
|
|||
if (this.status == TransactionStatus.Confirmed) return null
|
||||
|
||||
val type: PendingTransactionType = when {
|
||||
this.sourceAddress == walletAddress -> {
|
||||
PendingTransactionType.Outgoing
|
||||
}
|
||||
this.destinationAddress == walletAddress -> {
|
||||
PendingTransactionType.Incoming
|
||||
}
|
||||
else -> {
|
||||
PendingTransactionType.Unknown
|
||||
}
|
||||
this.sourceAddress == walletAddress -> PendingTransactionType.Outgoing
|
||||
this.destinationAddress == walletAddress -> PendingTransactionType.Incoming
|
||||
else -> PendingTransactionType.Unknown
|
||||
}
|
||||
val address = if (this.sourceAddress == walletAddress) {
|
||||
this.destinationAddress
|
||||
} else {
|
||||
this.sourceAddress
|
||||
}
|
||||
|
||||
return PendingTransaction(
|
||||
if (address == "unknown") null else address,
|
||||
this.amount.value,
|
||||
this.amount.value?.toFormattedString(amount.decimals),
|
||||
this.amount.currencySymbol,
|
||||
type
|
||||
)
|
||||
return PendingTransaction(this, type)
|
||||
}
|
||||
|
||||
fun List<TransactionData>.toPendingTransactions(walletAddress: String): List<PendingTransaction> {
|
||||
|
|
@ -55,27 +43,58 @@ fun List<PendingTransaction>.removeUnknownTransactions(): List<PendingTransactio
|
|||
return this.filter { it.type != PendingTransactionType.Unknown }
|
||||
}
|
||||
|
||||
fun List<PendingTransaction>.filterByCoin(): List<PendingTransaction> {
|
||||
return this.filter { it.transactionData.amount.type == AmountType.Coin }
|
||||
}
|
||||
|
||||
fun List<PendingTransaction>.filterByToken(token: Token): List<PendingTransaction> {
|
||||
return this.filter { it.currency == token.symbol }
|
||||
}
|
||||
|
||||
fun TransactionData.toPendingTransactionForToken(token: Token, walletAddress: String): PendingTransaction? {
|
||||
if (this.amount.currencySymbol != token.symbol) return null
|
||||
return this.toPendingTransaction(walletAddress)
|
||||
}
|
||||
|
||||
fun List<TransactionData>.toPendingTransactionsForToken(token: Token, walletAddress: String): List<PendingTransaction> {
|
||||
return this.mapNotNull { it.toPendingTransactionForToken(token, walletAddress) }
|
||||
}
|
||||
|
||||
fun Wallet.getPendingTransactions(type: PendingTransactionType? = null): List<PendingTransaction> {
|
||||
val txs = recentTransactions.toPendingTransactions(address)
|
||||
return when(type) {
|
||||
return when (type) {
|
||||
null -> txs
|
||||
else -> txs.filter { it.type == type }
|
||||
}
|
||||
}
|
||||
|
||||
fun Wallet.getPendingTransactions(token: Token): List<PendingTransaction> {
|
||||
return recentTransactions.mapNotNull { it.toPendingTransactionForToken(token, address) }
|
||||
}
|
||||
|
||||
fun Wallet.hasPendingTransactions(): Boolean {
|
||||
return getPendingTransactions().isNotEmpty()
|
||||
}
|
||||
|
||||
fun Wallet.hasPendingTransactions(token: Token): Boolean {
|
||||
return getPendingTransactions(token).isNotEmpty()
|
||||
}
|
||||
|
||||
fun Wallet.getSendableAmounts(): List<Amount> {
|
||||
return amounts.values
|
||||
.filter { it.type != AmountType.Reserve }
|
||||
.filter { it.isAboveZero() }
|
||||
}
|
||||
|
||||
fun Wallet.hasSendableAmounts(): Boolean {
|
||||
return getSendableAmounts().isNotEmpty()
|
||||
}
|
||||
|
||||
fun Wallet.hasSendableAmountsOrPendingTransactions(): Boolean {
|
||||
return hasPendingTransactions() || amounts.toSendableAmounts().isNotEmpty()
|
||||
return hasPendingTransactions() || hasSendableAmounts()
|
||||
}
|
||||
|
||||
fun Wallet.isSendableAmount(type: AmountType): Boolean {
|
||||
return amounts[type]?.isAboveZero() == true
|
||||
|
||||
}
|
||||
|
||||
fun Wallet.isSendableAmount(token: Token): Boolean {
|
||||
return isSendableAmount(AmountType.Token(token))
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.tap.features.wallet.models
|
||||
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class TotalBalance(
|
||||
val state: State,
|
||||
val fiatAmount: BigDecimal,
|
||||
val fiatCurrency: FiatCurrency,
|
||||
) {
|
||||
enum class State {
|
||||
Loading,
|
||||
SomeTokensFailed,
|
||||
Success,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.tap.features.wallet.models
|
||||
|
||||
sealed class WalletWarning(
|
||||
val showingPosition: Int,
|
||||
) {
|
||||
object TransactionInProgress : WalletWarning(10)
|
||||
object SolanaTokensUnsupported : WalletWarning(20)
|
||||
data class BalanceNotEnoughForFee(val blockchainFullName: String) : WalletWarning(30)
|
||||
data class Rent(val walletRent: WalletRent) : WalletWarning(40)
|
||||
}
|
||||
|
||||
data class WalletWarningDescription(
|
||||
val title: String,
|
||||
val message: String,
|
||||
)
|
||||
|
||||
data class WalletRent(
|
||||
val minRentValue: String,
|
||||
val rentExemptValue: String,
|
||||
)
|
||||
|
|
@ -4,11 +4,12 @@ import android.content.Context
|
|||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -98,7 +99,10 @@ sealed class WalletAction : Action {
|
|||
data class LoadFiatRate(
|
||||
val wallet: Wallet? = null, val coinsList: List<Currency>? = null,
|
||||
) : WalletAction() {
|
||||
data class Success(val fiatRate: Pair<Currency, BigDecimal?>) : WalletAction()
|
||||
data class Success(
|
||||
val fiatRates: Map<Currency, BigDecimal?>
|
||||
) : WalletAction()
|
||||
|
||||
object Failure : WalletAction()
|
||||
}
|
||||
|
||||
|
|
@ -113,10 +117,8 @@ sealed class WalletAction : Action {
|
|||
|
||||
data class Send(val amount: Amount? = null) : WalletAction() {
|
||||
data class ChooseCurrency(val amounts: List<Amount>?) : WalletAction()
|
||||
object Cancel : WalletAction()
|
||||
}
|
||||
|
||||
|
||||
object EmptyField : WalletAction(), ErrorAction {
|
||||
override val error = TapError.PayIdEmptyField
|
||||
}
|
||||
|
|
@ -129,13 +131,18 @@ sealed class WalletAction : Action {
|
|||
|
||||
data class ShareAddress(val address: String, val context: Context) : WalletAction()
|
||||
|
||||
object ShowDialog : WalletAction() {
|
||||
object QrCode : WalletAction()
|
||||
object SignedHashesMultiWalletDialog : WalletAction()
|
||||
object ChooseTradeActionDialog : WalletAction()
|
||||
}
|
||||
sealed class DialogAction : WalletAction() {
|
||||
data class QrCode(
|
||||
val currency: Currency,
|
||||
val selectedAddress: AddressData,
|
||||
) : DialogAction()
|
||||
|
||||
object HideDialog : WalletAction()
|
||||
object SignedHashesMultiWalletDialog : DialogAction()
|
||||
object ChooseTradeActionDialog : DialogAction()
|
||||
data class ChooseCurrency(val amounts: List<Amount>?) : DialogAction()
|
||||
|
||||
object Hide : DialogAction()
|
||||
}
|
||||
|
||||
data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction()
|
||||
|
||||
|
|
@ -157,10 +164,15 @@ sealed class WalletAction : Action {
|
|||
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
|
||||
|
||||
data class SetWalletRent(
|
||||
val blockchain: BlockchainNetwork,
|
||||
val wallet: Wallet,
|
||||
val minRent: String,
|
||||
val rentExempt: String
|
||||
) : WalletAction()
|
||||
|
||||
data class RemoveWalletRent(val blockchain: BlockchainNetwork) : WalletAction()
|
||||
data class RemoveWalletRent(val wallet: Wallet) : WalletAction()
|
||||
|
||||
sealed class AppCurrencyAction : WalletAction() {
|
||||
object ChooseAppCurrency : AppCurrencyAction()
|
||||
data class SelectAppCurrency(val fiatCurrency: FiatCurrency) : AppCurrencyAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,12 @@ package com.tangem.tap.features.wallet.redux
|
|||
import android.graphics.Bitmap
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.blockchain.extensions.isAboveZero
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.extensions.toCoinId
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.tap.common.entities.Button
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.toQrCode
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
|
|
@ -15,13 +16,12 @@ import com.tangem.tap.common.toggleWidget.WidgetState
|
|||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.extensions.buyIsAllowed
|
||||
import com.tangem.tap.domain.extensions.sellIsAllowed
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken
|
||||
import com.tangem.tap.features.wallet.models.*
|
||||
import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount
|
||||
import com.tangem.tap.features.wallet.redux.reducers.findTotalBalanceState
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
|
|
@ -35,7 +35,6 @@ data class WalletState(
|
|||
val error: ErrorType? = null,
|
||||
val cardImage: Artwork? = null,
|
||||
val hashesCountVerified: Boolean? = null,
|
||||
val walletDialog: StateDialog? = null,
|
||||
val mainWarningsList: List<WarningMessage> = mutableListOf(),
|
||||
val wallets: List<WalletStore> = listOf(),
|
||||
val isMultiwalletAllowed: Boolean = false,
|
||||
|
|
@ -44,6 +43,7 @@ data class WalletState(
|
|||
val primaryBlockchain: Blockchain? = null,
|
||||
val primaryToken: Token? = null,
|
||||
val isTestnet: Boolean = false,
|
||||
val totalBalance: TotalBalance? = null,
|
||||
) : StateType {
|
||||
|
||||
// if you do not delegate - the application crashes on startup,
|
||||
|
|
@ -140,16 +140,13 @@ data class WalletState(
|
|||
|
||||
val wallet = walletManager.wallet
|
||||
|
||||
if (walletData.currency is Currency.Blockchain) {
|
||||
return wallet.recentTransactions.toPendingTransactions(wallet.address).isEmpty() &&
|
||||
wallet.amounts.toSendableAmounts().isEmpty()
|
||||
} else if (walletData.currency is Currency.Token) (
|
||||
return wallet.recentTransactions.toPendingTransactionsForToken(
|
||||
walletData.currency.token, wallet.address
|
||||
).isEmpty()
|
||||
&& wallet.amounts[AmountType.Token(token = walletData.currency.token)]
|
||||
?.isAboveZero() != true
|
||||
)
|
||||
return when (walletData.currency) {
|
||||
is Currency.Blockchain -> !wallet.hasPendingTransactions() && !wallet.hasSendableAmounts()
|
||||
is Currency.Token -> {
|
||||
val token = walletData.currency.token
|
||||
!wallet.hasPendingTransactions(token) && !wallet.isSendableAmount(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -189,25 +186,28 @@ data class WalletState(
|
|||
.distinct().map { getWalletStore(it) }.mapNotNull { it?.updateWallets(walletsData) }
|
||||
|
||||
return updateWalletStores(walletStores)
|
||||
|
||||
}
|
||||
|
||||
fun updateWalletStore(walletStore: WalletStore?): WalletState {
|
||||
return copy(wallets = replaceWalletInWallets(walletStore))
|
||||
.updateTotalBalance()
|
||||
}
|
||||
|
||||
fun updateWalletStores(walletStores: List<WalletStore>): WalletState {
|
||||
val walletStores = walletStores.toMutableList()
|
||||
private fun updateWalletStores(walletStores: List<WalletStore>): WalletState {
|
||||
val walletStoresMutable = walletStores.toMutableList()
|
||||
val updatedWallets = wallets.map { oldWalletStore ->
|
||||
val walletStore = walletStores.find { it.blockchainNetwork == oldWalletStore.blockchainNetwork }
|
||||
val walletStore = walletStoresMutable.find {
|
||||
it.blockchainNetwork == oldWalletStore.blockchainNetwork
|
||||
}
|
||||
if (walletStore != null) {
|
||||
walletStores.remove(walletStore)
|
||||
walletStoresMutable.remove(walletStore)
|
||||
walletStore
|
||||
} else {
|
||||
oldWalletStore
|
||||
}
|
||||
}
|
||||
return copy(wallets = updatedWallets + walletStores)
|
||||
return copy(wallets = updatedWallets + walletStoresMutable)
|
||||
.updateTotalBalance()
|
||||
}
|
||||
|
||||
fun removeWallet(walletData: WalletData?): WalletState {
|
||||
|
|
@ -268,12 +268,31 @@ data class WalletState(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTotalBalance(): WalletState {
|
||||
return if (wallets.isNotEmpty()) {
|
||||
val walletsData = wallets.flatMap(WalletStore::walletsData)
|
||||
this.copy(
|
||||
totalBalance = TotalBalance(
|
||||
state = walletsData.findTotalBalanceState(),
|
||||
fiatAmount = walletsData.calculateTotalFiatAmount(),
|
||||
fiatCurrency = store.state.globalState.appCurrency
|
||||
)
|
||||
)
|
||||
} else this.copy(
|
||||
totalBalance = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WalletDialog : StateDialog {
|
||||
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog()
|
||||
object SignedHashesMultiWalletDialog : WalletDialog()
|
||||
object ChooseTradeActionDialog : WalletDialog()
|
||||
sealed interface WalletDialog : StateDialog {
|
||||
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog
|
||||
object SignedHashesMultiWalletDialog : WalletDialog
|
||||
object ChooseTradeActionDialog : WalletDialog
|
||||
data class CurrencySelectionDialog(
|
||||
val currenciesList: List<FiatCurrency>,
|
||||
val currentAppCurrency: FiatCurrency,
|
||||
) : WalletDialog
|
||||
}
|
||||
|
||||
enum class ProgressState : WidgetState { Loading, Done, Error }
|
||||
|
|
@ -345,31 +364,49 @@ data class WalletData(
|
|||
val fiatRate: BigDecimal? = null,
|
||||
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
|
||||
val currency: Currency,
|
||||
val warningRent: WalletRent? = null,
|
||||
val walletRent: WalletRent? = null,
|
||||
) {
|
||||
fun shouldShowMultipleAddress(): Boolean {
|
||||
val listOfAddresses = walletAddresses?.list ?: return false
|
||||
return listOfAddresses.size > 1
|
||||
}
|
||||
|
||||
fun shouldShowCoinAmountWarning(): Boolean = when (currency) {
|
||||
is Currency.Blockchain -> false
|
||||
is Currency.Token -> blockchainAmountIsEmpty() && !tokenAmountIsEmpty()
|
||||
fun shouldEnableTokenSendButton(): Boolean = if (blockchainAmountIsEmpty()) {
|
||||
false
|
||||
} else {
|
||||
!tokenAmountIsEmpty()
|
||||
}
|
||||
|
||||
fun shouldEnableTokenSendButton(): Boolean = !blockchainAmountIsEmpty() || !tokenAmountIsEmpty()
|
||||
fun assembleWarnings(): List<WalletWarning> {
|
||||
val blockchain = currency.blockchain
|
||||
val walletWarnings = mutableListOf<WalletWarning>()
|
||||
if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) {
|
||||
walletWarnings.add(WalletWarning.TransactionInProgress)
|
||||
}
|
||||
if (currency.isBlockchain()) {
|
||||
if (blockchain == Blockchain.Solana || blockchain == Blockchain.SolanaTestnet) {
|
||||
val card = store.state.globalState.scanResponse?.card
|
||||
if (card?.canHandleToken(blockchain) == false) {
|
||||
walletWarnings.add(WalletWarning.SolanaTokensUnsupported)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (walletRent != null) {
|
||||
walletWarnings.add(WalletWarning.Rent(walletRent))
|
||||
}
|
||||
if (!currency.isBlockchain() && (blockchainAmountIsEmpty() && !tokenAmountIsEmpty())) {
|
||||
val fullName = currency.blockchain.fullName
|
||||
walletWarnings.add(WalletWarning.BalanceNotEnoughForFee(fullName))
|
||||
}
|
||||
|
||||
private fun blockchainAmountIsEmpty(): Boolean =
|
||||
currencyData.blockchainAmount?.isZero() ?: false
|
||||
return walletWarnings.sortedBy { it.showingPosition }
|
||||
}
|
||||
|
||||
private fun blockchainAmountIsEmpty(): Boolean = currencyData.blockchainAmount?.isZero() == true
|
||||
|
||||
private fun tokenAmountIsEmpty(): Boolean = currencyData.amount?.isZero() == true
|
||||
}
|
||||
|
||||
data class WalletRent(
|
||||
val minRentValue: String,
|
||||
val rentExemptValue: String
|
||||
)
|
||||
|
||||
sealed interface Currency {
|
||||
|
||||
val coinId: String?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletDialog
|
||||
import com.tangem.tap.persistence.FiatCurrenciesPrefStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AppCurrencyMiddleware(
|
||||
private val tangemTechService: TangemTechService,
|
||||
private val tapWalletManager: TapWalletManager,
|
||||
private val fiatCurrenciesPrefStorage: FiatCurrenciesPrefStorage,
|
||||
private val appCurrencyProvider: () -> FiatCurrency,
|
||||
) {
|
||||
fun handle(action: WalletAction.AppCurrencyAction) {
|
||||
when (action) {
|
||||
is WalletAction.AppCurrencyAction.ChooseAppCurrency -> showSelector()
|
||||
is WalletAction.AppCurrencyAction.SelectAppCurrency -> selectCurrency(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSelector() {
|
||||
val storedFiatCurrencies = fiatCurrenciesPrefStorage.restore()
|
||||
if (storedFiatCurrencies.isNotEmpty()) {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.CurrencySelectionDialog(
|
||||
currenciesList = storedFiatCurrencies.mapToUiModel(),
|
||||
currentAppCurrency = appCurrencyProvider.invoke()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
when (val result = tangemTechService.currencies()) {
|
||||
is Result.Success -> {
|
||||
val currenciesList = result.data.currencies
|
||||
if (currenciesList.isNotEmpty() &&
|
||||
currenciesList.toSet() != storedFiatCurrencies.toSet()
|
||||
) {
|
||||
fiatCurrenciesPrefStorage.save(currenciesList)
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.CurrencySelectionDialog(
|
||||
currenciesList = currenciesList.mapToUiModel(),
|
||||
currentAppCurrency = appCurrencyProvider.invoke()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) {
|
||||
tapWalletManager.rates.clear()
|
||||
fiatCurrenciesPrefStorage.saveAppCurrency(action.fiatCurrency)
|
||||
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency))
|
||||
store.dispatch(WalletAction.LoadFiatRate())
|
||||
}
|
||||
|
||||
private fun List<CurrenciesResponse.Currency>.mapToUiModel(): List<FiatCurrency> {
|
||||
return this.map {
|
||||
FiatCurrency(
|
||||
code = it.code,
|
||||
name = it.name,
|
||||
symbol = it.unit
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen
|
|||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.wallet.redux.middlewares
|
|||
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
|
@ -51,7 +52,10 @@ class TradeCryptoMiddleware {
|
|||
currency is Currency.Token && currency.blockchain.isTestnet()
|
||||
) {
|
||||
val walletManager = store.state.walletState.getWalletManager(currency)
|
||||
if (walletManager !is EthereumWalletManager) return
|
||||
if (walletManager !is EthereumWalletManager) {
|
||||
store.dispatchDebugErrorNotification("Testnet tokens available only for the ETH")
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch { exchangeManager.buyErc20Tokens(walletManager, currency.token) }
|
||||
return
|
||||
|
|
@ -61,8 +65,9 @@ class TradeCryptoMiddleware {
|
|||
action = exchangeAction,
|
||||
blockchain = currency.blockchain,
|
||||
cryptoCurrencyName = currencySymbol,
|
||||
fiatCurrency = appCurrency,
|
||||
walletAddress = defaultAddress)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
|
||||
fiatCurrencyName = appCurrency.code,
|
||||
walletAddress = defaultAddress
|
||||
)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
|
||||
class WalletDialogsMiddleware {
|
||||
fun handle(action: WalletAction.DialogAction) {
|
||||
when (action) {
|
||||
is WalletAction.DialogAction.SignedHashesMultiWalletDialog -> {
|
||||
store.dispatchDialogShow(WalletDialog.SignedHashesMultiWalletDialog)
|
||||
}
|
||||
is WalletAction.DialogAction.ChooseTradeActionDialog -> {
|
||||
store.dispatchDialogShow(WalletDialog.ChooseTradeActionDialog)
|
||||
}
|
||||
is WalletAction.DialogAction.QrCode -> {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.AddressInfoDialog(
|
||||
currency = action.currency,
|
||||
addressData = action.selectedAddress,
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletAction.DialogAction.ChooseCurrency -> {
|
||||
store.dispatchDialogShow(
|
||||
WalletDialog.SelectAmountToSendDialog(
|
||||
amounts = action.amounts
|
||||
)
|
||||
)
|
||||
}
|
||||
is WalletAction.DialogAction.Hide -> {
|
||||
store.dispatchDialogHide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,22 +16,21 @@ import com.tangem.operations.attestation.Attestation
|
|||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.failedRates
|
||||
import com.tangem.tap.domain.loadedRates
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
||||
import com.tangem.tap.features.wallet.models.getSendableAmounts
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.network.NetworkStateChanged
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
|
@ -48,6 +47,15 @@ class WalletMiddleware {
|
|||
private val tradeCryptoMiddleware = TradeCryptoMiddleware()
|
||||
private val warningsMiddleware = WarningsMiddleware()
|
||||
private val multiWalletMiddleware = MultiWalletMiddleware()
|
||||
private val walletDialogMiddleware = WalletDialogsMiddleware()
|
||||
private val appCurrencyMiddleware by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
AppCurrencyMiddleware(
|
||||
tangemTechService = store.state.domainNetworks.tangemTechService,
|
||||
tapWalletManager = store.state.globalState.tapWalletManager,
|
||||
fiatCurrenciesPrefStorage = preferencesStorage.fiatCurrenciesPrefStorage,
|
||||
appCurrencyProvider = { store.state.globalState.appCurrency },
|
||||
)
|
||||
}
|
||||
|
||||
val walletMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
|
|
@ -72,6 +80,8 @@ class WalletMiddleware {
|
|||
walletState,
|
||||
globalState
|
||||
)
|
||||
is WalletAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action)
|
||||
is WalletAction.DialogAction -> walletDialogMiddleware.handle(action)
|
||||
is WalletAction.LoadWallet -> {
|
||||
scope.launch {
|
||||
if (action.blockchain == null) {
|
||||
|
|
@ -104,7 +114,7 @@ class WalletMiddleware {
|
|||
warningsMiddleware.tryToShowAppRatingWarning(action.wallet)
|
||||
}
|
||||
is WalletAction.LoadFiatRate -> {
|
||||
val appCurrencyId = globalState.appCurrency
|
||||
val appCurrencyId = globalState.appCurrency.code
|
||||
scope.launch {
|
||||
val coinsList = when {
|
||||
action.wallet != null -> {
|
||||
|
|
@ -122,11 +132,15 @@ class WalletMiddleware {
|
|||
)
|
||||
when (ratesResult) {
|
||||
is Result.Success -> {
|
||||
ratesResult.data.loadedRates.forEach {
|
||||
dispatchOnMain(WalletAction.LoadFiatRate.Success(it.toPair()))
|
||||
ratesResult.data.loadedRates.let {
|
||||
dispatchOnMain(WalletAction.LoadFiatRate.Success(it))
|
||||
}
|
||||
ratesResult.data.failedRates.forEach { (currency, throwable) ->
|
||||
Timber.e(throwable, "Loading rates failed for [%s]", currency.currencySymbol)
|
||||
Timber.e(
|
||||
throwable,
|
||||
"Loading rates failed for [%s]",
|
||||
currency.currencySymbol
|
||||
)
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
|
|
@ -219,15 +233,6 @@ class WalletMiddleware {
|
|||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
}
|
||||
}
|
||||
is WalletAction.ShowDialog.QrCode -> {
|
||||
val selectedWalletData =
|
||||
walletState.getWalletData(walletState.selectedCurrency) ?: return
|
||||
val selectedAddressData =
|
||||
selectedWalletData.walletAddresses?.selectedAddress ?: return
|
||||
|
||||
val currency = selectedWalletData.currency
|
||||
store.dispatchDialogShow(AppDialog.AddressInfoDialog(currency, selectedAddressData))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,13 +248,13 @@ class WalletMiddleware {
|
|||
PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletStore?.walletManager)
|
||||
}
|
||||
} else {
|
||||
val amounts = walletStore?.walletManager?.wallet?.amounts?.toSendableAmounts()
|
||||
val amounts = walletStore?.walletManager?.wallet?.getSendableAmounts()
|
||||
if (currency != null && state.isMultiwalletAllowed) {
|
||||
when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
val amountToSend =
|
||||
amounts?.find { it.currencySymbol == currency.blockchain.currency }
|
||||
?: return WalletAction.Send.ChooseCurrency(amounts)
|
||||
?: return WalletAction.DialogAction.ChooseCurrency(amounts)
|
||||
PrepareSendScreen(
|
||||
coinAmount = amountToSend,
|
||||
coinRate = selectedWalletData.fiatRate,
|
||||
|
|
@ -259,7 +264,7 @@ class WalletMiddleware {
|
|||
is Currency.Token -> {
|
||||
val amountToSend =
|
||||
amounts?.find { it.currencySymbol == currency.token.symbol }
|
||||
?: return WalletAction.Send.ChooseCurrency(amounts)
|
||||
?: return WalletAction.DialogAction.ChooseCurrency(amounts)
|
||||
prepareSendActionForToken(
|
||||
amount = amountToSend,
|
||||
state = state,
|
||||
|
|
@ -322,7 +327,7 @@ class WalletMiddleware {
|
|||
val show = if (outgoingTxs.isEmpty()) {
|
||||
isNeedToShowWarning(balance, rentExempt)
|
||||
} else {
|
||||
val outgoingAmount = outgoingTxs.sumOf { it.amount ?: BigDecimal.ZERO }
|
||||
val outgoingAmount = outgoingTxs.sumOf { it.amountValue ?: BigDecimal.ZERO }
|
||||
val rest = balance.minus(outgoingAmount)
|
||||
isNeedToShowWarning(rest, rentExempt)
|
||||
}
|
||||
|
|
@ -330,18 +335,16 @@ class WalletMiddleware {
|
|||
val currency = walletManager.wallet.blockchain.currency
|
||||
if (show) {
|
||||
dispatchOnMain(WalletAction.SetWalletRent(
|
||||
blockchain = BlockchainNetwork.fromWalletManager(walletManager),
|
||||
wallet = walletManager.wallet,
|
||||
minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
|
||||
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
|
||||
))
|
||||
} else {
|
||||
dispatchOnMain(WalletAction.RemoveWalletRent(
|
||||
blockchain = BlockchainNetwork.fromWalletManager(walletManager),
|
||||
))
|
||||
dispatchOnMain(WalletAction.RemoveWalletRent(walletManager.wallet))
|
||||
}
|
||||
}
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
||||
class AppCurrencyReducer {
|
||||
fun reduce(
|
||||
action: WalletAction.AppCurrencyAction,
|
||||
state: WalletState,
|
||||
): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.AppCurrencyAction.SelectAppCurrency,
|
||||
is WalletAction.AppCurrencyAction.ChooseAppCurrency -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,22 @@ package com.tangem.tap.features.wallet.redux.reducers
|
|||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.extensions.isAboveZero
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.extensions.toFiatString
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.filterByToken
|
||||
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
||||
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.WalletStore
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import com.tangem.tap.features.wallet.ui.TokenData
|
||||
|
|
@ -106,17 +113,16 @@ class MultiWalletReducer {
|
|||
throw NullPointerException("MultiWallet.TokenLoaded: WalletManager must be no NULL")
|
||||
}
|
||||
|
||||
val pendingTransactions = wallet.recentTransactions.toPendingTransactions(wallet.address)
|
||||
val sendButtonEnabled =
|
||||
action.amount.value?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val tokenPendingTransactions = pendingTransactions
|
||||
.filter { it.currency == action.amount.currencySymbol }
|
||||
val pendingTransactions = wallet.getPendingTransactions()
|
||||
val tokenPendingTransactions = pendingTransactions.filterByToken(action.token)
|
||||
val tokenBalanceStatus = when {
|
||||
tokenPendingTransactions.isNotEmpty() -> BalanceStatus.TransactionInProgress
|
||||
pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress
|
||||
else -> BalanceStatus.VerifiedOnline
|
||||
}
|
||||
val tokenWalletData = state.getWalletData(currency)
|
||||
val isTokenSendButtonEnabled = action.amount.isAboveZero() && pendingTransactions.isEmpty()
|
||||
|
||||
val newTokenWalletData = tokenWalletData?.copy(
|
||||
currencyData = tokenWalletData.currencyData.copy(
|
||||
status = tokenBalanceStatus,
|
||||
|
|
@ -126,12 +132,12 @@ class MultiWalletReducer {
|
|||
),
|
||||
fiatAmountFormatted = tokenWalletData.fiatRate?.let {
|
||||
action.amount.value
|
||||
?.toFiatString(it, store.state.globalState.appCurrency)
|
||||
?.toFiatString(it, store.state.globalState.appCurrency.symbol)
|
||||
},
|
||||
blockchainAmount = wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO
|
||||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(sendButtonEnabled),
|
||||
mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled),
|
||||
currency = Currency.Token(
|
||||
token = action.token,
|
||||
blockchain = action.blockchain.blockchain,
|
||||
|
|
|
|||
|
|
@ -8,10 +8,16 @@ import com.tangem.tap.common.extensions.toFiatValue
|
|||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.common.extensions.toFormattedFiatValue
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.filterByToken
|
||||
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
||||
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.TradeCryptoState
|
||||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import com.tangem.tap.features.wallet.ui.TokenData
|
||||
|
|
@ -33,31 +39,27 @@ class OnWalletLoadedReducer {
|
|||
blockchainNetwork: BlockchainNetwork,
|
||||
walletState: WalletState
|
||||
): WalletState {
|
||||
val fiatCurrencySymbol = store.state.globalState.appCurrency
|
||||
val walletData = walletState.getWalletData(blockchainNetwork) ?: return walletState
|
||||
|
||||
val fiatCurrency = store.state.globalState.appCurrency
|
||||
val exchangeManager = store.state.globalState.currencyExchangeManager
|
||||
|
||||
val coinAmountValue = wallet.amounts[AmountType.Coin]?.value
|
||||
if (walletState.getWalletData(blockchainNetwork) == null) {
|
||||
return walletState
|
||||
}
|
||||
val formattedAmount = coinAmountValue?.toFormattedCurrencyString(
|
||||
wallet.blockchain.decimals(),
|
||||
wallet.blockchain.currency
|
||||
)
|
||||
|
||||
val pendingTransactions = wallet.recentTransactions
|
||||
.toPendingTransactions(wallet.address)
|
||||
|
||||
val coinSendButton = coinAmountValue?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val pendingTransactions = wallet.getPendingTransactions()
|
||||
val isCoinSendButtonEnabled = coinAmountValue?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val balanceStatus = if (pendingTransactions.isNotEmpty()) {
|
||||
BalanceStatus.TransactionInProgress
|
||||
} else {
|
||||
BalanceStatus.VerifiedOnline
|
||||
}
|
||||
val walletData = walletState.getWalletData(blockchainNetwork)
|
||||
|
||||
val fiatAmount = walletData?.fiatRate?.let { coinAmountValue?.toFiatValue(it) }
|
||||
val newWalletData = walletData?.copy(
|
||||
val fiatAmount = walletData.fiatRate?.let { coinAmountValue?.toFiatValue(it) }
|
||||
val newWalletData = walletData.copy(
|
||||
currencyData = walletData.currencyData.copy(
|
||||
status = balanceStatus, currency = wallet.blockchain.fullName,
|
||||
currencySymbol = wallet.blockchain.currency,
|
||||
|
|
@ -65,10 +67,10 @@ class OnWalletLoadedReducer {
|
|||
amount = coinAmountValue,
|
||||
amountFormatted = formattedAmount,
|
||||
fiatAmount = fiatAmount,
|
||||
fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(fiatCurrencySymbol)
|
||||
fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(fiatCurrency.symbol)
|
||||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(coinSendButton),
|
||||
mainButton = WalletMainButton.SendButton(isCoinSendButtonEnabled),
|
||||
currency = Currency.fromBlockchainNetwork(blockchainNetwork),
|
||||
tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData),
|
||||
)
|
||||
|
|
@ -76,8 +78,7 @@ class OnWalletLoadedReducer {
|
|||
val tokens = wallet.getTokens().mapNotNull { token ->
|
||||
val currency = Currency.fromBlockchainNetwork(blockchainNetwork, token)
|
||||
val tokenWalletData = walletState.getWalletData(currency)
|
||||
val tokenPendingTransactions =
|
||||
pendingTransactions.filter { it.currency == token.symbol }
|
||||
val tokenPendingTransactions = pendingTransactions.filterByToken(token)
|
||||
val tokenBalanceStatus = when {
|
||||
tokenPendingTransactions.isNotEmpty() -> BalanceStatus.TransactionInProgress
|
||||
pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress
|
||||
|
|
@ -87,8 +88,8 @@ class OnWalletLoadedReducer {
|
|||
val tokenFiatAmount =
|
||||
tokenWalletData?.fiatRate?.let { rate -> tokenAmountValue?.toFiatValue(rate) }
|
||||
|
||||
val tokenSendButton = newWalletData?.shouldEnableTokenSendButton() == true
|
||||
&& tokenPendingTransactions.isEmpty()
|
||||
val isTokenSendButtonEnabled = newWalletData.shouldEnableTokenSendButton()
|
||||
&& tokenPendingTransactions.isEmpty()
|
||||
tokenWalletData?.copy(
|
||||
currencyData = tokenWalletData.currencyData.copy(
|
||||
status = tokenBalanceStatus,
|
||||
|
|
@ -99,14 +100,14 @@ class OnWalletLoadedReducer {
|
|||
token.symbol
|
||||
),
|
||||
fiatAmount = tokenFiatAmount,
|
||||
fiatAmountFormatted = tokenFiatAmount?.toFormattedFiatValue(fiatCurrencySymbol)
|
||||
fiatAmountFormatted = tokenFiatAmount?.toFormattedFiatValue(fiatCurrency.symbol)
|
||||
),
|
||||
pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(tokenSendButton),
|
||||
mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled),
|
||||
tradeCryptoState = TradeCryptoState.from(exchangeManager, tokenWalletData),
|
||||
)
|
||||
}
|
||||
val newWallets = (tokens + newWalletData).mapNotNull { it }
|
||||
val newWallets = tokens + newWalletData
|
||||
val wallets = walletState.replaceSomeWallets((newWallets))
|
||||
|
||||
val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
|
||||
|
|
@ -114,16 +115,18 @@ class OnWalletLoadedReducer {
|
|||
} else {
|
||||
ProgressState.Done
|
||||
}
|
||||
val newState = walletState.updateWalletsData(wallets)
|
||||
return newState.copy(
|
||||
state = state, error = null
|
||||
)
|
||||
return walletState
|
||||
.updateWalletsData(wallets)
|
||||
.copy(
|
||||
state = state,
|
||||
error = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState {
|
||||
if (wallet.blockchain != walletState.primaryBlockchain) return walletState
|
||||
|
||||
val fiatCurrencySymbol = store.state.globalState.appCurrency
|
||||
val fiatCurrencyName = store.state.globalState.appCurrency.code
|
||||
val exchangeManager = store.state.globalState.currencyExchangeManager
|
||||
|
||||
val token = wallet.getFirstToken()
|
||||
|
|
@ -132,7 +135,7 @@ class OnWalletLoadedReducer {
|
|||
if (tokenAmount != null) {
|
||||
val tokenFiatRate = walletState.primaryWallet?.currencyData?.token?.fiatRate
|
||||
val tokenFiatAmount =
|
||||
tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
|
||||
tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencyName) }
|
||||
TokenData(
|
||||
tokenAmount.value?.toFormattedCurrencyString(
|
||||
token.decimals, token.symbol
|
||||
|
|
@ -152,9 +155,9 @@ class OnWalletLoadedReducer {
|
|||
)
|
||||
val fiatRate = walletState.primaryWallet?.fiatRate
|
||||
val fiatAmountRaw = fiatRate?.multiply(amount)?.setScale(2, RoundingMode.DOWN)
|
||||
val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
|
||||
val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencyName) }
|
||||
|
||||
val pendingTransactions = wallet.recentTransactions.toPendingTransactions(wallet.address)
|
||||
val pendingTransactions = wallet.getPendingTransactions()
|
||||
val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val balanceStatus = if (pendingTransactions.isNotEmpty()) {
|
||||
BalanceStatus.TransactionInProgress
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.tap.features.wallet.redux.reducers
|
||||
|
||||
import com.tangem.tap.features.wallet.models.TotalBalance
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import java.math.BigDecimal
|
||||
|
||||
fun List<WalletData>.findTotalBalanceState(): TotalBalance.State {
|
||||
return this.mapToTotalBalanceState()
|
||||
.reduce(TotalBalance.State::or)
|
||||
}
|
||||
|
||||
fun List<WalletData>.calculateTotalFiatAmount(): BigDecimal {
|
||||
return this.map { it.currencyData.fiatAmount ?: BigDecimal.ZERO }
|
||||
.reduce(BigDecimal::plus)
|
||||
}
|
||||
|
||||
fun List<WalletData>.mapToTotalBalanceState(): List<TotalBalance.State> {
|
||||
return this.map {
|
||||
when (it.currencyData.status) {
|
||||
BalanceStatus.VerifiedOnline,
|
||||
BalanceStatus.SameCurrencyTransactionInProgress,
|
||||
BalanceStatus.TransactionInProgress -> TotalBalance.State.Success
|
||||
BalanceStatus.Unreachable,
|
||||
BalanceStatus.NoAccount,
|
||||
BalanceStatus.EmptyCard,
|
||||
BalanceStatus.UnknownBlockchain -> TotalBalance.State.SomeTokensFailed
|
||||
BalanceStatus.Loading,
|
||||
null -> TotalBalance.State.Loading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
infix fun TotalBalance.State.or(newState: TotalBalance.State): TotalBalance.State {
|
||||
return when (this) {
|
||||
TotalBalance.State.Loading -> when (newState) {
|
||||
TotalBalance.State.Loading,
|
||||
TotalBalance.State.SomeTokensFailed,
|
||||
TotalBalance.State.Success -> this
|
||||
}
|
||||
TotalBalance.State.Success,
|
||||
TotalBalance.State.SomeTokensFailed -> when (newState) {
|
||||
TotalBalance.State.Loading,
|
||||
TotalBalance.State.SomeTokensFailed -> newState
|
||||
TotalBalance.State.Success -> this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,24 +3,37 @@ package com.tangem.tap.features.wallet.redux.reducers
|
|||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.extensions.mapNotNullValues
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.toFiatRateString
|
||||
import com.tangem.tap.common.extensions.toFiatString
|
||||
import com.tangem.tap.common.extensions.toFiatValue
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.common.extensions.toFormattedFiatValue
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.getArtworkUrl
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.WalletRent
|
||||
import com.tangem.tap.features.wallet.redux.AddressData
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.TradeCryptoState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAddresses
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletDialog
|
||||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.WalletStore
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import org.rekotlin.Action
|
||||
|
||||
class WalletReducer {
|
||||
companion object {
|
||||
|
|
@ -32,6 +45,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
|
||||
val multiWalletReducer = MultiWalletReducer()
|
||||
val onWalletLoadedReducer = OnWalletLoadedReducer()
|
||||
val appCurrencyReducer = AppCurrencyReducer()
|
||||
|
||||
if (action !is WalletAction) return state.walletState
|
||||
|
||||
|
|
@ -70,9 +84,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
when (action.error) {
|
||||
is TapError.NoInternetConnection -> {
|
||||
val wallets = newState.wallets
|
||||
.map {
|
||||
it.copy(
|
||||
walletsData = it.walletsData.map {
|
||||
.map { store ->
|
||||
store.copy(
|
||||
walletsData = store.walletsData.map {
|
||||
it.copy(
|
||||
currencyData = it.currencyData.copy(
|
||||
status = BalanceStatus.Unreachable
|
||||
|
|
@ -110,6 +124,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
)
|
||||
)
|
||||
}
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,12 +162,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
} else {
|
||||
val walletManager = newState.getWalletManager(action.blockchain) ?: return newState
|
||||
val currencies = listOf(Currency.fromBlockchainNetwork(action.blockchain)) +
|
||||
walletManager.cardTokens.map {
|
||||
Currency.fromBlockchainNetwork(
|
||||
action.blockchain,
|
||||
it
|
||||
)
|
||||
}
|
||||
walletManager.cardTokens.map {
|
||||
Currency.fromBlockchainNetwork(action.blockchain, it)
|
||||
}
|
||||
val newWallets = newState.walletsData.filter { currencies.contains(it.currency) }
|
||||
.map { wallet ->
|
||||
wallet.copy(
|
||||
|
|
@ -261,7 +274,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
}
|
||||
|
||||
is WalletAction.LoadFiatRate.Success ->
|
||||
newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState)
|
||||
newState = setNewFiatRate(action.fiatRates, state.globalState.appCurrency, newState)
|
||||
is WalletAction.LoadArtwork -> {
|
||||
val artworkUrl = action.card.getArtworkUrl(action.artworkId)
|
||||
?: when (state.twinCardsState.cardNumber) {
|
||||
|
|
@ -271,21 +284,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
}
|
||||
newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl))
|
||||
}
|
||||
is WalletAction.ShowDialog.SignedHashesMultiWalletDialog -> {
|
||||
newState = newState.copy(walletDialog = WalletDialog.SignedHashesMultiWalletDialog)
|
||||
}
|
||||
is WalletAction.ShowDialog.ChooseTradeActionDialog -> {
|
||||
newState = newState.copy(walletDialog = WalletDialog.ChooseTradeActionDialog)
|
||||
}
|
||||
is WalletAction.HideDialog -> {
|
||||
newState = newState.copy(walletDialog = null)
|
||||
}
|
||||
is WalletAction.Send.ChooseCurrency -> {
|
||||
newState = newState.copy(
|
||||
walletDialog = WalletDialog.SelectAmountToSendDialog(action.amounts)
|
||||
)
|
||||
}
|
||||
is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null)
|
||||
is WalletAction.TradeCryptoAction -> return newState
|
||||
is WalletAction.ChangeSelectedAddress -> {
|
||||
val selectedWalletData = newState.getWalletData(newState.selectedCurrency)
|
||||
|
|
@ -305,23 +303,20 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
)
|
||||
}
|
||||
is WalletAction.SetWalletRent -> {
|
||||
var walletData = newState.getWalletData(action.blockchain)
|
||||
if (walletData == null) {
|
||||
newState
|
||||
} else {
|
||||
walletData =
|
||||
walletData.copy(warningRent = WalletRent(action.minRent, action.rentExempt))
|
||||
newState = newState.updateWalletsData(listOf(walletData))
|
||||
}
|
||||
val walletStore = newState.getWalletStore(action.wallet) ?: return newState
|
||||
val walletRent = WalletRent(action.minRent, action.rentExempt)
|
||||
val walletsData = walletStore.walletsData.map { it.copy(walletRent = walletRent) }
|
||||
newState = newState.updateWalletsData(walletsData)
|
||||
}
|
||||
is WalletAction.RemoveWalletRent -> {
|
||||
var walletData = newState.getWalletData(action.blockchain)
|
||||
if (walletData == null) {
|
||||
newState
|
||||
} else {
|
||||
walletData = walletData.copy(warningRent = null)
|
||||
newState = newState.updateWalletsData(listOf(walletData))
|
||||
}
|
||||
val walletStore = newState.getWalletStore(action.wallet) ?: return newState
|
||||
val walletsData = walletStore.walletsData.map { it.copy(walletRent = null) }
|
||||
newState = newState.updateWalletsData(walletsData)
|
||||
}
|
||||
is WalletAction.AppCurrencyAction -> {
|
||||
newState = appCurrencyReducer.reduce(action, newState)
|
||||
}
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
return newState
|
||||
|
|
@ -376,56 +371,83 @@ private fun handleCheckSignedHashesActions(
|
|||
|
||||
|
||||
private fun setNewFiatRate(
|
||||
fiatRate: Pair<Currency, BigDecimal?>,
|
||||
appCurrency: FiatCurrencyName, state: WalletState
|
||||
fiatRates: Map<Currency, BigDecimal?>,
|
||||
appCurrency: FiatCurrency,
|
||||
state: WalletState
|
||||
): WalletState {
|
||||
val rate = fiatRate.second ?: return state
|
||||
val rateFormatted = rate.toFormattedCurrencyString(2, appCurrency, RoundingMode.HALF_UP)
|
||||
val currency = fiatRate.first
|
||||
val rateFormatter: (BigDecimal) -> String = { rate: BigDecimal ->
|
||||
rate.toFiatRateString(
|
||||
fiatCurrencyName = appCurrency.symbol
|
||||
)
|
||||
}
|
||||
|
||||
return if (!state.isMultiwalletAllowed) {
|
||||
setSingeWalletFiatRate(rate, rateFormatted, currency, appCurrency, state)
|
||||
return if (state.isMultiwalletAllowed) {
|
||||
setMultiWalletFiatRate(
|
||||
fiatRates = fiatRates.mapNotNullValues { it.value },
|
||||
rateFormatter = rateFormatter,
|
||||
appCurrency = appCurrency,
|
||||
state = state
|
||||
)
|
||||
} else {
|
||||
setMultiWalletFiatRate(rate, rateFormatted, currency, appCurrency, state)
|
||||
val fiatRate = fiatRates.entries.firstOrNull()
|
||||
val currency = fiatRate?.key ?: return state
|
||||
val rate = fiatRate.value ?: return state
|
||||
|
||||
setSingleWalletFiatRate(
|
||||
rate = rate,
|
||||
rateFormatted = rateFormatter(rate),
|
||||
currency = currency,
|
||||
appCurrency = appCurrency,
|
||||
state = state
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMultiWalletFiatRate(
|
||||
rate: BigDecimal, rateFormatted: String, currency: Currency,
|
||||
appCurrency: FiatCurrencyName, state: WalletState
|
||||
fiatRates: Map<Currency, BigDecimal>,
|
||||
rateFormatter: (BigDecimal) -> String,
|
||||
appCurrency: FiatCurrency,
|
||||
state: WalletState
|
||||
): WalletState {
|
||||
val newWalletsData = fiatRates.mapNotNull { (currency, rate) ->
|
||||
val walletStore = state.getWalletStore(currency) ?: return state
|
||||
val wallet = walletStore.walletManager?.wallet
|
||||
val walletData = state.getWalletData(currency) ?: return state
|
||||
|
||||
val walletStore = state.getWalletStore(currency) ?: return state
|
||||
val wallet = walletStore.walletManager?.wallet
|
||||
val walletData = state.getWalletData(currency) ?: return state
|
||||
|
||||
val fiatAmount = when (currency) {
|
||||
is Currency.Blockchain ->
|
||||
wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatValue(rate)
|
||||
is Currency.Token ->
|
||||
wallet?.getTokenAmount(currency.token)?.value?.toFiatValue(rate)
|
||||
val fiatAmount = when (currency) {
|
||||
is Currency.Blockchain ->
|
||||
wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatValue(rate)
|
||||
is Currency.Token ->
|
||||
wallet?.getTokenAmount(currency.token)?.value?.toFiatValue(rate)
|
||||
}
|
||||
val fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(appCurrency.symbol)
|
||||
state.getWalletData(currency)?.copy(
|
||||
currencyData = walletData.currencyData.copy(
|
||||
fiatAmountFormatted = fiatAmountFormatted,
|
||||
fiatAmount = fiatAmount
|
||||
),
|
||||
fiatRate = rate,
|
||||
fiatRateString = rateFormatter(rate)
|
||||
)
|
||||
}
|
||||
val fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(appCurrency)
|
||||
val newWalletData = state.getWalletData(currency)?.copy(
|
||||
currencyData = walletData.currencyData.copy(
|
||||
fiatAmountFormatted = fiatAmountFormatted,
|
||||
fiatAmount = fiatAmount
|
||||
),
|
||||
fiatRate = rate, fiatRateString = rateFormatted
|
||||
)
|
||||
return state.updateWalletData(newWalletData)
|
||||
|
||||
return state
|
||||
.updateWalletsData(newWalletsData)
|
||||
}
|
||||
|
||||
private fun setSingeWalletFiatRate(
|
||||
rate: BigDecimal, rateFormatted: String, currency: Currency,
|
||||
appCurrency: FiatCurrencyName, state: WalletState
|
||||
private fun setSingleWalletFiatRate(
|
||||
rate: BigDecimal,
|
||||
rateFormatted: String,
|
||||
currency: Currency,
|
||||
appCurrency: FiatCurrency,
|
||||
state: WalletState
|
||||
): WalletState {
|
||||
val wallet = state.primaryWalletManager?.wallet ?: return state
|
||||
val token = wallet.getFirstToken()
|
||||
|
||||
if (currency == state.primaryWallet?.currency) {
|
||||
val fiatAmount = wallet.amounts[AmountType.Coin]?.value
|
||||
?.toFiatString(rate, appCurrency)
|
||||
?.toFiatString(rate, appCurrency.code)
|
||||
val walletData = state.primaryWallet.copy(
|
||||
currencyData = state.primaryWallet.currencyData.copy(fiatAmountFormatted = fiatAmount),
|
||||
fiatRate = rate,
|
||||
|
|
@ -433,7 +455,9 @@ private fun setSingeWalletFiatRate(
|
|||
)
|
||||
return state.updateWalletData(walletData)
|
||||
} else if (currency is Currency.Token && currency.token == token) {
|
||||
val tokenFiatAmount = wallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency)
|
||||
val tokenFiatAmount = wallet.getTokenAmount(token)
|
||||
?.value
|
||||
?.toFiatString(rate, appCurrency.code)
|
||||
val tokenData = state.primaryWallet?.currencyData?.token?.copy(
|
||||
fiatAmount = tokenFiatAmount,
|
||||
fiatRate = rate,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.ColorRes
|
||||
|
|
@ -13,18 +16,30 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.extensions.appendIfNotNull
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
|
||||
import com.tangem.tap.common.extensions.getColor
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.loadCurrenciesIcon
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.extensions.toQrCode
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.onboarding.getQRReceiveMessage
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter
|
||||
import com.tangem.tap.features.wallet.ui.test.TestWalletDetails
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -35,7 +50,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
StoreSubscriber<WalletState> {
|
||||
|
||||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||
private var dialog: Dialog? = null
|
||||
private lateinit var warningMessagesAdapter: WalletDetailWarningMessagesAdapter
|
||||
|
||||
private val binding: FragmentWalletDetailsBinding by viewBinding(FragmentWalletDetailsBinding::bind)
|
||||
|
||||
|
|
@ -72,6 +87,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
|
||||
setupTransactionsRecyclerView()
|
||||
setupButtons()
|
||||
setupWarningsRecyclerView()
|
||||
setupTestActionButton()
|
||||
}
|
||||
|
||||
|
|
@ -81,15 +97,26 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
rvPendingTransaction.adapter = pendingTransactionAdapter
|
||||
}
|
||||
|
||||
private fun setupWarningsRecyclerView() = with(binding) {
|
||||
warningMessagesAdapter = WalletDetailWarningMessagesAdapter()
|
||||
rvWarningMessages.layoutManager = LinearLayoutManager(requireContext())
|
||||
rvWarningMessages.adapter = warningMessagesAdapter
|
||||
rvWarningMessages.addItemDecoration(SpaceItemDecoration.vertical(8f))
|
||||
}
|
||||
|
||||
private fun setupButtons() = with(binding) {
|
||||
btnConfirm.text = getString(R.string.wallet_button_send)
|
||||
btnConfirm.setOnClickListener { store.dispatch(WalletAction.Send()) }
|
||||
|
||||
binding.lWalletDetails.btnShare.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
|
||||
|
||||
btnTrade.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Buy) }
|
||||
|
||||
btnSell.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
|
||||
rowButtons.onBuyClick = {
|
||||
store.dispatch(WalletAction.TradeCryptoAction.Buy)
|
||||
}
|
||||
rowButtons.onSellClick = {
|
||||
store.dispatch(WalletAction.TradeCryptoAction.Sell)
|
||||
}
|
||||
rowButtons.onTradeClick = {
|
||||
store.dispatch(WalletAction.DialogAction.ChooseTradeActionDialog)
|
||||
}
|
||||
rowButtons.onSendClick = {
|
||||
store.dispatch(WalletAction.Send())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTestActionButton() {
|
||||
|
|
@ -114,10 +141,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
setupBalanceData(selectedWallet.currencyData)
|
||||
setupButtons(selectedWallet)
|
||||
|
||||
handleDialogs(state.walletDialog)
|
||||
handleCurrencyIcon(selectedWallet)
|
||||
handleWalletRent(selectedWallet.warningRent)
|
||||
handleNotEnoughFundsOnMainCurrency(selectedWallet)
|
||||
handleWarnings(selectedWallet)
|
||||
|
||||
binding.srlWalletDetails.setOnRefreshListener {
|
||||
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
|
||||
|
|
@ -147,7 +172,6 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
}
|
||||
|
||||
private fun setupButtons(selectedWallet: WalletData) = with(binding) {
|
||||
btnConfirm.isEnabled = selectedWallet.mainButton.enabled
|
||||
lWalletDetails.btnCopy.setOnClickListener {
|
||||
selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
|
||||
store.dispatch(WalletAction.CopyAddress(addressString, requireContext()))
|
||||
|
|
@ -158,35 +182,20 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
store.dispatch(WalletAction.ShareAddress(addressString, requireContext()))
|
||||
}
|
||||
}
|
||||
btnTrade.isEnabled = selectedWallet.tradeCryptoState.buyingAllowed
|
||||
btnSell.show(selectedWallet.tradeCryptoState.sellingAllowed)
|
||||
}
|
||||
|
||||
private fun handleWalletRent(rent: WalletRent?) = with(binding) {
|
||||
val rent = rent.guard {
|
||||
lWarning.root.hide()
|
||||
return
|
||||
}
|
||||
val warningMessage = requireContext().getString(
|
||||
R.string.solana_rent_warning, rent.minRentValue, rent.rentExemptValue
|
||||
rowButtons.updateButtonsVisibility(
|
||||
buyAllowed = selectedWallet.tradeCryptoState.buyingAllowed,
|
||||
sellAllowed = selectedWallet.tradeCryptoState.sellingAllowed,
|
||||
sendAllowed = selectedWallet.mainButton.enabled,
|
||||
)
|
||||
lWarning.tvWarningMessage.text = warningMessage
|
||||
lWarning.root.show()
|
||||
}
|
||||
|
||||
private fun handleNotEnoughFundsOnMainCurrency(selectedWalletData: WalletData) = with(binding) {
|
||||
if (selectedWalletData.currency.isBlockchain()) return@with
|
||||
private fun handleWarnings(selectedWallet: WalletData) = with(binding) {
|
||||
val converter = WalletWarningConverter(requireContext())
|
||||
val warningDetails = selectedWallet.assembleWarnings().map { converter.convert(it) }
|
||||
|
||||
if (selectedWalletData.shouldShowCoinAmountWarning()) {
|
||||
val blockchainName = selectedWalletData.currency.blockchain.fullName
|
||||
val warningMessage = requireContext().getString(
|
||||
R.string.token_details_send_blocked_fee_format, blockchainName, blockchainName
|
||||
)
|
||||
lWarning.tvWarningMessage.text = warningMessage
|
||||
lWarning.root.show()
|
||||
} else {
|
||||
lWarning.root.hide()
|
||||
}
|
||||
warningMessagesAdapter.submitList(warningDetails)
|
||||
rvWarningMessages.show(warningDetails.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) {
|
||||
|
|
@ -299,24 +308,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
)
|
||||
}
|
||||
}
|
||||
binding.cardPendingTransactionWarning.show(data.status == BalanceStatus.SameCurrencyTransactionInProgress)
|
||||
}
|
||||
|
||||
private fun handleDialogs(walletDialog: StateDialog?) {
|
||||
when (walletDialog) {
|
||||
is WalletDialog.SelectAmountToSendDialog -> {
|
||||
if (dialog == null) dialog = AmountToSendDialog(requireContext()).apply {
|
||||
this.show(walletDialog.amounts)
|
||||
}
|
||||
}
|
||||
null -> {
|
||||
dialog?.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_remove -> {
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
||||
import com.tangem.tap.MainActivity
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
|
|
@ -24,8 +24,11 @@ import com.tangem.tap.domain.statePrinter.printScanResponseState
|
|||
import com.tangem.tap.domain.statePrinter.printWalletState
|
||||
import com.tangem.tap.domain.termsOfUse.CardTou
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.ui.adapters.SpacesItemDecoration
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
|
||||
import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView
|
||||
import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView
|
||||
|
|
@ -79,7 +82,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatch(WalletAction.Scan)
|
||||
}
|
||||
setupWarningsRecyclerView()
|
||||
walletView.changeWalletView(this, binding)
|
||||
|
|
@ -100,7 +103,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||
with(binding) {
|
||||
rvWarningMessages.layoutManager = layoutManager
|
||||
rvWarningMessages.addItemDecoration(SpacesItemDecoration(rvWarningMessages.dpToPx(16f).toInt()))
|
||||
rvWarningMessages.addItemDecoration(SpaceItemDecoration.all(16f))
|
||||
rvWarningMessages.adapter = warningsAdapter
|
||||
}
|
||||
}
|
||||
|
|
@ -179,8 +182,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
scanNoteResponse,
|
||||
store.state.walletState.walletManagers.map { it.wallet },
|
||||
CardTou(),
|
||||
store.state.globalState.appCurrency,
|
||||
tangemTechService = store.state.domainNetworks.tangemTechService
|
||||
))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Details))
|
||||
true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.tap.features.wallet.ui
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.common.module.ModuleMessageConverter
|
||||
import com.tangem.tap.features.wallet.models.WalletWarning
|
||||
import com.tangem.tap.features.wallet.models.WalletWarningDescription
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WalletWarningConverter(
|
||||
private val context: Context,
|
||||
) : ModuleMessageConverter<WalletWarning, WalletWarningDescription> {
|
||||
|
||||
override fun convert(message: WalletWarning): WalletWarningDescription {
|
||||
val warningMessage = when (message) {
|
||||
is WalletWarning.BalanceNotEnoughForFee -> {
|
||||
context.getString(
|
||||
R.string.token_details_send_blocked_fee_format,
|
||||
message.blockchainFullName, message.blockchainFullName
|
||||
)
|
||||
}
|
||||
WalletWarning.SolanaTokensUnsupported -> {
|
||||
context.getString(R.string.warning_token_send_unsupported_message)
|
||||
}
|
||||
WalletWarning.TransactionInProgress -> {
|
||||
context.getString(R.string.wallet_pending_transaction_warning)
|
||||
}
|
||||
is WalletWarning.Rent -> {
|
||||
context.getString(
|
||||
R.string.solana_rent_warning,
|
||||
message.walletRent.minRentValue, message.walletRent.rentExemptValue
|
||||
)
|
||||
}
|
||||
}
|
||||
return WalletWarningDescription(context.getString(R.string.common_warning), warningMessage)
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ class PendingTransactionsAdapter
|
|||
}
|
||||
binding.tvPendingTransaction.text = binding.root.getString(transactionDescriptionRes)
|
||||
|
||||
transaction.amountUi?.let { binding.tvPendingTransactionAmount.text = "$it " }
|
||||
transaction.amountValueUi?.let { binding.tvPendingTransactionAmount.text = "$it " }
|
||||
binding.tvPendingTransactionCurrency.text = "${transaction.currency}"
|
||||
|
||||
if (transaction.address != null) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.wallet.ui.adapters
|
|||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
|
@ -62,67 +63,63 @@ class WalletAdapter
|
|||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(wallet: WalletData) = with(binding) {
|
||||
tvCurrency.text = wallet.currencyData.currency
|
||||
tvAmount.text = wallet.currencyData.amountFormatted?.takeWhile { !it.isWhitespace() }
|
||||
tvCurrencySymbol.text =
|
||||
wallet.currencyData.amountFormatted?.takeLastWhile { !it.isWhitespace() }
|
||||
tvAmountFiat.text = wallet.currencyData.fiatAmountFormatted
|
||||
tvExchangeRate.text = wallet.fiatRateString
|
||||
cardWallet.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
|
||||
val status = wallet.currencyData.status
|
||||
val isCustomCurrency = wallet.currency.isCustomCurrency(
|
||||
derivationStyle = store.state.globalState
|
||||
.scanResponse
|
||||
?.card
|
||||
?.derivationStyle
|
||||
)
|
||||
val statusMessage = when (status) {
|
||||
BalanceStatus.TransactionInProgress -> {
|
||||
root.getString(R.string.wallet_balance_tx_in_progress)
|
||||
}
|
||||
BalanceStatus.Unreachable -> {
|
||||
root.getString(R.string.wallet_balance_blockchain_unreachable)
|
||||
}
|
||||
BalanceStatus.NoAccount -> {
|
||||
root.getString(R.string.wallet_error_no_account)
|
||||
}
|
||||
BalanceStatus.VerifiedOnline,
|
||||
BalanceStatus.SameCurrencyTransactionInProgress,
|
||||
BalanceStatus.EmptyCard,
|
||||
BalanceStatus.UnknownBlockchain,
|
||||
BalanceStatus.Loading,
|
||||
null -> null
|
||||
}
|
||||
val blockchain = wallet.currency.blockchain
|
||||
val token = (wallet.currency as? Currency.Token)?.token
|
||||
|
||||
val isCustom =
|
||||
wallet.currency.isCustomCurrency(store.state.globalState.scanResponse?.card?.derivationStyle)
|
||||
tvExchangeRate.show(!isCustom)
|
||||
tvCustomCurrency.show(isCustom)
|
||||
if (status == null || status == BalanceStatus.Loading) {
|
||||
lContent.root.hide()
|
||||
lShimmer.root.veil()
|
||||
} else {
|
||||
lShimmer.root.unVeil()
|
||||
lContent.root.show()
|
||||
}
|
||||
|
||||
Picasso.get().loadCurrenciesIcon(
|
||||
imageView = ivCurrency,
|
||||
textView = tvTokenLetter,
|
||||
token = token, blockchain = blockchain,
|
||||
token = (wallet.currency as? Currency.Token)?.token,
|
||||
blockchain = wallet.currency.blockchain,
|
||||
)
|
||||
|
||||
when (wallet.currencyData.status) {
|
||||
BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress -> hideWarning(isCustom)
|
||||
BalanceStatus.Loading -> {
|
||||
hideWarning(isCustom)
|
||||
if (wallet.currencyData.amountFormatted == null) {
|
||||
tvExchangeRate.text = root.getString(R.string.wallet_balance_loading)
|
||||
}
|
||||
}
|
||||
BalanceStatus.TransactionInProgress ->
|
||||
showWarning(root.getString(R.string.wallet_balance_tx_in_progress), isCustom)
|
||||
BalanceStatus.Unreachable ->
|
||||
showWarning(root.getString(R.string.wallet_balance_blockchain_unreachable), isCustom)
|
||||
lContent.tvCurrency.text = wallet.currencyData.currency
|
||||
lContent.tvAmountFiat.text = wallet.currencyData.fiatAmountFormatted ?: "—"
|
||||
lContent.tvAmount.text = wallet.currencyData.amountFormatted ?: "—"
|
||||
|
||||
BalanceStatus.NoAccount ->
|
||||
showWarning(root.getString(R.string.wallet_error_no_account), isCustom)
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
lContent.tvStatus.isVisible = statusMessage != null
|
||||
lContent.tvStatus.text = statusMessage
|
||||
|
||||
private fun showWarning(message: String, isCustom: Boolean = false) {
|
||||
toggleWarning(true, isCustom)
|
||||
binding.tvStatusErrorMessage.text = message
|
||||
}
|
||||
|
||||
private fun hideWarning(isCustom: Boolean = false) {
|
||||
toggleWarning(false, isCustom)
|
||||
}
|
||||
|
||||
private fun toggleWarning(show: Boolean, isCustom: Boolean = false) {
|
||||
if (!show) {
|
||||
binding.tvExchangeRate.show(!isCustom)
|
||||
binding.tvCustomCurrency.show(isCustom)
|
||||
lContent.tvExchangeRate.isVisible = statusMessage == null
|
||||
lContent.tvExchangeRate.text = if (isCustomCurrency) {
|
||||
root.getString(id = R.string.token_item_no_rate)
|
||||
} else {
|
||||
binding.tvExchangeRate.hide()
|
||||
binding.tvCustomCurrency.hide()
|
||||
wallet.fiatRateString
|
||||
}
|
||||
|
||||
cardWallet.setOnClickListener {
|
||||
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
|
||||
}
|
||||
binding.tvStatusErrorMessage.show(show)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tap.common.extensions.getColor
|
||||
import com.tangem.tap.features.wallet.models.WalletWarningDescription
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutWarningCardBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WalletDetailWarningMessagesAdapter
|
||||
: ListAdapter<WalletWarningDescription, WalletDetailsWarningMessageVH>(DiffUtilCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletDetailsWarningMessageVH {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = LayoutWarningCardBinding.inflate(inflater, parent, false)
|
||||
|
||||
return WalletDetailsWarningMessageVH(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: WalletDetailsWarningMessageVH, position: Int) {
|
||||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
private class DiffUtilCallback : DiffUtil.ItemCallback<WalletWarningDescription>() {
|
||||
override fun areContentsTheSame(oldItem: WalletWarningDescription, newItem: WalletWarningDescription) =
|
||||
oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(oldItem: WalletWarningDescription, newItem: WalletWarningDescription) =
|
||||
oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
class WalletDetailsWarningMessageVH(
|
||||
val binding: LayoutWarningCardBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(warning: WalletWarningDescription) {
|
||||
binding.warningCard.setCardBackgroundColor(binding.root.getColor(R.color.darkGray2))
|
||||
setText(warning)
|
||||
}
|
||||
|
||||
private fun setText(warning: WalletWarningDescription) = with(binding.warningContentContainer) {
|
||||
tvTitle.text = warning.title
|
||||
tvMessage.text = warning.message
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Rect
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -9,10 +8,14 @@ import androidx.core.os.ConfigurationCompat
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
|
||||
import com.google.android.play.core.review.ReviewManagerFactory
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.getActivity
|
||||
import com.tangem.tap.common.extensions.getColor
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
|
|
@ -20,13 +23,13 @@ import com.tangem.tap.features.feedback.RateCanBeBetterEmail
|
|||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutWarningBinding
|
||||
import com.tangem.wallet.databinding.LayoutWarningCardActionBinding
|
||||
import timber.log.Timber
|
||||
|
||||
class WarningMessagesAdapter : ListAdapter<WarningMessage, WarningMessageVH>(DiffUtilCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WarningMessageVH {
|
||||
val binding = LayoutWarningBinding.inflate(
|
||||
val binding = LayoutWarningCardActionBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return WarningMessageVH(binding)
|
||||
|
|
@ -45,7 +48,7 @@ class WarningMessagesAdapter : ListAdapter<WarningMessage, WarningMessageVH>(Dif
|
|||
}
|
||||
}
|
||||
|
||||
class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(warning: WarningMessage) {
|
||||
setBgColor(warning.priority)
|
||||
|
|
@ -53,7 +56,7 @@ class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHol
|
|||
setupControlButtons(warning)
|
||||
}
|
||||
|
||||
private fun setText(warning: WarningMessage) = with(binding) {
|
||||
private fun setText(warning: WarningMessage) = with(binding.warningContentContainer) {
|
||||
fun getString(resId: Int?, default: String, formatArgs: String? = null) =
|
||||
if (resId == null) default else root.getString(resId, formatArgs)
|
||||
|
||||
|
|
@ -71,7 +74,7 @@ class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHol
|
|||
WarningMessage.Priority.Warning -> R.color.warning_warning
|
||||
WarningMessage.Priority.Critical -> R.color.warning_critical
|
||||
}
|
||||
binding.cardView.setCardBackgroundColor(binding.root.getColor(color))
|
||||
binding.warningCardAction.setCardBackgroundColor(binding.root.getColor(color))
|
||||
}
|
||||
|
||||
private fun setupControlButtons(warning: WarningMessage) = when (warning.type) {
|
||||
|
|
@ -89,7 +92,7 @@ class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHol
|
|||
when {
|
||||
warning.titleResId == R.string.warning_important_security_info -> {
|
||||
View.OnClickListener {
|
||||
store.dispatch(WalletAction.ShowDialog.SignedHashesMultiWalletDialog)
|
||||
store.dispatch(WalletAction.DialogAction.SignedHashesMultiWalletDialog)
|
||||
}
|
||||
}
|
||||
warning.messageResId == R.string.alert_funds_restoration_message -> {
|
||||
|
|
@ -115,7 +118,7 @@ class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHol
|
|||
val buttonTitle = binding.root.getString(
|
||||
warning.buttonTextId ?: R.string.how_to_got_it_button
|
||||
)
|
||||
binding.btnGotIt.setOnClickListener (buttonAction)
|
||||
binding.btnGotIt.setOnClickListener(buttonAction)
|
||||
binding.btnGotIt.text = buttonTitle
|
||||
}
|
||||
WarningMessage.Type.AppRating -> {
|
||||
|
|
@ -161,19 +164,4 @@ class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHol
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpacesItemDecoration(private val spacePx: Int) : ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
outRect.left = spacePx
|
||||
outRect.right = spacePx
|
||||
|
||||
outRect.top = spacePx / 2
|
||||
outRect.top = spacePx / 2
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,29 @@
|
|||
package com.tangem.tap.features.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.recyclerview.widget.*
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.DialogWalletSendBinding
|
||||
import com.tangem.wallet.databinding.ItemWalletAmountToSendBinding
|
||||
|
||||
class AmountToSendDialog(context: Context) : BottomSheetDialog(context) {
|
||||
class AmountToSendBottomSheetDialog(
|
||||
context: Context,
|
||||
private val dialog: WalletDialog.SelectAmountToSendDialog,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
var binding: DialogWalletSendBinding? = null
|
||||
|
||||
|
|
@ -26,29 +33,25 @@ class AmountToSendDialog(context: Context) : BottomSheetDialog(context) {
|
|||
setContentView(binding!!.root)
|
||||
}
|
||||
|
||||
override fun setOnCancelListener(listener: DialogInterface.OnCancelListener?) {
|
||||
super.setOnCancelListener(listener)
|
||||
binding = null
|
||||
}
|
||||
override fun show() {
|
||||
super.show()
|
||||
|
||||
fun show(amounts: List<Amount>?) {
|
||||
this.setOnDismissListener {
|
||||
store.dispatch(WalletAction.Send.Cancel)
|
||||
setOnDismissListener {
|
||||
binding = null
|
||||
store.dispatch(WalletAction.DialogAction.Hide)
|
||||
}
|
||||
|
||||
binding!!.rvAmountsToSend.layoutManager = LinearLayoutManager(context)
|
||||
val dividerItemDecoration = DividerItemDecoration(
|
||||
ContextThemeWrapper(binding!!.root.context, R.style.AppTheme),
|
||||
DividerItemDecoration.VERTICAL
|
||||
ContextThemeWrapper(binding!!.root.context, R.style.AppTheme),
|
||||
DividerItemDecoration.VERTICAL
|
||||
)
|
||||
binding!!.rvAmountsToSend.addItemDecoration(dividerItemDecoration)
|
||||
|
||||
val viewAdapter = ChooseAmountAdapter()
|
||||
binding!!.rvAmountsToSend.adapter = viewAdapter
|
||||
|
||||
viewAdapter.submitList(amounts)
|
||||
|
||||
show()
|
||||
viewAdapter.submitList(dialog.amounts)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,22 +72,22 @@ class ChooseAmountAdapter
|
|||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<Amount>() {
|
||||
override fun areContentsTheSame(
|
||||
oldItem: Amount, newItem: Amount
|
||||
oldItem: Amount, newItem: Amount
|
||||
) = oldItem.currencySymbol == newItem.currencySymbol
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: Amount, newItem: Amount
|
||||
oldItem: Amount, newItem: Amount
|
||||
) = oldItem == newItem
|
||||
}
|
||||
|
||||
class AmountViewHolder(val binding: ItemWalletAmountToSendBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(amount: Amount) = with(binding) {
|
||||
tvCurrencySymbol.text = amount.currencySymbol
|
||||
tvAmount.text = amount.value?.toFormattedString(amount.decimals)
|
||||
root.setOnClickListener {
|
||||
store.dispatch(WalletAction.Send.Cancel)
|
||||
store.dispatch(WalletAction.DialogAction.Hide)
|
||||
store.dispatch(WalletAction.Send(amount))
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.databinding.DialogWalletTradeBinding
|
||||
|
||||
class ChooseTradeActionDialog(context: Context) : BottomSheetDialog(context) {
|
||||
class ChooseTradeActionBottomSheetDialog(context: Context) : BottomSheetDialog(context) {
|
||||
|
||||
var binding: DialogWalletTradeBinding? = null
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ class ChooseTradeActionDialog(context: Context) : BottomSheetDialog(context) {
|
|||
|
||||
this.setOnDismissListener {
|
||||
binding = null
|
||||
store.dispatch(WalletAction.HideDialog)
|
||||
store.dispatch(WalletAction.DialogAction.Hide)
|
||||
}
|
||||
|
||||
binding!!.dialogBtnBuy.setOnClickListener {
|
||||
|
|
@ -24,7 +24,7 @@ class SignedHashesWarningDialog {
|
|||
}
|
||||
setNegativeButton(R.string.common_cancel) { _, _ -> }
|
||||
setOnDismissListener {
|
||||
store.dispatch(WalletAction.HideDialog)
|
||||
store.dispatch(WalletAction.DialogAction.Hide)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.tangem.blockchain.common.Amount
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.common.TestAction
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.tap.features.wallet.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.view.isVisible
|
||||
import com.tangem.wallet.databinding.ViewWalletDetailsButtonsRowBinding
|
||||
|
||||
internal class WalletDetailsButtonsRow @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
private val binding = ViewWalletDetailsButtonsRowBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
this
|
||||
)
|
||||
|
||||
var onBuyClick: (() -> Unit)? = null
|
||||
var onSellClick: (() -> Unit)? = null
|
||||
var onTradeClick: (() -> Unit)? = null
|
||||
var onSendClick: (() -> Unit)? = null
|
||||
|
||||
init {
|
||||
orientation = HORIZONTAL
|
||||
|
||||
with(binding) {
|
||||
btnBuy.setOnClickListener { onBuyClick?.invoke() }
|
||||
btnSell.setOnClickListener { onSellClick?.invoke() }
|
||||
btnTrade.setOnClickListener { onTradeClick?.invoke() }
|
||||
btnSend.setOnClickListener { onSendClick?.invoke() }
|
||||
}
|
||||
}
|
||||
|
||||
fun updateButtonsVisibility(
|
||||
buyAllowed: Boolean,
|
||||
sellAllowed: Boolean,
|
||||
sendAllowed: Boolean
|
||||
) = with(binding) {
|
||||
btnBuy.isVisible = (buyAllowed && !sellAllowed) || (!buyAllowed && !sellAllowed)
|
||||
btnBuy.isEnabled = buyAllowed
|
||||
btnSell.isVisible = !buyAllowed && sellAllowed
|
||||
btnTrade.isVisible = buyAllowed && sellAllowed
|
||||
btnSend.isEnabled = sendAllowed
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object CurrencySelectionDialog {
|
||||
fun create(
|
||||
dialog: WalletDialog.CurrencySelectionDialog,
|
||||
context: Context
|
||||
): AlertDialog {
|
||||
val currenciesToShow = dialog.currenciesList
|
||||
.map { it.displayName }
|
||||
.toTypedArray()
|
||||
var currentSelection = dialog.currenciesList
|
||||
.indexOfFirst { it.code == dialog.currentAppCurrency.code }
|
||||
|
||||
return AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.details_row_title_currency))
|
||||
.setNegativeButton(context.getString(R.string.common_cancel)) { _, _ -> /* no-op */ }
|
||||
.setPositiveButton(context.getString(R.string.common_done)) { _, _ ->
|
||||
dialog.currenciesList.getOrNull(currentSelection)?.let { selectedCurrency ->
|
||||
store.dispatch(
|
||||
WalletAction.AppCurrencyAction.SelectAppCurrency(
|
||||
fiatCurrency = selectedCurrency
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.setOnDismissListener {
|
||||
store.dispatchDialogHide()
|
||||
}
|
||||
.setSingleChoiceItems(currenciesToShow, currentSelection) { _, which ->
|
||||
currentSelection = which
|
||||
}
|
||||
.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,24 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import android.app.Dialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.common.extensions.animateVisibility
|
||||
import com.tangem.tap.common.extensions.formatAmountAsSpannedString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.models.TotalBalance
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletDialog
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.wallet.ui.adapters.WalletAdapter
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentWalletBinding
|
||||
|
|
@ -28,7 +28,6 @@ class MultiWalletView : WalletView {
|
|||
|
||||
private var fragment: WalletFragment? = null
|
||||
private var binding: FragmentWalletBinding? = null
|
||||
private var dialog: Dialog? = null
|
||||
|
||||
private lateinit var walletsAdapter: WalletAdapter
|
||||
|
||||
|
|
@ -37,7 +36,6 @@ class MultiWalletView : WalletView {
|
|||
setFragment(fragment, binding)
|
||||
onViewCreated()
|
||||
showMultiWalletView(binding)
|
||||
setupButtons(binding)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -48,7 +46,6 @@ class MultiWalletView : WalletView {
|
|||
lAddress.root.hide()
|
||||
lButtonsShort.root.hide()
|
||||
lButtonsLong.root.hide()
|
||||
btnScanMultiwallet.show()
|
||||
rvMultiwallet.show()
|
||||
btnAddToken.show()
|
||||
setupWalletCardNumber(binding)
|
||||
|
|
@ -66,11 +63,6 @@ class MultiWalletView : WalletView {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun setupButtons(binding: FragmentWalletBinding) = with(binding) {
|
||||
btnScanMultiwallet.setOnClickListener { store.dispatch(WalletAction.Scan) }
|
||||
}
|
||||
|
||||
override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
|
||||
this.fragment = fragment
|
||||
this.binding = binding
|
||||
|
|
@ -97,6 +89,7 @@ class MultiWalletView : WalletView {
|
|||
val fragment = fragment ?: return
|
||||
val binding = binding ?: return
|
||||
|
||||
handleTotalBalance(binding, state.totalBalance)
|
||||
walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken)
|
||||
|
||||
binding.btnAddToken.setOnClickListener {
|
||||
|
|
@ -124,7 +117,33 @@ class MultiWalletView : WalletView {
|
|||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
|
||||
}
|
||||
handleErrorStates(state = state, binding = binding, fragment = fragment)
|
||||
handleDialogs(state.walletDialog)
|
||||
}
|
||||
|
||||
private fun handleTotalBalance(
|
||||
binding: FragmentWalletBinding,
|
||||
totalBalance: TotalBalance?,
|
||||
) = with(binding.lCardTotalBalance) {
|
||||
root.isVisible = totalBalance != null
|
||||
if (totalBalance != null) {
|
||||
if (totalBalance.state == TotalBalance.State.Loading) {
|
||||
veilBalance.veil()
|
||||
} else {
|
||||
veilBalance.unVeil()
|
||||
}
|
||||
tvProcessing.animateVisibility(
|
||||
show = totalBalance.state == TotalBalance.State.SomeTokensFailed
|
||||
)
|
||||
|
||||
tvBalance.text = if (totalBalance.state == TotalBalance.State.SomeTokensFailed) "—"
|
||||
else totalBalance.fiatAmount.formatAmountAsSpannedString(
|
||||
currencySymbol = totalBalance.fiatCurrency.symbol
|
||||
)
|
||||
tvCurrencyName.text = totalBalance.fiatCurrency.code
|
||||
|
||||
tvCurrencyName.setOnClickListener {
|
||||
store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleErrorStates(
|
||||
|
|
@ -148,6 +167,8 @@ class MultiWalletView : WalletView {
|
|||
fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
|
||||
)
|
||||
}
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,27 +188,10 @@ class MultiWalletView : WalletView {
|
|||
|
||||
private fun configureButtonsForEmptyWalletState(binding: FragmentWalletBinding) =
|
||||
with(binding) {
|
||||
btnScanMultiwallet.hide()
|
||||
lButtonsLong.root.show()
|
||||
lButtonsLong.btnScanLong.setOnClickListener { store.dispatch(WalletAction.Scan) }
|
||||
lButtonsLong.btnConfirmLong.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
|
||||
lButtonsLong.btnConfirmLong.text =
|
||||
fragment?.getText(R.string.wallet_button_create_wallet)
|
||||
}
|
||||
|
||||
private fun handleDialogs(walletDialog: StateDialog?) {
|
||||
val fragment = fragment ?: return
|
||||
val context = fragment.context ?: return
|
||||
when (walletDialog) {
|
||||
is WalletDialog.SignedHashesMultiWalletDialog -> {
|
||||
if (dialog == null) {
|
||||
dialog = SignedHashesWarningDialog.create(context).apply { show() }
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
dialog?.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import android.app.Dialog
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
|
|
@ -10,17 +9,18 @@ import com.tangem.tap.common.extensions.beginDelayedTransition
|
|||
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.TradeCryptoState
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletMainButton
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidget
|
||||
import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.ChooseTradeActionDialog
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentWalletBinding
|
||||
|
|
@ -30,7 +30,6 @@ class SingleWalletView : WalletView {
|
|||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||
private var fragment: WalletFragment? = null
|
||||
private var binding: FragmentWalletBinding? = null
|
||||
private var dialog: Dialog? = null
|
||||
|
||||
override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
|
||||
this.fragment = fragment
|
||||
|
|
@ -51,7 +50,6 @@ class SingleWalletView : WalletView {
|
|||
private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
|
||||
rvMultiwallet.hide()
|
||||
btnAddToken.hide()
|
||||
btnScanMultiwallet.hide()
|
||||
rvPendingTransaction.hide()
|
||||
lCardBalance.root.show()
|
||||
lAddress.root.show()
|
||||
|
|
@ -80,7 +78,6 @@ class SingleWalletView : WalletView {
|
|||
setupAddressCard(state.primaryWallet, binding)
|
||||
showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
|
||||
setupBalance(state, state.primaryWallet)
|
||||
handleDialogs(state.walletDialog, fragment)
|
||||
}
|
||||
|
||||
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
|
||||
|
|
@ -153,7 +150,14 @@ class SingleWalletView : WalletView {
|
|||
}
|
||||
}
|
||||
lAddress.btnShowQr.setOnClickListener {
|
||||
store.dispatch(WalletAction.ShowDialog.QrCode)
|
||||
state.walletAddresses?.selectedAddress?.let { selectedAddress ->
|
||||
store.dispatch(
|
||||
WalletAction.DialogAction.QrCode(
|
||||
currency = state.currency,
|
||||
selectedAddress = selectedAddress
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setupTradeButton(binding, state.tradeCryptoState)
|
||||
|
|
@ -165,7 +169,7 @@ class SingleWalletView : WalletView {
|
|||
val action = when {
|
||||
allowedToBuy && !allowedToSell -> WalletAction.TradeCryptoAction.Buy
|
||||
!allowedToBuy && allowedToSell -> WalletAction.TradeCryptoAction.Sell
|
||||
allowedToBuy && allowedToSell -> WalletAction.ShowDialog.ChooseTradeActionDialog
|
||||
allowedToBuy && allowedToSell -> WalletAction.DialogAction.ChooseTradeActionDialog
|
||||
else -> null
|
||||
}
|
||||
val text = when {
|
||||
|
|
@ -257,28 +261,4 @@ class SingleWalletView : WalletView {
|
|||
binding.lAddress.root.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDialogs(walletDialog: StateDialog?, fragment: WalletFragment) {
|
||||
val context = fragment.context ?: return
|
||||
when (walletDialog) {
|
||||
is WalletDialog.SelectAmountToSendDialog -> {
|
||||
if (dialog == null) dialog = AmountToSendDialog(context).apply {
|
||||
this.show(walletDialog.amounts)
|
||||
}
|
||||
}
|
||||
is WalletDialog.SignedHashesMultiWalletDialog -> {
|
||||
if (dialog == null) {
|
||||
dialog = SignedHashesWarningDialog.create(context).apply { show() }
|
||||
}
|
||||
}
|
||||
is WalletDialog.ChooseTradeActionDialog -> {
|
||||
if (dialog == null) dialog = ChooseTradeActionDialog(context).apply { show() }
|
||||
}
|
||||
null -> {
|
||||
dialog?.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package com.tangem.tap.network.coinmarketcap
|
||||
|
||||
import com.tangem.network.common.AddHeaderInterceptor
|
||||
import com.tangem.network.common.createRetrofitInstance
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface CoinMarketCapApi {
|
||||
|
||||
@GET("v1/tools/price-conversion")
|
||||
suspend fun getRateInfo(
|
||||
@Query("amount") amount: Int,
|
||||
@Query("symbol") cryptoCurrencyName: String,
|
||||
@Query("convert") fiatCurrencyName: String? = null
|
||||
): RateInfoResponse
|
||||
|
||||
@GET("v1/fiat/map")
|
||||
suspend fun getFiatMap(): FiatMapResponse
|
||||
|
||||
|
||||
companion object {
|
||||
private const val baseUrl = "https://pro-api.coinmarketcap.com/"
|
||||
|
||||
fun create(apiKey: String): CoinMarketCapApi {
|
||||
return createRetrofitInstance(
|
||||
baseUrl = baseUrl,
|
||||
interceptors = listOf(
|
||||
AddHeaderInterceptor(mapOf("X-CMC_PRO_API_KEY" to apiKey)),
|
||||
),
|
||||
).create(CoinMarketCapApi::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package com.tangem.tap.network.coinmarketcap
|
||||
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
//TODO: refactoring: move to the domain module and aggregate it as the alternative service for TangemTech
|
||||
class CoinMarketCapService() {
|
||||
private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create(getApiKey()) }
|
||||
|
||||
private fun getApiKey(): String {
|
||||
return store.state.globalState.configManager?.config?.coinMarketCapKey ?: ""
|
||||
}
|
||||
|
||||
suspend fun getRate(
|
||||
currency: String, fiatCurrency: FiatCurrencyName? = null
|
||||
): Result<BigDecimal> = withContext(Dispatchers.IO) {
|
||||
val response = performRequest { api.getRateInfo(1, currency, fiatCurrency) }
|
||||
return@withContext when (response) {
|
||||
is Result.Success -> Result.Success(response.data.data.getRate())
|
||||
is Result.Failure -> response
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getFiatCurrencies(): Result<List<FiatCurrency>> = withContext(Dispatchers.IO) {
|
||||
val response = performRequest { api.getFiatMap() }
|
||||
return@withContext when (response) {
|
||||
is Result.Success -> Result.Success(response.data.data.sortedBy { it.name })
|
||||
is Result.Failure -> response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package com.tangem.tap.network.coinmarketcap
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class RateInfoResponse : CoinMarketResponse<RateData>()
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RateData(
|
||||
val quote: Map<String, CurrencyRate>
|
||||
) {
|
||||
fun getRate(): BigDecimal = quote.values.first().price
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrencyRate(
|
||||
val price: BigDecimal
|
||||
)
|
||||
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Status(
|
||||
val timestamp: String,
|
||||
@Json(name = "error_code")
|
||||
val errorCode: Int,
|
||||
@Json(name = "error_message")
|
||||
val errorMessage: String?,
|
||||
val elapsed: Int,
|
||||
@Json(name = "credit_count")
|
||||
val creditCount: Int,
|
||||
val notice: String?
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class FiatMapResponse : CoinMarketResponse<List<FiatCurrency>>()
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
open class CoinMarketResponse<T : Any> {
|
||||
lateinit var status: Status
|
||||
lateinit var data: T
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class FiatCurrency(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val sign: String,
|
||||
val symbol: String
|
||||
)
|
||||
|
|
@ -30,7 +30,7 @@ interface ExchangeUrlBuilder {
|
|||
action: CurrencyExchangeManager.Action,
|
||||
blockchain: Blockchain,
|
||||
cryptoCurrencyName: CryptoCurrencyName,
|
||||
fiatCurrency: String,
|
||||
fiatCurrencyName: String,
|
||||
walletAddress: String,
|
||||
): String?
|
||||
|
||||
|
|
@ -74,13 +74,19 @@ class CurrencyExchangeManager(
|
|||
action: Action,
|
||||
blockchain: Blockchain,
|
||||
cryptoCurrencyName: CryptoCurrencyName,
|
||||
fiatCurrency: String,
|
||||
fiatCurrencyName: String,
|
||||
walletAddress: String,
|
||||
): String? {
|
||||
if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl()
|
||||
|
||||
val urlBuilder = getExchangeUrlBuilder(action)
|
||||
return urlBuilder.getUrl(action, blockchain, cryptoCurrencyName, fiatCurrency, walletAddress)
|
||||
return urlBuilder.getUrl(
|
||||
action,
|
||||
blockchain,
|
||||
cryptoCurrencyName,
|
||||
fiatCurrencyName,
|
||||
walletAddress
|
||||
)
|
||||
}
|
||||
|
||||
override fun getSellCryptoReceiptUrl(action: Action, transactionId: String): String? {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.SharedPreferences
|
|||
import androidx.core.content.edit
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.network.api.tangemTech.CurrenciesResponse
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -12,15 +13,25 @@ class FiatCurrenciesPrefStorage(
|
|||
private val preferences: SharedPreferences,
|
||||
private val converter: MoshiJsonConverter,
|
||||
) {
|
||||
private val FIAT_CURRENCIES_KEY_OLD = "fiatCurrencies"
|
||||
private val FIAT_CURRENCIES_KEY = "fiatCurrencies_v2"
|
||||
|
||||
fun migrate() {
|
||||
preferences.edit(true) {
|
||||
remove(FIAT_CURRENCIES_KEY_OLD)
|
||||
remove(APP_CURRENCY_KEY_OLD)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAppCurrency(): FiatCurrency {
|
||||
val json = preferences.getString(APP_CURRENCY_KEY, "")
|
||||
if (json.isNullOrBlank()) return FiatCurrency.Default
|
||||
|
||||
return converter.fromJson(json) ?: FiatCurrency.Default
|
||||
}
|
||||
|
||||
fun saveAppCurrency(fiatCurrency: FiatCurrency) {
|
||||
val json = converter.toJson(fiatCurrency)
|
||||
preferences.edit { putString(APP_CURRENCY_KEY, json) }
|
||||
}
|
||||
|
||||
fun save(currencies: List<CurrenciesResponse.Currency>) {
|
||||
val json: String = converter.toJson(currencies)
|
||||
return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply()
|
||||
|
|
@ -33,4 +44,12 @@ class FiatCurrenciesPrefStorage(
|
|||
|
||||
return converter.fromJson(json, type) ?: emptyList()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FIAT_CURRENCIES_KEY_OLD = "fiatCurrencies"
|
||||
private const val APP_CURRENCY_KEY_OLD = "appCurrency"
|
||||
|
||||
private const val FIAT_CURRENCIES_KEY = "fiatCurrencies_v2"
|
||||
private const val APP_CURRENCY_KEY = "appCurrency_v2"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,6 @@ import android.content.Context
|
|||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
|
@ -27,15 +25,6 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
fiatCurrenciesPrefStorage.migrate()
|
||||
}
|
||||
|
||||
fun getAppCurrency(): FiatCurrencyName {
|
||||
return preferences.getString(APP_CURRENCY_KEY, DEFAULT_FIAT_CURRENCY)
|
||||
?: DEFAULT_FIAT_CURRENCY
|
||||
}
|
||||
|
||||
fun saveAppCurrency(fiatCurrencyName: FiatCurrencyName) {
|
||||
preferences.edit { putString(APP_CURRENCY_KEY, fiatCurrencyName) }
|
||||
}
|
||||
|
||||
fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1)
|
||||
|
||||
@Deprecated("Use UsedCardsPrefStorage instead")
|
||||
|
|
@ -74,7 +63,6 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
|
||||
companion object {
|
||||
private const val PREFERENCES_NAME = "tapPrefs"
|
||||
private const val APP_CURRENCY_KEY = "appCurrency"
|
||||
private const val DISCLAIMER_ACCEPTED_KEY = "disclaimerAccepted"
|
||||
private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown"
|
||||
private const val APP_LAUNCH_COUNT_KEY = "launchCount"
|
||||
|
|
|
|||
13
app/src/main/res/drawable/ic_arbitrum_no_color.xml
Normal file
13
app/src/main/res/drawable/ic_arbitrum_no_color.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<vector android:autoMirrored="true" android:height="22dp"
|
||||
android:viewportHeight="22" android:viewportWidth="22"
|
||||
android:width="22dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h22v22h-22z"/>
|
||||
<path android:fillColor="#F1F1F1" android:pathData="M11,22C17.1327,22 22,17.0354 22,11C22,4.8673 17.0354,0 11,0C4.8673,0 0,4.9646 0,11C0,17.0354 4.9646,22 11,22Z"/>
|
||||
<path android:fillColor="#A1A1A4" android:pathData="M16.5486,12.9469L13.6283,8.469L12.5575,10.2212L15.3805,14.7964L16.646,14.2124L16.5486,12.9469Z"/>
|
||||
<path android:fillColor="#A1A1A4" android:pathData="M15.186,15.3805L12.2656,10.9026L11.0001,12.8495L13.3364,16.5486L14.7966,15.9645L15.186,15.3805Z"/>
|
||||
<path android:fillColor="#A1A1A4" android:fillType="evenOdd" android:pathData="M5.6461,7.8851L11.0001,4.7701L16.3541,7.8851V14.1152L11.0001,17.2303L7.0089,14.9913L7,16L10.9028,18.3984C11.0001,18.4958 11.0974,18.4958 11.1948,18.3984L17.3275,14.894C17.4249,14.7966 17.5222,14.6993 17.5222,14.602V7.5931C17.5222,7.4958 17.4249,7.3984 17.3275,7.3011L11.1948,3.7966C11.0974,3.6993 11.0001,3.6993 10.9028,3.7966L4.77,7.2037C4.6727,7.3011 4.5753,7.3984 4.5753,7.4958V14.5046C4.5753,14.602 4.6727,14.6993 4.77,14.7966L6,15.5L6.5,14.5L5.7435,14.1152V7.8851H5.6461Z"/>
|
||||
<path android:fillColor="#A1A1A4" android:pathData="M13.3364,7.96H11.2921L6.5222,15.7476L8.1771,16.721L13.3364,7.96Z"/>
|
||||
<path android:fillColor="#A1A1A4" android:pathData="M8.1771,8.6636C8.6638,7.8849 10.0266,7.8849 10.6107,7.9822L6,15.5C5.8053,15.4027 5.3541,15.1857 5.062,14.9911C4.6727,14.7964 4.5753,14.7964 4.5753,14.5043V14.1149C5.6461,12.5574 7.6904,9.4424 8.1771,8.6636Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
13
app/src/main/res/drawable/ic_arbitrum_round.xml
Normal file
13
app/src/main/res/drawable/ic_arbitrum_round.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<vector android:autoMirrored="true" android:height="22dp"
|
||||
android:viewportHeight="22" android:viewportWidth="22"
|
||||
android:width="22dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h22v22h-22z"/>
|
||||
<path android:fillColor="#2C374B" android:pathData="M11,22C17.1327,22 22,17.0354 22,11C22,4.8673 17.0354,0 11,0C4.8673,0 0,4.9646 0,11C0,17.0354 4.9646,22 11,22Z"/>
|
||||
<path android:fillColor="#24A2EE" android:pathData="M16.5486,12.9469L13.6283,8.469L12.5575,10.2212L15.3805,14.7964L16.646,14.2124L16.5486,12.9469Z"/>
|
||||
<path android:fillColor="#24A2EE" android:pathData="M15.186,15.3805L12.2656,10.9026L11.0001,12.8495L13.3364,16.5486L14.7966,15.9645L15.186,15.3805Z"/>
|
||||
<path android:fillColor="#98BCDC" android:fillType="evenOdd" android:pathData="M5.6461,7.8851L11.0001,4.7701L16.3541,7.8851V14.1152L11.0001,17.2303L7.0089,14.9913L7,16L10.9028,18.3984C11.0001,18.4958 11.0974,18.4958 11.1948,18.3984L17.3275,14.894C17.4249,14.7966 17.5222,14.6993 17.5222,14.602V7.5931C17.5222,7.4958 17.4249,7.3984 17.3275,7.3011L11.1948,3.7966C11.0974,3.6993 11.0001,3.6993 10.9028,3.7966L4.77,7.2037C4.6727,7.3011 4.5753,7.3984 4.5753,7.4958V14.5046C4.5753,14.602 4.6727,14.6993 4.77,14.7966L6,15.5L6.5,14.5L5.7435,14.1152V7.8851H5.6461Z"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M13.3364,7.96H11.2921L6.5222,15.7476L8.1771,16.721L13.3364,7.96Z"/>
|
||||
<path android:fillColor="#ffffff" android:pathData="M8.1771,8.6636C8.6638,7.8849 10.0266,7.8849 10.6107,7.9822L6,15.5C5.8053,15.4027 5.3541,15.1857 5.062,14.9911C4.6727,14.7964 4.5753,14.7964 4.5753,14.5043V14.1149C5.6461,12.5574 7.6904,9.4424 8.1771,8.6636Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -1,20 +1,14 @@
|
|||
<vector android:autoMirrored="true" android:height="16dp"
|
||||
android:viewportHeight="16" android:viewportWidth="16"
|
||||
android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#F1F1F1" android:pathData="M8,16C12.4183,16 16,12.4183 16,8C16,3.5817 12.4183,0 8,0C3.5817,0 0,3.5817 0,8C0,12.4183 3.5817,16 8,16Z"/>
|
||||
<vector android:autoMirrored="true" android:height="22dp"
|
||||
android:viewportHeight="22" android:viewportWidth="22"
|
||||
android:width="22dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#F1F1F1" android:pathData="M11,22C17.0751,22 22,17.0751 22,11C22,4.9249 17.0751,0 11,0C4.9249,0 0,4.9249 0,11C0,17.0751 4.9249,22 11,22Z"/>
|
||||
<group>
|
||||
<clip-path android:pathData="M8,2.8001L8,2.8001A3.2,3.2 0,0 1,11.2001 6.0001L11.2001,10A3.2,3.2 0,0 1,8 13.2001L8,13.2001A3.2,3.2 0,0 1,4.8 10L4.8,6.0001A3.2,3.2 0,0 1,8 2.8001z"/>
|
||||
<path android:fillAlpha="0.8" android:fillColor="#343434"
|
||||
android:pathData="M7.9992,2.8001L7.9293,3.037V9.9122L7.9992,9.9817L11.1982,8.0953L7.9992,2.8001Z" android:strokeAlpha="0.8"/>
|
||||
<path android:fillAlpha="0.8" android:fillColor="#8C8C8C"
|
||||
android:pathData="M7.9991,2.8001L4.8,8.0953L7.9991,9.9818V6.6448V2.8001Z" android:strokeAlpha="0.8"/>
|
||||
<path android:fillAlpha="0.8" android:fillColor="#3C3C3B"
|
||||
android:pathData="M7.999,10.5862L7.9596,10.634V13.0831L7.999,13.1978L11.1999,8.7007L7.999,10.5862Z" android:strokeAlpha="0.8"/>
|
||||
<path android:fillAlpha="0.8" android:fillColor="#8C8C8C"
|
||||
android:pathData="M7.9991,13.1979V10.5862L4.8,8.7007L7.9991,13.1979Z" android:strokeAlpha="0.8"/>
|
||||
<path android:fillAlpha="0.8" android:fillColor="#141414"
|
||||
android:pathData="M7.999,9.9817L11.198,8.0953L7.999,6.6448V9.9817Z" android:strokeAlpha="0.8"/>
|
||||
<path android:fillAlpha="0.8" android:fillColor="#393939"
|
||||
android:pathData="M4.8,8.0953L7.9991,9.9818V6.6448L4.8,8.0953Z" android:strokeAlpha="0.8"/>
|
||||
<clip-path android:pathData="M11.0001,3.8501L11.0001,3.8501A4.4,4.4 0,0 1,15.4001 8.2501L15.4001,13.7501A4.4,4.4 0,0 1,11.0001 18.1501L11.0001,18.1501A4.4,4.4 0,0 1,6.6001 13.7501L6.6001,8.2501A4.4,4.4 0,0 1,11.0001 3.8501z"/>
|
||||
<path android:fillColor="#343434" android:pathData="M10.999,3.8501L10.903,4.1759V13.6292L10.999,13.7249L15.3977,11.1311L10.999,3.8501Z"/>
|
||||
<path android:fillColor="#8C8C8C" android:pathData="M10.9988,3.8501L6.6001,11.1311L10.9988,13.725V9.1366V3.8501Z"/>
|
||||
<path android:fillColor="#3C3C3B" android:pathData="M10.9987,14.5559L10.9446,14.6217V17.9892L10.9987,18.147L15.4001,11.9634L10.9987,14.5559Z"/>
|
||||
<path android:fillColor="#8C8C8C" android:pathData="M10.9988,18.147V14.5559L6.6001,11.9634L10.9988,18.147Z"/>
|
||||
<path android:fillColor="#141414" android:pathData="M10.9987,13.725L15.3973,11.1312L10.9987,9.1367V13.725Z"/>
|
||||
<path android:fillColor="#393939" android:pathData="M6.6001,11.1312L10.9987,13.7251V9.1367L6.6001,11.1312Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
|
|||
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