diff --git a/app/build.gradle b/app/build.gradle
index 549774d931..76faa09bc5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -69,21 +69,25 @@ dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
- implementation 'com.google.android.material:material:1.2.1'
- coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
+ implementation 'com.google.android.material:material:1.3.0'
+ implementation "androidx.core:core-ktx:1.3.2"
+ implementation 'com.google.android.play:core:1.10.0'
+ implementation 'com.google.android.play:core-ktx:1.8.1'
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
- implementation 'com.tangem:blockchain:1.141.0'
- implementation 'com.tangem:core:1.101.0'
- implementation 'com.tangem:sdk:1.101.0'
+
+ implementation 'com.tangem:blockchain:1.166.0'
+ implementation 'com.tangem:core:1.109.0'
+ implementation 'com.tangem:sdk:1.109.0'
// WebView
implementation "androidx.browser:browser:1.3.0"
//lifecycle
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
- implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
+ implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "org.rekotlin:rekotlin:1.0.4"
@@ -103,7 +107,7 @@ dependencies {
implementation 'com.google.firebase:firebase-config-ktx'
implementation 'com.google.firebase:firebase-analytics-ktx'
- testImplementation 'junit:junit:4.13.1'
+ testImplementation 'junit:junit:4.13.2'
testImplementation "com.google.truth:truth:1.0.1"
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml
index 4cc2b19474..bf9ef4d842 100644
--- a/app/src/debug/res/values/strings.xml
+++ b/app/src/debug/res/values/strings.xml
@@ -1,6 +1,6 @@
- DEBUG
+ DebugTangem
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2b7b764bfc..ee1acb1fe8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -81,6 +81,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/erc20_tokens.json b/app/src/main/assets/erc20_tokens.json
new file mode 100644
index 0000000000..9b29c198bb
--- /dev/null
+++ b/app/src/main/assets/erc20_tokens.json
@@ -0,0 +1,914 @@
+[
+ {
+ "decimalCount" : 18,
+ "symbol" : "ZRX",
+ "name" : "0x",
+ "contractAddress" : "0xE41d2489571d322189246DaFA5ebDe1F4699F498"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AAVE",
+ "name" : "Aave",
+ "contractAddress" : "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "LEND",
+ "name" : "Aave (OLD)",
+ "contractAddress" : "0x80fB784B7eD66730e8b1DBd9820aFD29931aab03"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ADX",
+ "name" : "AdEx",
+ "contractAddress" : "0xADE00C28244d5CE17D72E40330B1c318cD12B7c3"
+ },
+ {
+ "decimalCount" : 9,
+ "symbol" : "ADT",
+ "name" : "adToken",
+ "contractAddress" : "0xD0D6D6C5Fe4a677D343cC433536BB717bAe167dD"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ELF",
+ "name" : "Aelf",
+ "contractAddress" : "0xbf2179859fc6d5bee9bf9158632dc51678a4100e"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "ARN",
+ "name" : "Aeron",
+ "contractAddress" : "0xBA5F11b16B155792Cf3B2E6880E8706859A8AEB6"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AE",
+ "name" : "Aeternity",
+ "contractAddress" : "0x5ca9a71b1d01849c0a95490cc00559717fcf0d1d"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "AION",
+ "name" : "Aion",
+ "contractAddress" : "0x4CEdA7906a5Ed2179785Cd3A40A69ee8bc99C466"
+ },
+ {
+ "decimalCount" : 4,
+ "symbol" : "AST",
+ "name" : "Airswap",
+ "contractAddress" : "0x27054b13b1b798b345b591a4d22e6562d47ea75a"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AMB",
+ "name" : "Amber",
+ "contractAddress" : "0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AMLT",
+ "name" : "AMLT by Coinfirm",
+ "contractAddress" : "0xca0e7269600d353f70b14ad118a49575455c0f2f"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "APPC",
+ "name" : "AppCoins",
+ "contractAddress" : "0x1a7a8bd9106f2b8d977e08582dc7d24c723ab0db"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ANT",
+ "name" : "Aragon",
+ "contractAddress" : "0xa117000000f279d81a1d3cc75430faa017fa5a2e"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ABT",
+ "name" : "ArcBlock",
+ "contractAddress" : "0xb98d4c97425d9908e66e53a6fdf673acca0be986"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ATMI",
+ "name" : "Atonomi",
+ "contractAddress" : "0x97aeb5066e1a590e868b511457beb6fe99d329f5"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "REP",
+ "name" : "Augur",
+ "contractAddress" : "0x1985365e9f78359a9B6AD760e32412f4a445E862"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AVM",
+ "name" : "AVM Ecosystem",
+ "contractAddress" : "0x74004a7227615fb52b82d17ffabfa376907d8a4d"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BNT",
+ "name" : "Bancor",
+ "contractAddress" : "0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BAT",
+ "name" : "Basic Attention",
+ "contractAddress" : "0x0D8775F648430679A709E98d2b0Cb6250d2887EF"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BNB",
+ "name" : "Binance Coin",
+ "contractAddress" : "0xb8c77482e45f1f44de1745f52c74426c631bdd52"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BUSD",
+ "name" : "Binance USD",
+ "contractAddress" : "0x4fabb145d64652a948d72533023f6e7a623c7c53"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STU",
+ "name" : "bitJob",
+ "contractAddress" : "0x0371a82e4a9d0a4312f3ee2ac9c6958512891372"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BIX",
+ "name" : "BIX Token",
+ "contractAddress" : "0xb3104b4b9da82025e8b9f8fb28b3553ce2f67069"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "VEE",
+ "name" : "BLOCKv",
+ "contractAddress" : "0x340d2bde5eb28c1eed91b2f790723e3b160613b7"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BLZ",
+ "name" : "Bluzelle",
+ "contractAddress" : "0x5732046a883704404f284ce41ffadd5b007fd668"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "BRD",
+ "name" : "BRD Token",
+ "contractAddress" : "0x558ec3152e2eb2174905cd19aea4e34a23de9ad6"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "BTM",
+ "name" : "Bytom",
+ "contractAddress" : "0xcb97e65f07da24d46bcdd078ebebd7c6e6e3d750"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CAS",
+ "name" : "Cashaa",
+ "contractAddress" : "0xe8780b48bdb05f928697a5e8155f672ed91462f7"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CSP",
+ "name" : "Caspian",
+ "contractAddress" : "0xa6446d655a0c34bc4f05042ee88170d056cbaf45"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "LINK",
+ "name" : "ChainLink",
+ "contractAddress" : "0x514910771af9ca656af840dff83e8264ecf986ca"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "TIME",
+ "name" : "Chronobank",
+ "contractAddress" : "0x6531f133e6DeeBe7F2dcE5A0441aA7ef330B4e53"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CND",
+ "name" : "Cindicator",
+ "contractAddress" : "0xd4c435f5b09f855c3317c8524cb1f586e42795fa"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "CVC",
+ "name" : "Civic",
+ "contractAddress" : "0x41e5560054824ea6b0732e656e3ad64e20e94e45"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CNN",
+ "name" : "CNN Token",
+ "contractAddress" : "0x8713d26637cf49e1b6b4a7ce57106aabc9325343"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CFI",
+ "name" : "Cofound.it",
+ "contractAddress" : "0x12FEF5e57bF45873Cd9B62E9DBd7BFb99e32D73e"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CCC",
+ "name" : "Container Crypto Coin",
+ "contractAddress" : "0x9e3359f862b6c7f5c660cfd6d1aa6909b1d9504d"
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "CS",
+ "name" : "Credits",
+ "contractAddress" : "0x46b9ad944d1059450da1163511069c718f699d31"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CRPT",
+ "name" : "Crypterium",
+ "contractAddress" : "0x08389495d7456e1951ddf7c3a1314a4bfb646d8b"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "AUTO",
+ "name" : "CUBE",
+ "contractAddress" : "0x622dFfCc4e83C64ba959530A5a5580687a57581b"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "CMT",
+ "name" : "CyberMiles",
+ "contractAddress" : "0xf85fEea2FdD81d51177F6b8F35F0e6734Ce45F5F"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "DAI",
+ "name" : "Dai",
+ "contractAddress" : "0x6b175474e89094c44da98b954eedeac495271d0f"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "MANA",
+ "name" : "Decentraland",
+ "contractAddress" : "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "DENT",
+ "name" : "DENT",
+ "contractAddress" : "0x3597bfd533a99c9aa083587b074434e61eb0a258"
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "XJP",
+ "name" : "Digital JPY",
+ "contractAddress" : "0x39689fE671C01fcE173395f6BC45D4C332026666"
+ },
+ {
+ "decimalCount" : 9,
+ "symbol" : "DGD",
+ "name" : "DigixDAO",
+ "contractAddress" : "0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "DNT",
+ "name" : "district0x",
+ "contractAddress" : "0x0abdace70d3790235af448c88547603b945604ea"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "DOV",
+ "name" : "Dovu",
+ "contractAddress" : "0xac3211a5025414af2866ff09c23fc18bc97e79b1"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "DRGN",
+ "name" : "Dragon",
+ "contractAddress" : "0x419c4db4b9e25d6db2ad9691ccb832c8d9fda05e"
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "EDG",
+ "name" : "Edgeless",
+ "contractAddress" : "0x08711D3B02C8758F2FB3ab4e80228418a7F8e39c"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "EDO",
+ "name" : "Eidoo",
+ "contractAddress" : "0xced4e93198734ddaff8492d525bd258d49eb388e"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "ENG",
+ "name" : "Enigma",
+ "contractAddress" : "0xf0ee6b27b759c9893ce4f094b49ad28fd15a23e4"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ENJ",
+ "name" : "EnjinCoin",
+ "contractAddress" : "0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "EOS",
+ "name" : "EOS",
+ "contractAddress" : "0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0"
+ },
+ {
+ "decimalCount" : 2,
+ "symbol" : "EUR.AVM",
+ "name" : "EUR AVM",
+ "contractAddress" : "0xF01Cd2f1c9E42c509d309aC8C5b29B6dA8E64b1a"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "XUC",
+ "name" : "Exchange Union",
+ "contractAddress" : "0xc324a2f6b05880503444451b8b27e6f9e63287cb"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "1ST",
+ "name" : "FirstBlood",
+ "contractAddress" : "0xAf30D2a7E90d7DC361c8C4585e9BB7D2F6f15bc7"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "FXC",
+ "name" : "Flexacoin",
+ "contractAddress" : "0x4a57E687b9126435a9B19E4A802113e266AdeBde"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "FUN",
+ "name" : "FunFair",
+ "contractAddress" : "0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "FSN",
+ "name" : "Fusion",
+ "contractAddress" : "0xd0352a019e9ab9d757776f532377aaebd36fd541"
+ },
+ {
+ "decimalCount" : 2,
+ "symbol" : "GUSD",
+ "name" : "Gemini Dollar",
+ "contractAddress" : "0x056fd409e1d7a124bd7017459dfea2f387b6d5cd"
+ },
+ {
+ "decimalCount" : 9,
+ "symbol" : "GNX",
+ "name" : "Genaro Network",
+ "contractAddress" : "0x6ec8a24cabdc339a06a172f8223ea557055adaa5"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "GVT",
+ "name" : "Genesis Vision",
+ "contractAddress" : "0x103c3A209da59d3E7C4A89307e66521e081CFDF0"
+ },
+ {
+ "decimalCount" : 5,
+ "symbol" : "GTO",
+ "name" : "Gifto",
+ "contractAddress" : "0xc5bbae50781be1669306b9e001eff57a2957b09d"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "GBX",
+ "name" : "Globitex",
+ "contractAddress" : "0x12fcd6463e66974cf7bbc24ffc4d40d6be458283"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "GNO",
+ "name" : "Gnosis",
+ "contractAddress" : "0x6810e776880C02933D47DB1b9fc05908e5386b96"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "GNT",
+ "name" : "Golem",
+ "contractAddress" : "0xa74476443119A942dE498590Fe1f2454d7D4aC0d"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "HPB",
+ "name" : "HPBCoin",
+ "contractAddress" : "0x38c6a68304cdefb9bec48bbfaaba5c5b47818bb2"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "HYDRO",
+ "name" : "Hydro",
+ "contractAddress" : "0xebbdf302c940c6bfd49c6b165f457fdb324649bc"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ICX",
+ "name" : "ICON",
+ "contractAddress" : "0xb5a5f22694352c15b00323844ad545abb2b11028"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ICN",
+ "name" : "ICONOMI",
+ "contractAddress" : "0x888666CA69E0f178DED6D75b5726Cee99A87D698"
+ },
+ {
+ "decimalCount" : 9,
+ "symbol" : "RLC",
+ "name" : "iExec RLC",
+ "contractAddress" : "0x607F4C5BB672230e8672085532f7e901544a7375"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "IOST",
+ "name" : "IOStoken",
+ "contractAddress" : "0xfa1a856cfa3409cfa145fa4e20eb270df3eb21ab"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ITC",
+ "name" : "IOT Chain",
+ "contractAddress" : "0x5e6b6d9abad9093fdc861ea1600eba1b355cd940"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "IXT",
+ "name" : "iXledger",
+ "contractAddress" : "0xfca47962d45adfdfd1ab2d972315db4ce7ccf094"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "KIN",
+ "name" : "Kin",
+ "contractAddress" : "0x818Fc6C2Ec5986bc6E2CBf00939d90556aB12ce5"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "KNC",
+ "name" : "Kyber Network",
+ "contractAddress" : "0xdd974D5C2e2928deA5F71b9825b8b646686BD200"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "LOOM",
+ "name" : "Loom",
+ "contractAddress" : "0xa4e8c3ec456107ea67d3075bf9e3df3a75823db0"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "LRC",
+ "name" : "Loopring",
+ "contractAddress" : "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "LUN",
+ "name" : "Lunyr",
+ "contractAddress" : "0xfa05A73FfE78ef8f1a739473e462c54bae6567D9"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "MKR",
+ "name" : "Maker",
+ "contractAddress" : "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2"
+ },
+ {
+ "decimalCount" : 3,
+ "symbol" : "GUP",
+ "name" : "Matchpool",
+ "contractAddress" : "0xf7B098298f7C69Fc14610bf71d5e02c60792894C"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "MAN",
+ "name" : "MATRIX AI Network",
+ "contractAddress" : "0xe25bcec5d3801ce3a794079bf94adf1b8ccd802d"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "MTL",
+ "name" : "Metal",
+ "contractAddress" : "0xF433089366899D83a9f26A773D59ec7eCF30355e"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "MCO",
+ "name" : "Monaco",
+ "contractAddress" : "0xB63B606Ac810a52cCa15e44bB630fd42D8d1d83d"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "MYST",
+ "name" : "Mysterium",
+ "contractAddress" : "0xa645264C5603E96c3b0B078cdab68733794B0A71"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "NAS",
+ "name" : "Nebulas",
+ "contractAddress" : "0x5d65D971895Edc438f465c17DB6992698a52318D"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "NEXO",
+ "name" : "Nexo",
+ "contractAddress" : "0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "NCASH",
+ "name" : "Nucleus Vision",
+ "contractAddress" : "0x809826cceab68c387726af962713b64cb5cb3cca"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "NULS",
+ "name" : "Nuls",
+ "contractAddress" : "0xb91318f35bdb262e9423bc7c7c2a3a93dd93c92c"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "NMR",
+ "name" : "Numeraire",
+ "contractAddress" : "0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "OMG",
+ "name" : "OmiseGO",
+ "contractAddress" : "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "OXT",
+ "name" : "Orchid",
+ "contractAddress" : "0x4575f41308ec1483f3d399aa9a2826d74da13deb"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "OST",
+ "name" : "OST",
+ "contractAddress" : "0x2C4e8f2D746113d0696cE89B35F0d8bF88E0AEcA"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "PRL",
+ "name" : "Oyster Pearl",
+ "contractAddress" : "0x1844b21593262668b7248d0f57a220caaba46ab9"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "PTOY",
+ "name" : "Patientory",
+ "contractAddress" : "0x8Ae4BF2C33a8e667de34B54938B0ccD03Eb8CC06"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "PAX",
+ "name" : "Paxos Standard",
+ "contractAddress" : "0x8e870d67f660d95d5be530380d0ec0bd388289e1"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "PMTN",
+ "name" : "Peer Mountain",
+ "contractAddress" : "0xe91df2bb9bccd18c45f01843cab88768d64f2d32"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "POE",
+ "name" : "Po.et",
+ "contractAddress" : "0x0e0989b1f9b8a38983c2ba8053269ca62ec9b195"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "PAL",
+ "name" : "PolicyPal Network",
+ "contractAddress" : "0xfeDAE5642668f8636A11987Ff386bfd215F942EE"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "POLY",
+ "name" : "Polymath",
+ "contractAddress" : "0x9992eC3cF6A55b00978cdDF2b27BC6882d88D1eC"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "PPT",
+ "name" : "Populous",
+ "contractAddress" : "0xd4fa1460F537bb9085d22C7bcCB5DD450Ef28e3a"
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "POWR",
+ "name" : "Power Ledger",
+ "contractAddress" : "0x595832f8fc6bf59c85c527fec3740a1b7a361269"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "NPXS",
+ "name" : "Pundi X Token",
+ "contractAddress" : "0xa15c7ebe1f07caf6bff097d8a589fb8ac49ae5b3"
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "QASH",
+ "name" : "QASH",
+ "contractAddress" : "0x618e75ac90b12c6049ba3b27f5d5f8651b0037f6"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "QRL",
+ "name" : "QRL",
+ "contractAddress" : "0x697beac28B09E122C4332D163985e8a73121b97F"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "QSP",
+ "name" : "Quantstamp",
+ "contractAddress" : "0x99ea4dB9EE77ACD40B119BD1dC4E33e1C070b80d"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "RDN",
+ "name" : "Raiden Network",
+ "contractAddress" : "0x255aa6df07540cb5d3d297f0d0d4d84cb52bc8e6"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "RCC",
+ "name" : "Reality Clash",
+ "contractAddress" : "0x9b6443b0fb9c241a7fdac375595cea13e6b7807a"
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "RC1U03",
+ "name" : "RentalCoin 1.0",
+ "contractAddress" : "0x7384db1aaa06364d1e6e67f5434391d3eea2a038"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "REQ",
+ "name" : "Request",
+ "contractAddress" : "0x8f8221aFbB33998d8584A2B05749bA73c37a938a"
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "REV",
+ "name" : "Revain",
+ "contractAddress" : "0x48f775efbe4f5ece6e0df2f7b5932df56823b990"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "RCN",
+ "name" : "Ripio Credit",
+ "contractAddress" : "0xf970b8e36e23f7fc3fd752eea86f8be8d83375a6"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "RVT",
+ "name" : "Rivetz",
+ "contractAddress" : "0x3d1ba9be9f66b8ee101911bc36d3fb562eac2244"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "RUFF",
+ "name" : "RUFF",
+ "contractAddress" : "0xf278c1ca969095ffddded020290cf8b5c424ace2"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SAI",
+ "name" : "Sai",
+ "contractAddress" : "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "SALT",
+ "name" : "SALT",
+ "contractAddress" : "0x4156D3342D5c385a87D264F90653733592000581"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SAN",
+ "name" : "SAN",
+ "contractAddress" : "0x7C5A0CE9267ED19B22F8cae653F198e3E8daf098"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "SENT",
+ "name" : "SENTinel",
+ "contractAddress" : "0xa44e5137293e855b1b7bc7e2c6f8cd796ffcb037"
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "SNGLS",
+ "name" : "Singular DTV",
+ "contractAddress" : "0xaeC2E87E0A235266D9C5ADc9DEb4b2E29b54D009"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "AGI",
+ "name" : "SingularityNET",
+ "contractAddress" : "0x8eB24319393716668D768dCEC29356ae9CfFe285"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SRN",
+ "name" : "SIRIN",
+ "contractAddress" : "0x68d57c9a1C35f63E2c83eE8e49A64e9d70528D25"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SNM",
+ "name" : "SONM",
+ "contractAddress" : "0x983F6d60db79ea8cA4eB9968C6aFf8cfA04B3c63"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SNT",
+ "name" : "Status",
+ "contractAddress" : "0x744d70FDBE2Ba4CF95131626614a1763DF805B9E"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STK",
+ "name" : "STK",
+ "contractAddress" : "0xae73b38d1c9a8b274127ec30160a4927c4d71824"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STQ",
+ "name" : "Storiqa",
+ "contractAddress" : "0x5c3a228510d246b78a3765c20221cbf3082b44a4"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "STORJ",
+ "name" : "Storj",
+ "contractAddress" : "0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STORM",
+ "name" : "Storm",
+ "contractAddress" : "0xD0a4b8946Cb52f0661273bfbC6fD0E0C75Fc6433"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STMX",
+ "name" : "StormX",
+ "contractAddress" : "0xbE9375C6a420D2eEB258962efB95551A5b722803"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "STX",
+ "name" : "Stox",
+ "contractAddress" : "0x006BeA43Baa3f7A6f765F14f10A1a1b08334EF45"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SUB",
+ "name" : "Substratum",
+ "contractAddress" : "0x8D75959f1E61EC2571aa72798237101F084DE63a"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SNC",
+ "name" : "SunContract",
+ "contractAddress" : "0xF4134146AF2d511Dd5EA8cDB1C4AC88C57D60404"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "SUSHI",
+ "name" : "SushiSwap",
+ "contractAddress" : "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2"
+ },
+ {
+ "decimalCount" : 2,
+ "symbol" : "TEL",
+ "name" : "Telcoin",
+ "contractAddress" : "0x467bccd9d29f223bce8043b84e8c8b282827790f"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "PAY",
+ "name" : "TenX",
+ "contractAddress" : "0xB97048628DB6B661D4C2aA833e95Dbe1A905B280"
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "USDT",
+ "name" : "Tether USD",
+ "contractAddress" : "0xdac17f958d2ee523a2206206994597c13d831ec7"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "THETA",
+ "name" : "Theta Token",
+ "contractAddress" : "0x3883f5e181fccaf8410fa61e12b59bad963fb645"
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "TRX",
+ "name" : "TRON",
+ "contractAddress" : "0xf230b790e05390fc8295f4d3f60332c93bed42e2"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "TUSD",
+ "name" : "TrueUSD",
+ "contractAddress" : "0x0000000000085d4780B73119b644AE5ecd22b376"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "UCASH",
+ "name" : "UCASH",
+ "contractAddress" : "0x92e52a1a235d9a103d970901066ce910aacefd37"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "UNI",
+ "name" : "Uniswap",
+ "contractAddress" : "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "USDC",
+ "name" : "USD Coin",
+ "contractAddress" : "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "VIB",
+ "name" : "Viberate",
+ "contractAddress" : "0x2C974B2d0BA1716E644c1FC59982a89DDD2fF724"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "VGX",
+ "name" : "Voyager",
+ "contractAddress" : "0x5af2be193a6abca9c8817001f45744777db30756"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "WTC",
+ "name" : "Waltonchain",
+ "contractAddress" : "0xb7cb1c96db6b22b0d3d9536e0108d062bd488f74"
+ },
+ {
+ "decimalCount" : 8,
+ "symbol" : "WAX",
+ "name" : "WAX",
+ "contractAddress" : "0x39Bb259F66E1C59d5ABEF88375979b4D20D98022"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "WPR",
+ "name" : "WePower",
+ "contractAddress" : "0x4CF488387F035FF08c371515562CBa712f9015d4"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "WINGS",
+ "name" : "Wings",
+ "contractAddress" : "0x667088b212ce3d06a1b553a7221E1fD19000d9aF"
+ },
+ {
+ "decimalCount" : 6,
+ "symbol" : "WRC",
+ "name" : "Worldcore",
+ "contractAddress" : "0x72adadb447784dd7ab1f472467750fc485e4cb2d"
+ },
+ {
+ "decimalCount" : 18,
+ "symbol" : "ZLA",
+ "name" : "Zilla",
+ "contractAddress" : "0xfd8971d5e8e1740ce2d0a84095fca4de729d0c16"
+ },
+ {
+ "decimalCount" : 0,
+ "symbol" : "GD",
+ "name" : "Zilla GD",
+ "contractAddress" : "0xb4cebdb66b3a5183fd4764a25cad1fc109535016"
+ },
+ {
+ "decimalCount" : 12,
+ "symbol" : "ZIL",
+ "name" : "Zilliqa",
+ "contractAddress" : "0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27"
+ }
+]
diff --git a/app/src/main/assets/tangem-android-config b/app/src/main/assets/tangem-android-config
new file mode 160000
index 0000000000..9a731fcb5a
--- /dev/null
+++ b/app/src/main/assets/tangem-android-config
@@ -0,0 +1 @@
+Subproject commit 9a731fcb5a58d53c90d7db79e5441bdf18e087fc
diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt
index 2a20c88628..78c6c7dbd6 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -5,10 +5,12 @@ import android.content.pm.ActivityInfo
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Bundle
+import android.util.Log
+import android.view.View
import androidx.appcompat.app.AppCompatActivity
+import com.google.android.material.snackbar.Snackbar
import com.tangem.CardFilter
import com.tangem.Config
-import com.tangem.Log
import com.tangem.TangemSdk
import com.tangem.commands.common.card.CardType
import com.tangem.tangem_sdk_new.extensions.init
@@ -46,15 +48,19 @@ private fun initCoroutineExceptionHandler(): CoroutineExceptionHandler {
val sw = StringWriter()
throwable.printStackTrace(PrintWriter(sw))
val exceptionAsString: String = sw.toString()
- Log.e("TangemSdk", exceptionAsString)
+ Log.e("Coroutine", exceptionAsString)
throw throwable
}
}
class MainActivity : AppCompatActivity() {
+
+ private var snackbar: Snackbar? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
+ store.state.globalState.feedbackManager?.updateAcivity(this)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
@@ -103,4 +109,21 @@ class MainActivity : AppCompatActivity() {
store.dispatch(NavigationAction.ActivityDestroyed)
super.onDestroy()
}
+
+ fun showSnackbar(text: Int, buttonTitle: Int? = null, action: View.OnClickListener? = null) {
+ if (snackbar != null) return
+
+ snackbar = Snackbar.make(
+ fragment_container, getString(text), Snackbar.LENGTH_INDEFINITE
+ )
+ if (buttonTitle != null && action != null) {
+ snackbar?.setAction(getString(buttonTitle), action)
+ }
+ snackbar?.show()
+ }
+
+ fun dismissSnackbar() {
+ snackbar?.dismiss()
+ snackbar = null
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt
index cf26114c15..e458fd4e04 100644
--- a/app/src/main/java/com/tangem/tap/TapApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TapApplication.kt
@@ -1,14 +1,23 @@
package com.tangem.tap
import android.app.Application
+import com.google.firebase.ktx.Firebase
+import com.google.firebase.remoteconfig.ktx.remoteConfig
+import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
+import com.tangem.Log
import com.tangem.tap.common.images.PicassoHelper
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.domain.config.ConfigLoader
-import com.tangem.tap.domain.config.ConfigManager
-import com.tangem.tap.domain.config.LocalLoader
-import com.tangem.tap.domain.config.RemoteLoader
+import com.tangem.tap.domain.configurable.config.ConfigManager
+import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
+import com.tangem.tap.domain.configurable.config.FeaturesRemoteLoader
+import com.tangem.tap.domain.configurable.warningMessage.RemoteWarningLoader
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
+import com.tangem.tap.domain.tokens.CurrenciesRepository
+import com.tangem.tap.features.feedback.AdditionalEmailInfo
+import com.tangem.tap.features.feedback.FeedbackManager
+import com.tangem.tap.features.feedback.TangemLogCollector
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.network.createMoshi
import com.tangem.tap.persistence.PreferencesStorage
@@ -22,27 +31,49 @@ val store = Store(
state = AppState()
)
lateinit var preferencesStorage: PreferencesStorage
+lateinit var currenciesRepository: CurrenciesRepository
class TapApplication : Application() {
override fun onCreate() {
super.onCreate()
- if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
+ if (BuildConfig.DEBUG) {
+ Timber.plant(Timber.DebugTree())
+ Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
+ this.minimumFetchIntervalInSeconds = 60
+ })
+ } else {
+ Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
+ this.minimumFetchIntervalInSeconds = 3600
+ })
+ }
- ConfigLoader.init()
NetworkConnectivity.createInstance(store, this)
preferencesStorage = PreferencesStorage(this)
PicassoHelper.initPicassoWithCaching(this)
+ currenciesRepository = CurrenciesRepository(this)
+ initFeedbackManager()
loadConfigs()
}
-
private fun loadConfigs() {
val moshi = createMoshi()
- val localLoader = LocalLoader(this, moshi)
- val remoteLoader = RemoteLoader(moshi)
+ val localLoader = FeaturesLocalLoader(this, moshi)
+ val remoteLoader = FeaturesRemoteLoader(moshi)
val configManager = ConfigManager(localLoader, remoteLoader)
configManager.load { store.dispatch(GlobalAction.SetConfigManager(configManager)) }
+ val warningsManager = WarningMessagesManager(RemoteWarningLoader(moshi))
+ warningsManager.load { store.dispatch(GlobalAction.SetWarningManager(warningsManager)) }
+ }
+
+ private fun initFeedbackManager() {
+ val infoHolder = AdditionalEmailInfo()
+ infoHolder.setAppVersion(this)
+
+ val logWriter = TangemLogCollector()
+ Log.addLogger(logWriter)
+
+ store.dispatch(GlobalAction.SetFeedbackManager(FeedbackManager(infoHolder, logWriter)))
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
index 932897e204..4395bd0922 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt
@@ -4,4 +4,8 @@ enum class AnalyticsEvent(val event: String) {
CARD_IS_SCANNED("card_is_scanned"),
TRANSACTION_IS_SENT("transaction_is_sent"),
READY_TO_SCAN("ready_to_scan"),
+ APP_RATING_DISPLAYED("rate_app_warning_displayed"),
+ APP_RATING_DISMISS("dismiss_rate_app_warning"),
+ APP_RATING_NEGATIVE("negative_rate_app_feedback"),
+ APP_RATING_POSITIVE("positive_rate_app_feedback"),
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
index 9dddeee4f2..1dc3586808 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt
@@ -4,5 +4,5 @@ import com.tangem.commands.common.card.Card
interface AnalyticsHandler {
- fun triggerEvent(event: AnalyticsEvent, card: Card?)
+ fun triggerEvent(event: AnalyticsEvent, card: Card? = null)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
index 0d49728dd6..d1d3ce7a38 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt
@@ -8,8 +8,7 @@ import com.tangem.commands.common.card.Card
object FirebaseAnalyticsHandler : AnalyticsHandler {
override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
- Firebase.analytics
- .logEvent(event.event, setCardData(card))
+ Firebase.analytics.logEvent(event.event, setCardData(card))
}
fun logException(name: String, throwable: Throwable) {
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt
new file mode 100644
index 0000000000..b2905ec473
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt
@@ -0,0 +1,6 @@
+package com.tangem.tap.common.extensions
+
+import android.content.res.AssetManager
+
+fun AssetManager.readJsonFileToString(fileName: String): String =
+ this.open("$fileName.json").bufferedReader().readText()
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
new file mode 100644
index 0000000000..dc12172a82
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
@@ -0,0 +1,23 @@
+package com.tangem.tap.common.extensions
+
+import androidx.annotation.DrawableRes
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.wallet.R
+
+@DrawableRes
+fun Blockchain.getIconRes(): Int {
+ return when (this) {
+ Blockchain.Unknown -> R.drawable.shape_circle
+ Blockchain.Ducatus -> R.drawable.ic_ducatus
+ Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_btc
+ Blockchain.BitcoinCash -> R.drawable.ic_btc_cash
+ Blockchain.Litecoin -> R.drawable.ic_ltc
+ Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth
+ Blockchain.RSK -> R.drawable.ic_rsk
+ Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano
+ Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_binance
+ Blockchain.Tezos -> R.drawable.ic_tezos
+ Blockchain.XRP -> R.drawable.ic_xrp
+ Blockchain.Stellar -> R.drawable.ic_stellar
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt
new file mode 100644
index 0000000000..a280071a02
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt
@@ -0,0 +1,11 @@
+package com.tangem.tap.common.extensions
+
+/**
+[REDACTED_AUTHOR]
+ */
+fun List.containsAny(list: List): Boolean {
+ this.forEach { mainItem ->
+ list.forEach { if (it == mainItem) return true }
+ }
+ return false
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
index 56f0d70061..6e348ea8cf 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
@@ -12,6 +12,8 @@ import com.tangem.tap.features.details.ui.twins.TwinWalletWarningFragment
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
import com.tangem.tap.features.home.HomeFragment
import com.tangem.tap.features.send.ui.SendFragment
+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.tap.features.wallet.ui.dialogs.TwinsOnboardingFragment
import com.tangem.wallet.R
@@ -46,5 +48,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.CreateTwinWalletWarning -> TwinWalletWarningFragment()
AppScreen.CreateTwinWallet -> CreateTwinWalletFragment()
AppScreen.TwinsOnboarding -> TwinsOnboardingFragment()
+ AppScreen.AddTokens -> AddTokensFragment()
+ AppScreen.WalletDetails -> WalletDetailsFragment()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
index 42fc659da8..8ed277a345 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
@@ -1,11 +1,5 @@
package com.tangem.tap.common.extensions
-import android.graphics.Bitmap
-import android.graphics.Color
-import com.google.zxing.BarcodeFormat
-import com.google.zxing.EncodeHintType
-import com.google.zxing.qrcode.QRCodeWriter
-import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.network.coinmarketcap.FiatCurrency
@@ -15,27 +9,6 @@ import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.*
-
-fun String.toQrCode(): Bitmap {
- val hintMap = Hashtable()
- hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
- hintMap[EncodeHintType.MARGIN] = 2
-
- val qrCodeWriter = QRCodeWriter()
-
- val size = 256
-
- val bitMatrix = qrCodeWriter.encode(this, BarcodeFormat.QR_CODE, size, size, hintMap)
- val width = bitMatrix.width
- val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565)
- for (x in 0 until width) {
- for (y in 0 until width) {
- bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE)
- }
- }
- return bmp
-}
-
fun BigDecimal.toFormattedString(
decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US
): String {
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt
new file mode 100644
index 0000000000..f0f4ef1d00
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt
@@ -0,0 +1,62 @@
+package com.tangem.tap.common.extensions
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.text.Spannable
+import android.text.style.ForegroundColorSpan
+import androidx.core.content.ContextCompat
+import androidx.core.text.toSpannable
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.EncodeHintType
+import com.google.zxing.qrcode.QRCodeWriter
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
+import java.util.*
+
+fun String?.ellipsizeBeforeSpace(allowedSize: Int): String {
+ if (this.isNullOrBlank()) return ""
+ val size = this.length
+ val sizeDifference = size - allowedSize
+ val endIndex = this.indexOf(" ")
+ val startIndex = endIndex - sizeDifference
+ val newString = this.removeRange(startIndex, endIndex)
+ return newString.substring(0 until startIndex) + "..." +
+ newString.substring(startIndex until newString.length)
+}
+
+fun String.colorSegment(
+ context: Context,
+ color: Int,
+ startIndex: Int = 0,
+ endIndex: Int = this.length
+): Spannable {
+ return this.toSpannable()
+ .also { spannable ->
+ spannable.setSpan(
+ ForegroundColorSpan(ContextCompat.getColor(context, color)),
+ startIndex,
+ endIndex,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ }
+}
+
+fun String.toQrCode(): Bitmap {
+ val hintMap = Hashtable()
+ hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
+ hintMap[EncodeHintType.MARGIN] = 2
+
+ val qrCodeWriter = QRCodeWriter()
+
+ val size = 256
+
+ val bitMatrix = qrCodeWriter.encode(this, BarcodeFormat.QR_CODE, size, size, hintMap)
+ val width = bitMatrix.width
+ val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565)
+ for (x in 0 until width) {
+ for (y in 0 until width) {
+ bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE)
+ }
+ }
+ return bmp
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
index 5774e94b61..48ee67afc7 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
@@ -56,4 +56,13 @@ fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) {
error = null
isErrorEnabled = false
}
+}
+
+fun TextView.isEllipsized(): Boolean {
+ val layout = this.layout
+ if (layout != null) {
+ val lines: Int = layout.lineCount
+ return (lines > 0) && (layout.getEllipsisCount(lines - 1) > 0)
+ }
+ return false
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt
new file mode 100644
index 0000000000..fdfb83ef77
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt
@@ -0,0 +1,16 @@
+package com.tangem.tap.common.extensions
+
+import androidx.annotation.ColorInt
+import androidx.core.graphics.toColorInt
+import com.tangem.blockchain.common.Token
+import com.tangem.wallet.R
+
+@ColorInt
+fun Token.getColor(): Int {
+ return try {
+ ("#" + this.contractAddress.subSequence(2..7).toString())
+ .toColorInt()
+ } catch (exception: Exception) {
+ R.color.lightGray4
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
index c7bb0daf0f..b43c49b307 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
@@ -1,15 +1,10 @@
package com.tangem.tap.common.extensions
import android.app.Activity
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.content.Intent
+import android.content.*
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.os.Build
-import android.text.Spannable
-import android.text.style.ForegroundColorSpan
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
@@ -19,7 +14,6 @@ import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
-import androidx.core.text.toSpannable
import androidx.fragment.app.Fragment
import com.google.android.material.card.MaterialCardView
@@ -77,6 +71,9 @@ fun Context.dpToPixels(dp: Int): Int =
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics
).toInt()
+tailrec fun Context?.getActivity(): Activity? = this as? Activity ?:
+ (this as? ContextWrapper)?.baseContext?.getActivity()
+
fun MaterialCardView.setMargins(
marginLeftDp: Int = 16,
marginTopDp: Int = 8,
@@ -106,24 +103,6 @@ fun Activity.setSystemBarTextColor(setTextDark: Boolean) {
}
}
-
-fun String.colorSegment(
- context: Context,
- color: Int,
- startIndex: Int = 0,
- endIndex: Int = this.length
-): Spannable {
- return this.toSpannable()
- .also { spannable ->
- spannable.setSpan(
- ForegroundColorSpan(ContextCompat.getColor(context, color)),
- startIndex,
- endIndex,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- }
-}
-
fun View.hideKeyboard() {
val inputMethodManager = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? InputMethodManager
inputMethodManager?.hideSoftInputFromWindow(this.windowToken, 0)
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
index 5e80adec68..03dcb4a441 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
@@ -6,7 +6,8 @@ import com.tangem.tap.features.details.redux.DetailsReducer
import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
import com.tangem.tap.features.home.redux.HomeReducer
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
-import com.tangem.tap.features.wallet.redux.WalletReducer
+import com.tangem.tap.features.tokens.redux.TokensReducer
+import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
import org.rekotlin.Action
fun appReducer(action: Action, state: AppState?): AppState {
@@ -20,7 +21,8 @@ fun appReducer(action: Action, state: AppState?): AppState {
walletState = WalletReducer.reduce(action, state),
sendState = SendScreenReducer.reduce(action, state.sendState),
detailsState = DetailsReducer.reduce(action, state),
- disclaimerState = DisclaimerReducer.reduce(action, state)
+ disclaimerState = DisclaimerReducer.reduce(action, state),
+ tokensState = TokensReducer.reduce(action, state),
)
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
index e60ae97fa7..4548ca046f 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
@@ -12,8 +12,10 @@ import com.tangem.tap.features.home.redux.HomeMiddleware
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.send.redux.middlewares.sendMiddleware
import com.tangem.tap.features.send.redux.states.SendState
-import com.tangem.tap.features.wallet.redux.WalletMiddleware
+import com.tangem.tap.features.tokens.redux.TokensMiddleware
+import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
import org.rekotlin.Middleware
import org.rekotlin.StateType
@@ -24,7 +26,8 @@ data class AppState(
val walletState: WalletState = WalletState(),
val sendState: SendState = SendState(),
val detailsState: DetailsState = DetailsState(),
- val disclaimerState: DisclaimerState = DisclaimerState()
+ val disclaimerState: DisclaimerState = DisclaimerState(),
+ val tokensState: TokensState = TokensState(),
) : StateType {
companion object {
@@ -35,7 +38,8 @@ data class AppState(
WalletMiddleware().walletMiddleware,
sendMiddleware,
DetailsMiddleware().detailsMiddleware,
- DisclaimerMiddleware().disclaimerMiddleware
+ DisclaimerMiddleware().disclaimerMiddleware,
+ TokensMiddleware().tokensMiddleware,
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
index fc4f5bba9e..2a2a612757 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
@@ -1,22 +1,36 @@
package com.tangem.tap.common.redux.global
-import com.tangem.tap.domain.config.ConfigManager
+import com.tangem.common.CompletionResult
+import com.tangem.tap.domain.configurable.config.ConfigManager
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.features.details.redux.SecurityOption
+import com.tangem.tap.features.feedback.EmailData
+import com.tangem.tap.features.feedback.FeedbackManager
import org.rekotlin.Action
-import java.math.BigDecimal
sealed class GlobalAction : Action {
+ object ScanFailsCounter {
+ data class ChooseBehavior(val result: CompletionResult) : GlobalAction()
+ object Reset : GlobalAction()
+ object Increment : GlobalAction()
+ }
+
data class SaveScanNoteResponse(val scanNoteResponse: ScanNoteResponse) : GlobalAction()
- data class SetFiatRate(
- val fiatRates: Pair
- ) : GlobalAction()
data class ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction()
object RestoreAppCurrency : GlobalAction() {
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()
}
+
data class UpdateWalletSignedHashes(val walletSignedHashes: Int?) : GlobalAction()
- data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
+ data class HideWarningMessage(val warning: WarningMessage) : GlobalAction()
data class UpdateSecurityOptions(val securityOption: SecurityOption) : GlobalAction()
+
+ data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
+ data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
+ data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction()
+
+ data class SendFeedback(val emailData: EmailData) : GlobalAction()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
index d7ec41960f..dce356f1be 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
@@ -1,6 +1,12 @@
package com.tangem.tap.common.redux.global
+import com.tangem.TangemSdkError
+import com.tangem.common.CompletionResult
import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
+import com.tangem.tap.features.home.redux.HomeAction
+import com.tangem.tap.features.send.redux.SendAction
+import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.preferencesStorage
import com.tangem.tap.store
import org.rekotlin.Middleware
@@ -9,11 +15,44 @@ val globalMiddleware: Middleware = { dispatch, appState ->
{ nextDispatch ->
{ action ->
when (action) {
+ is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
+ when (action.result) {
+ is CompletionResult.Success -> store.dispatch(GlobalAction.ScanFailsCounter.Reset)
+ is CompletionResult.Failure -> {
+ if (action.result.error is TangemSdkError.UserCancelled) {
+ store.dispatch(GlobalAction.ScanFailsCounter.Increment)
+ if (store.state.globalState.scanCardFailsCounter >= 2) {
+ store.dispatch(HomeAction.ShowDialog.ScanFails)
+ store.dispatch(WalletAction.ShowDialog.ScanFails)
+ }
+ } else {
+ store.dispatch(GlobalAction.ScanFailsCounter.Reset)
+ }
+ }
+ }
+ }
is GlobalAction.RestoreAppCurrency -> {
store.dispatch(GlobalAction.RestoreAppCurrency.Success(
preferencesStorage.getAppCurrency()
))
}
+ is GlobalAction.HideWarningMessage -> {
+ store.state.globalState.warningManager?.let {
+ if (it.hideWarning(action.warning)) {
+ if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
+ //TODO: No appropriate warningMessage identification. Make it better later
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
+ }
+
+ store.dispatch(WalletAction.Warnings.Update)
+ store.dispatch(SendAction.Warnings.Update)
+ }
+ }
+ }
+
+ is GlobalAction.SendFeedback -> {
+ store.state.globalState.feedbackManager?.send(action.emailData)
+ }
}
nextDispatch(action)
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
index 7ab9334181..bfc866a06c 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
@@ -11,18 +11,19 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
val globalState = state.globalState
return when (action) {
+ is GlobalAction.ScanFailsCounter.Increment -> {
+ globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1)
+ }
+ is GlobalAction.ScanFailsCounter.Reset -> {
+ globalState.copy(scanCardFailsCounter = 0)
+ }
is GlobalAction.SaveScanNoteResponse ->
globalState.copy(scanNoteResponse = action.scanNoteResponse)
- is GlobalAction.SetFiatRate -> {
- val rates = globalState.conversionRates.rates.toMutableMap()
- rates[action.fiatRates.first] = action.fiatRates.second
- globalState.copy(conversionRates = ConversionRates(rates))
- }
is GlobalAction.ChangeAppCurrency -> {
- globalState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
+ globalState.copy(appCurrency = action.appCurrency)
}
is GlobalAction.RestoreAppCurrency.Success -> {
- globalState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
+ globalState.copy(appCurrency = action.appCurrency)
}
is GlobalAction.UpdateWalletSignedHashes -> {
val card = globalState.scanNoteResponse?.card?.copy(
@@ -37,6 +38,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.SetConfigManager -> {
globalState.copy(configManager = action.configManager)
}
+ is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
is GlobalAction.UpdateSecurityOptions -> {
val card = when (action.securityOption) {
SecurityOption.LongTap -> globalState.scanNoteResponse?.card?.copy(
@@ -55,6 +57,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
globalState
}
}
+ is GlobalAction.SetFeedbackManager -> {
+ globalState.copy(feedbackManager = action.feedbackManager)
+ }
else -> globalState
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
index f9e05cdeac..a0ac13bc06 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
@@ -4,11 +4,12 @@ import com.tangem.commands.common.network.TangemService
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapWalletManager
-import com.tangem.tap.domain.config.ConfigManager
+import com.tangem.tap.domain.configurable.config.ConfigManager
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tasks.ScanNoteResponse
+import com.tangem.tap.features.feedback.FeedbackManager
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
import org.rekotlin.StateType
-import java.math.BigDecimal
data class GlobalState(
val scanNoteResponse: ScanNoteResponse? = null,
@@ -16,20 +17,15 @@ data class GlobalState(
val payIdManager: PayIdManager = PayIdManager(),
val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(),
val tangemService: TangemService = TangemService(),
- val conversionRates: ConversionRates = ConversionRates(emptyMap()),
val configManager: ConfigManager? = null,
- val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY
+ val warningManager: WarningMessagesManager? = null,
+ val feedbackManager: FeedbackManager? = null,
+ val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY,
+ val scanCardFailsCounter: Int = 0,
) : StateType
-data class ConversionRates(
- val rates: Map,
-) {
-
- fun getRate(cryptoCurrency: CryptoCurrencyName): BigDecimal? {
- return rates[cryptoCurrency]
- }
-}
-
typealias CryptoCurrencyName = String
typealias FiatCurrencyName = String
+
+interface StateDialog
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
index 393a4e7e77..26344b0175 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
@@ -10,6 +10,6 @@ data class NavigationState(
) : StateType
enum class AppScreen {
- Home, Wallet, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer,
- CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding
+ Home, Wallet, WalletDetails, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer,
+ CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding, AddTokens
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
index 3773bd41b7..a6e329ca04 100644
--- a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
@@ -2,46 +2,13 @@ package com.tangem.tap.domain
import com.tangem.blockchain.common.Blockchain
import com.tangem.commands.common.network.Result
-import com.tangem.tap.network.payid.PayIdService
import com.tangem.tap.network.payid.PayIdVerifyService
-import com.tangem.tap.network.payid.SetPayIdResponse
import com.tangem.tap.network.payid.VerifyPayIdResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-import retrofit2.HttpException
import java.util.*
class PayIdManager {
- private val payIdService = PayIdService()
-
- suspend fun getPayId(cardId: String, publicKey: String): Result = withContext(Dispatchers.IO) {
- val result = payIdService.getPayId(cardId, publicKey)
- when (result) {
- is Result.Success -> return@withContext Result.Success(result.data.payId)
- is Result.Failure -> {
- (result.error as? HttpException)?.let {
- if (it.code() == 404) return@withContext Result.Success(null)
- }
- return@withContext result
- }
- }
- }
-
-
- suspend fun setPayId(
- cardId: String, publicKey: String, payId: String, address: String, blockchain: Blockchain
- ): Result = withContext(Dispatchers.IO) {
- val result = payIdService.setPayId(cardId, publicKey, payId, address, blockchain.getPayIdNetwork())
- when (result) {
- is Result.Success -> return@withContext result
- is Result.Failure -> {
- (result.error as? HttpException)?.let {
- if (it.code() == 409) return@withContext Result.Failure(TapError.PayIdAlreadyCreated)
- }
- return@withContext result
- }
- }
- }
suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result = withContext(Dispatchers.IO) {
val splitPayId = payId.split("\$")
diff --git a/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt b/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt
new file mode 100644
index 0000000000..b9ec4705a5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt
@@ -0,0 +1,13 @@
+package com.tangem.tap.domain
+
+class TapBlockchainConfig {
+ companion object {
+ val blockchairApiKey: String = "A___0Shpsu4KagE7oSabrw20DfXAqWlT"
+ val blockcypherTokens: Set = setOf(
+ "aa8184b0e0894b88a5688e01b3dc1e82",
+ "56c4ca23c6484c8f8864c32fde4def8d",
+ "66a8a37c5e9d4d2c9bb191acfe7f93aa"
+ )
+ val infuraProjectId: String = "613a0b14833145968b1f656240c7d245"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
index 37f159fdb8..521ed462b4 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
@@ -49,7 +49,7 @@ sealed class TapError(
}
sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemError {
- final override val code: Int = 1
+ final override val code: Int = 50100
override var customMessage: String = code.toString()
object CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card)
diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
index 36dd3c238c..497c7dde68 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
@@ -1,23 +1,23 @@
package com.tangem.tap.domain
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.Token
-import com.tangem.blockchain.common.Wallet
-import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.common.*
+import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardStatus
import com.tangem.commands.common.network.Result
-import com.tangem.common.extensions.toHexString
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.domain.config.ConfigManager
+import com.tangem.tap.currenciesRepository
+import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.extensions.isNoAccountError
import com.tangem.tap.domain.tasks.ScanNoteResponse
+import com.tangem.tap.domain.tokens.CardCurrencies
import com.tangem.tap.domain.twins.TwinsHelper
import com.tangem.tap.domain.twins.isTwinCard
+import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
@@ -28,29 +28,25 @@ import java.math.BigDecimal
class TapWalletManager {
- private val payIdManager = PayIdManager()
private val coinMarketCapService = CoinMarketCapService()
- suspend fun loadWalletData() {
- val walletManager = store.state.globalState.scanNoteResponse?.walletManager
- if (walletManager == null) {
- store.dispatch(WalletAction.LoadWallet.Failure())
- return
+ private val blockchainSdkConfig by lazy {
+ store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
+ }
+ val walletManagerFactory: WalletManagerFactory
+ by lazy { WalletManagerFactory(blockchainSdkConfig) }
+
+ suspend fun loadWalletData(walletManager: WalletManager) {
+ val result = try {
+ walletManager.update()
+ Result.Success(walletManager.wallet)
+ } catch (exception: Exception) {
+ Result.Failure(exception)
}
- loadWallet(walletManager)
+ handleUpdateWalletResult(result, walletManager)
}
- suspend fun loadPayId() {
- val result = loadPayIdIfNeeded()
- result?.let { handlePayIdResult(it) }
- }
-
- suspend fun updateWallet() {
- val walletManager = store.state.globalState.scanNoteResponse?.walletManager
- if (walletManager == null) {
- withContext(Dispatchers.Main) { store.dispatch(WalletAction.UpdateWallet.Failure()) }
- return
- }
+ suspend fun updateWallet(walletManager: WalletManager) {
val result = try {
walletManager.update()
Result.Success(walletManager.wallet)
@@ -68,12 +64,18 @@ class TapWalletManager {
}
- suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName) {
- val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet ?: return
-
+ suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
val currencyList = wallet.getTokens().map { it.symbol }.toMutableList()
currencyList.add(wallet.blockchain.currency)
+ loadFiatRate(fiatCurrency, currencyList)
+ }
+ suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, cryptoCurrencyName: CryptoCurrencyName) {
+ val currencyList = listOf(cryptoCurrencyName)
+ loadFiatRate(fiatCurrency, currencyList)
+ }
+
+ private suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, currencyList: List) {
val results = mutableListOf?>>()
currencyList.forEach { results.add(it to coinMarketCapService.getRate(it, fiatCurrency)) }
handleFiatRatesResult(results)
@@ -84,24 +86,13 @@ class TapWalletManager {
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card)
}
TapWorkarounds.updateCard(data.card)
- val configManager = store.state.globalState.configManager
- if (TapWorkarounds.isStart2Coin) {
- configManager?.turnOff(ConfigManager.isWalletPayIdEnabled)
- configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
- configManager?.turnOff(ConfigManager.isTopUpEnabled)
- } else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin
- || data.card.cardData?.blockchainName == Blockchain.Bitcoin.id){
- configManager?.turnOff(ConfigManager.isWalletPayIdEnabled)
- configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
- configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
- } else {
- configManager?.resetToDefault(ConfigManager.isWalletPayIdEnabled)
- configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
- configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
- }
+ store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card)
+ updateConfigManager(data)
+
withContext(Dispatchers.Main) {
store.dispatch(WalletAction.ResetState)
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
+ store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
if (data.card.isTwinCard()) {
val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId)
val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId)
@@ -111,26 +102,55 @@ class TapWalletManager {
))
}
}
+ if (data.walletManager?.wallet?.blockchain == Blockchain.Ethereum ||
+ data.walletManager?.wallet?.blockchain == Blockchain.EthereumTestnet) {
+ store.dispatch(TokensAction.LoadCardTokens)
+ }
loadData(data)
}
}
+ private fun updateConfigManager(data: ScanNoteResponse) {
+ val configManager = store.state.globalState.configManager
+ if (TapWorkarounds.isStart2Coin) {
+ configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
+ configManager?.turnOff(ConfigManager.isTopUpEnabled)
+ } else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin
+ || data.card.cardData?.blockchainName == Blockchain.Bitcoin.id) {
+ configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
+ configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
+ } else {
+ configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
+ configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
+ }
+ }
+
suspend fun loadData(data: ScanNoteResponse) {
withContext(Dispatchers.Main) {
- store.dispatch(WalletAction.CheckIfWarningNeeded)
val artworkId = data.verifyResponse?.artworkInfo?.id
if (data.walletManager != null) {
- if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
- store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
- return@withContext
- }
val config = store.state.globalState.configManager?.config ?: return@withContext
- store.dispatch(WalletAction.LoadWallet(
- data.walletManager.wallet, data.verifyResponse?.artworkInfo?.id, config.isTopUpEnabled))
+ val primaryWalletManager = data.walletManager
+ val primaryBlockchain = data.walletManager.wallet.blockchain
+ val primaryTokenSymbol = data.card.cardData?.tokenSymbol
+ val primaryToken = primaryWalletManager.presetTokens.toList()
+ .firstOrNull { it.symbol == primaryTokenSymbol }
+
+ store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(primaryBlockchain))
+ if (primaryToken != null) {
+ store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
+ }
+ if (data.card.isMultiwalletAllowed) {
+ loadMultiWalletData(data.card, primaryBlockchain, primaryWalletManager)
+ } else {
+ store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
+ store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain)))
+ }
+ store.dispatch(WalletAction.SetArtworkId(data.verifyResponse?.artworkInfo?.id))
+ store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
- store.dispatch(WalletAction.LoadFiatRate)
- store.dispatch(WalletAction.LoadPayId)
+ store.dispatch(WalletAction.LoadFiatRate())
} else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) {
store.dispatch(WalletAction.EmptyWallet)
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
@@ -138,17 +158,54 @@ class TapWalletManager {
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
}
+ store.dispatch(WalletAction.Warnings.CheckIfNeeded)
}
}
- private suspend fun loadWallet(walletManager: WalletManager) {
- val result = try {
- walletManager.update()
- Result.Success(walletManager.wallet)
- } catch (exception: Exception) {
- Result.Failure(exception)
+ private fun loadMultiWalletData(
+ card: Card, primaryBlockchain: Blockchain, primaryWalletManager: WalletManager
+ ) {
+ val presetTokens = primaryWalletManager.presetTokens.toList()
+ val savedCurrencies = currenciesRepository.loadCardCurrencies(card.cardId)
+
+ if (savedCurrencies == null) {
+ store.dispatch(WalletAction.MultiWallet.SaveCurrencies(
+ CardCurrencies(
+ blockchains = listOf(primaryBlockchain), tokens = presetTokens
+ )))
+ store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
+ store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain)))
+ store.dispatch(WalletAction.MultiWallet.AddTokens(presetTokens))
+ store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse(card, walletManagerFactory))
+ store.dispatch(WalletAction.MultiWallet.FindTokensInUse)
+ } else {
+ val blockchains = listOf(primaryBlockchain) + savedCurrencies.blockchains
+ val walletManagers = walletManagerFactory.makeWalletManagers(card, blockchains)
+ val tokens = presetTokens + savedCurrencies.tokens
+
+ store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))
+ store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains))
+ store.dispatch(WalletAction.MultiWallet.AddTokens(tokens))
+ store.dispatch(WalletAction.MultiWallet.FindTokensInUse)
+ }
+ }
+
+ suspend fun reloadData(data: ScanNoteResponse) {
+ withContext(Dispatchers.Main) {
+ if (data.walletManager != null) {
+ if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
+ store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
+ return@withContext
+ }
+ val config = store.state.globalState.configManager?.config ?: return@withContext
+ store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
+ store.dispatch(WalletAction.LoadFiatRate())
+ } else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) {
+ store.dispatch(WalletAction.EmptyWallet)
+ } else {
+ store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
+ }
}
- handleUpdateWalletResult(result, walletManager)
}
private suspend fun handleUpdateWalletResult(result: Result, walletManager: WalletManager) {
@@ -156,65 +213,34 @@ class TapWalletManager {
when (result) {
is Result.Success -> store.dispatch(WalletAction.LoadWallet.Success(result.data))
is Result.Failure -> {
+ if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
+ store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
+ return@withContext
+ }
val error = result.error
val blockchain = walletManager.wallet.blockchain
if (error != null && blockchain.isNoAccountError(error)) {
val token = walletManager.wallet.getFirstToken()
val amountToCreateAccount = blockchain.amountToCreateAccount(token)
if (amountToCreateAccount != null) {
- store.dispatch(WalletAction.LoadWallet.NoAccount(amountToCreateAccount.toString()))
+ store.dispatch(WalletAction.LoadWallet.NoAccount(
+ walletManager.wallet,
+ amountToCreateAccount.toString()))
return@withContext
}
}
- store.dispatch(WalletAction.LoadWallet.Failure(result.error?.localizedMessage))
+ store.dispatch(WalletAction.LoadWallet.Failure(walletManager.wallet, result.error?.localizedMessage))
}
}
}
}
-
- private suspend fun loadPayIdIfNeeded(): Result? {
- val scanNoteResponse = store.state.globalState.scanNoteResponse
- if (store.state.globalState.configManager?.config?.isWalletPayIdEnabled == false
- || scanNoteResponse?.walletManager?.wallet?.blockchain?.isPayIdSupported() == false) {
- return null
- }
- val cardId = scanNoteResponse?.card?.cardId
- val publicKey = scanNoteResponse?.card?.cardPublicKey
- if (cardId == null || publicKey == null) {
- return null
- }
- return payIdManager.getPayId(cardId, publicKey.toHexString())
- }
-
- private suspend fun handlePayIdResult(result: Result) {
- withContext(Dispatchers.Main) {
- when (result) {
- is Result.Success -> {
- val config = store.state.globalState.configManager?.config
- if (config?.isWalletPayIdEnabled == false) {
- store.dispatch(WalletAction.DisablePayId)
- return@withContext
- }
- val payId = result.data
- if (payId == null) {
- store.dispatch(WalletAction.LoadPayId.NotCreated)
- } else {
- store.dispatch(WalletAction.LoadPayId.Success(payId))
- }
- }
- is Result.Failure -> store.dispatch(WalletAction.LoadPayId.Failure)
- }
- }
- }
-
private suspend fun handleFiatRatesResult(results: List?>>) {
withContext(Dispatchers.Main) {
results.map {
when (it.second) {
is Result.Success -> {
val rate = it.first to (it.second as Result.Success).data
- store.dispatch(GlobalAction.SetFiatRate(rate))
store.dispatch(WalletAction.LoadFiatRate.Success(rate))
}
is Result.Failure -> store.dispatch(WalletAction.LoadFiatRate.Failure)
diff --git a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
index d0cc8b64b2..48526204e8 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.domain
import com.tangem.commands.common.card.Card
+import com.tangem.commands.common.card.EllipticCurve
import com.tangem.commands.common.card.masks.Product
import java.util.*
@@ -17,7 +18,7 @@ object TapWorkarounds {
val cardData = this.cardData ?: return false
val productMask = cardData.productMask
val excludedBatch = excludedBatches.contains(cardData.batchId)
- val excludedIssuerName = excludedIssuers.contains(cardData.issuerName?.capitalize(Locale.US))
+ val excludedIssuerName = excludedIssuers.contains(cardData.issuerName?.toUpperCase(Locale.US))
val excludedProductMask = (productMask != null && // product mask is on cards v2.30 and later
!productMask.contains(Product.Note) && !productMask.contains(Product.TwinCard))
return excludedBatch || excludedIssuerName || excludedProductMask
@@ -35,4 +36,11 @@ object TapWorkarounds {
private val excludedIssuers = listOf(
"TTM BANK"
)
-}
\ No newline at end of file
+}
+
+val Card.isMultiwalletAllowed: Boolean
+ get() {
+ return cardData?.productMask?.contains(Product.TwinCard) != true
+ && !TapWorkarounds.isStart2Coin
+ && this.curve == EllipticCurve.Secp256k1
+ }
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt b/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt
new file mode 100644
index 0000000000..3471ed78ee
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt
@@ -0,0 +1,16 @@
+package com.tangem.tap.domain.configurable
+
+import com.tangem.wallet.BuildConfig
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface Loader {
+ fun load(onComplete: (T) -> Unit)
+
+ companion object {
+ const val featuresName = "features_${BuildConfig.CONFIG_ENVIRONMENT}"
+ const val configValuesName = "tangem-android-config/config_${BuildConfig.CONFIG_ENVIRONMENT}"
+ const val warnings = "warnings_${BuildConfig.CONFIG_ENVIRONMENT}"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt
similarity index 58%
rename from app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt
rename to app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt
index 2e90f4cb16..a47e8fec1c 100644
--- a/app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt
@@ -1,52 +1,29 @@
-package com.tangem.tap.domain.config
+package com.tangem.tap.domain.configurable.config
import android.content.Context
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
-import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
-import com.tangem.wallet.BuildConfig
+import com.tangem.tap.domain.configurable.Loader
import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
-interface ConfigLoader {
- fun loadConfig(onComplete: (ConfigModel) -> Unit)
-
- companion object {
- const val featuresName = "features_${BuildConfig.CONFIG_ENVIRONMENT}"
- const val configValuesName = "config_${BuildConfig.CONFIG_ENVIRONMENT}"
-
- fun init() {
- if (BuildConfig.CONFIG_ENVIRONMENT == "prod") {
- Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
- this.minimumFetchIntervalInSeconds = 1200
- })
- } else {
- Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
- this.minimumFetchIntervalInSeconds = 60
- })
- }
- }
- }
-}
-
-
-class LocalLoader(
+class FeaturesLocalLoader(
private val context: Context,
private val moshi: Moshi,
-) : ConfigLoader {
+) : Loader {
- override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
+ override fun load(onComplete: (ConfigModel) -> Unit) {
val config = try {
val featureAdapter: JsonAdapter = moshi.adapter(FeatureModel::class.java)
val valuesAdapter: JsonAdapter = moshi.adapter(ConfigValueModel::class.java)
- val jsonFeatures = readAssetAsString(ConfigLoader.featuresName)
- val jsonConfigValues = readAssetAsString(ConfigLoader.configValuesName)
+ val jsonFeatures = readAssetAsString(Loader.featuresName)
+ val jsonConfigValues = readAssetAsString(Loader.configValuesName)
ConfigModel(featureAdapter.fromJson(jsonFeatures), valuesAdapter.fromJson(jsonConfigValues))
} catch (ex: Exception) {
@@ -61,16 +38,16 @@ class LocalLoader(
}
}
-class RemoteLoader(
+class FeaturesRemoteLoader(
private val moshi: Moshi,
-) : ConfigLoader {
+) : Loader {
- override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
+ override fun load(onComplete: (ConfigModel) -> Unit) {
val emptyConfig = ConfigModel.empty()
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener {
if (it.isSuccessful) {
- val config = remoteConfig.getValue(ConfigLoader.featuresName)
+ val config = remoteConfig.getValue(Loader.featuresName)
val jsonConfig = config.asString()
if (jsonConfig.isEmpty()) {
onComplete(emptyConfig)
@@ -82,7 +59,7 @@ class RemoteLoader(
onComplete(emptyConfig)
}
}.addOnFailureListener {
- FirebaseAnalyticsHandler.logException("remote_config_error", it)
+ FirebaseAnalyticsHandler.logException("remote_config_error.features", it)
onComplete(emptyConfig)
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
similarity index 73%
rename from app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt
rename to app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
index c7acd653c7..58096022fd 100644
--- a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
@@ -1,6 +1,8 @@
-package com.tangem.tap.domain.config
+package com.tangem.tap.domain.configurable.config
+import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
+import com.tangem.tap.domain.configurable.Loader
/**
[REDACTED_AUTHOR]
@@ -9,15 +11,15 @@ data class Config(
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
- val isWalletPayIdEnabled: Boolean = true,
+ val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
val isSendingToPayIdEnabled: Boolean = true,
val isTopUpEnabled: Boolean = false,
val isCreatingTwinCardsAllowed: Boolean = false
)
class ConfigManager(
- private val localLoader: ConfigLoader,
- private val remoteLoader: ConfigLoader
+ private val localLoader: Loader,
+ private val remoteLoader: Loader
) {
var config: Config = Config()
@@ -26,19 +28,19 @@ class ConfigManager(
private var defaultConfig = Config()
fun load(onComplete: VoidCallback? = null) {
- localLoader.loadConfig { config ->
+ localLoader.load { config ->
setupFeature(config.features)
setupKey(config.configValues)
- }
- remoteLoader.loadConfig { config ->
- setupFeature(config.features)
onComplete?.invoke()
}
+ // Uncomment to enable remote config
+// remoteLoader.load { config ->
+// setupFeature(config.features)
+// }
}
fun turnOff(name: String) {
when (name) {
- isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = false)
isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = false)
isTopUpEnabled -> config = config.copy(isTopUpEnabled = false)
isCreatingTwinCardsAllowed -> config = config.copy(isCreatingTwinCardsAllowed = false)
@@ -47,7 +49,6 @@ class ConfigManager(
fun resetToDefault(name: String) {
when (name) {
- isWalletPayIdEnabled -> config = config.copy(isWalletPayIdEnabled = defaultConfig.isWalletPayIdEnabled)
isSendingToPayIdEnabled -> config = config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
isCreatingTwinCardsAllowed -> config =
@@ -59,13 +60,11 @@ class ConfigManager(
val model = featureModel ?: return
config = config.copy(
- isWalletPayIdEnabled = model.isWalletPayIdEnabled,
isTopUpEnabled = model.isTopUpEnabled,
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
)
defaultConfig = defaultConfig.copy(
- isWalletPayIdEnabled = model.isWalletPayIdEnabled,
isTopUpEnabled = model.isTopUpEnabled,
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
@@ -77,22 +76,28 @@ class ConfigManager(
config = config.copy(
coinMarketCapKey = values.coinMarketCapKey,
moonPayApiKey = values.moonPayApiKey,
- moonPayApiSecretKey = values.moonPayApiSecretKey
+ moonPayApiSecretKey = values.moonPayApiSecretKey,
+ blockchainSdkConfig = BlockchainSdkConfig(
+ blockchairApiKey = values.blockchairApiKey,
+ blockcypherTokens = values.blockcypherTokens,
+ infuraProjectId = values.infuraProjectId
+ )
)
defaultConfig = defaultConfig.copy(
coinMarketCapKey = values.coinMarketCapKey,
moonPayApiKey = values.moonPayApiKey,
- moonPayApiSecretKey = values.moonPayApiSecretKey
+ moonPayApiSecretKey = values.moonPayApiSecretKey,
+ blockchainSdkConfig = BlockchainSdkConfig(
+ blockchairApiKey = values.blockchairApiKey,
+ blockcypherTokens = values.blockcypherTokens,
+ infuraProjectId = values.infuraProjectId
+ )
)
}
companion object {
- const val isWalletPayIdEnabled = "isWalletPayIdEnabled"
const val isSendingToPayIdEnabled = "isSendingToPayIdEnabled"
const val isCreatingTwinCardsAllowed = "isCreatingTwinCardsAllowed"
const val isTopUpEnabled = "isTopUpEnabled"
- const val coinMarketCapKey = "coinMarketCapKey"
- const val moonPayApiKey = "moonPayApiKey"
- const val moonPayApiSecretKey = "moonPayApiSecretKey"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
similarity index 75%
rename from app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt
rename to app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
index 4724f8b2c3..69c5f0a8b0 100644
--- a/app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
@@ -1,4 +1,4 @@
-package com.tangem.tap.domain.config
+package com.tangem.tap.domain.configurable.config
/**
[REDACTED_AUTHOR]
@@ -15,6 +15,9 @@ class ConfigValueModel(
val coinMarketCapKey: String,
val moonPayApiKey: String,
val moonPayApiSecretKey: String,
+ val blockchairApiKey: String?,
+ val blockcypherTokens: Set?,
+ val infuraProjectId: String?,
)
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningLoader.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningLoader.kt
new file mode 100644
index 0000000000..c95fc39a24
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningLoader.kt
@@ -0,0 +1,52 @@
+package com.tangem.tap.domain.configurable.warningMessage
+
+import com.google.firebase.ktx.Firebase
+import com.google.firebase.remoteconfig.ktx.remoteConfig
+import com.squareup.moshi.JsonAdapter
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.Types
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.domain.configurable.Loader
+
+/**
+[REDACTED_AUTHOR]
+ */
+class RemoteWarningLoader(
+ private val moshi: Moshi,
+) : Loader> {
+
+ override fun load(onComplete: (List) -> Unit) {
+ val emptyConfig = listOf()
+ val remoteConfig = Firebase.remoteConfig
+ remoteConfig.fetchAndActivate().addOnCompleteListener {
+ if (!it.isSuccessful) {
+ onComplete(emptyConfig)
+ return@addOnCompleteListener
+ }
+
+ val config = remoteConfig.getValue(Loader.warnings)
+ val jsonConfig = config.asString()
+ if (jsonConfig.isEmpty()) {
+ onComplete(emptyConfig)
+ return@addOnCompleteListener
+ }
+
+ val adapterType = Types.newParameterizedType(List::class.java, WarningMessage::class.java)
+ val warningsAdapter: JsonAdapter> = moshi.adapter(adapterType)
+ try {
+ val warnings = warningsAdapter.fromJson(jsonConfig) ?: listOf()
+ onComplete(warnings)
+ } catch (ex: Exception) {
+ handleError(ex)
+ onComplete(emptyConfig)
+ }
+ }.addOnFailureListener {
+ handleError(it)
+ onComplete(emptyConfig)
+ }
+ }
+
+ private fun handleError(ex: Exception) {
+ FirebaseAnalyticsHandler.logException("remote_config_error.warnings", ex)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt
new file mode 100644
index 0000000000..ab94c0d563
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt
@@ -0,0 +1,59 @@
+package com.tangem.tap.domain.configurable.warningMessage
+
+import com.squareup.moshi.Json
+import com.tangem.blockchain.common.Blockchain
+
+/**
+[REDACTED_AUTHOR]
+ */
+data class WarningMessage(
+ val title: String,
+ val message: String,
+ val type: Type,
+ val priority: Priority,
+ val location: List,
+ private val blockchains: List?,
+ val titleResId: Int? = null,
+ val messageResId: Int? = null,
+ val origin: Origin = Origin.Remote,
+) {
+ val blockchainList: List? by lazy {
+ blockchains?.map { Blockchain.fromId(it.toUpperCase()) }
+ }
+
+
+ var isHidden = false
+
+ enum class Priority {
+ @Json(name = "critical")
+ Critical,
+
+ @Json(name = "warning")
+ Warning,
+
+ @Json(name = "info")
+ Info
+ }
+
+ enum class Type {
+ @Json(name = "permanent")
+ Permanent, // нельзя скрыть
+
+ @Json(name = "temporary")
+ Temporary, // можно скрыть (кнопка ОК)
+
+ AppRating
+ }
+
+ enum class Location {
+ @Json(name = "main")
+ MainScreen,
+
+ @Json(name = "send")
+ SendScreen
+ }
+
+ enum class Origin {
+ Local, Remote
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
new file mode 100644
index 0000000000..81739ee7b5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
@@ -0,0 +1,129 @@
+package com.tangem.tap.domain.configurable.warningMessage
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
+import com.tangem.tap.common.extensions.containsAny
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+class WarningMessagesManager(
+ private val warningLoader: RemoteWarningLoader,
+) {
+
+ private val warningsList: MutableList = mutableListOf()
+
+ fun load(onComplete: VoidCallback? = null) {
+ warningLoader.load { remoteList ->
+ warningsList.clear()
+ warningsList.addAll(remoteList)
+ sortByPriority()
+ onComplete?.invoke()
+ }
+ }
+
+ fun addWarning(warning: WarningMessage) {
+ if (findWarning(warning) == null) {
+ warningsList.add(warning)
+ sortByPriority()
+ }
+ }
+
+ fun getWarnings(location: WarningMessage.Location, forBlockchains: List = emptyList()): List {
+ return warningsList
+ .filter { !it.isHidden && it.location.contains(location) }
+ .filter {
+ val list = it.blockchainList
+ when {
+ list == null -> true
+ list.containsAny(forBlockchains) -> true
+ else -> false
+ }
+ }
+ }
+
+ fun hideWarning(warning: WarningMessage): Boolean {
+ val foundWarning = findWarning(warning)
+ return when {
+ foundWarning == null -> false
+ foundWarning.type == WarningMessage.Type.Temporary
+ || foundWarning.type == WarningMessage.Type.AppRating -> {
+ if (foundWarning.isHidden) {
+ false
+ } else {
+ foundWarning.isHidden = true
+ true
+ }
+ }
+ else -> false
+ }
+ }
+
+ fun removeWarnings(origin: WarningMessage.Origin) {
+ warningsList.removeIf { it.origin == origin }
+ sortByPriority()
+ }
+
+ private fun sortByPriority() {
+ warningsList.sortBy { it.priority.ordinal }
+ }
+
+ private fun findWarning(warning: WarningMessage): WarningMessage? {
+ return warningsList.firstOrNull { it == warning }
+ }
+
+ companion object {
+ fun devCardWarning(): WarningMessage = WarningMessage(
+ "",
+ "",
+ type = WarningMessage.Type.Permanent,
+ priority = WarningMessage.Priority.Critical,
+ listOf(WarningMessage.Location.MainScreen),
+ null,
+ R.string.alert_title,
+ R.string.alert_developer_card,
+ WarningMessage.Origin.Local
+ )
+
+ fun alreadySignedHashesWarning(): WarningMessage = WarningMessage(
+ "",
+ "",
+ type = WarningMessage.Type.Temporary,
+ priority = WarningMessage.Priority.Info,
+ listOf(WarningMessage.Location.MainScreen),
+ null,
+ R.string.alert_title,
+ R.string.alert_card_signed_transactions,
+ WarningMessage.Origin.Local
+ )
+
+ fun appRatingWarning(): WarningMessage = WarningMessage(
+ "",
+ "",
+ WarningMessage.Type.AppRating,
+ WarningMessage.Priority.Info,
+ listOf(WarningMessage.Location.MainScreen),
+ null,
+ R.string.warning_rate_app_title,
+ R.string.warning_rate_app_message,
+ WarningMessage.Origin.Local
+ )
+
+ fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
+ return warning.messageResId == R.string.alert_card_signed_transactions
+ }
+
+ fun onlineVerificationFailed(): WarningMessage = WarningMessage(
+ "",
+ "",
+ type = WarningMessage.Type.Permanent,
+ priority = WarningMessage.Priority.Critical,
+ listOf(WarningMessage.Location.MainScreen),
+ null,
+ R.string.warning_failed_to_verify_card_title,
+ R.string.warning_failed_to_verify_card_message,
+ WarningMessage.Origin.Local
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt
index c0e33bb929..3e24204388 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt
@@ -1,9 +1,7 @@
package com.tangem.tap.domain.tasks
-import com.tangem.CardSession
-import com.tangem.CardSessionRunnable
-import com.tangem.TangemError
-import com.tangem.TangemSdkError
+import com.tangem.*
+import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.commands.CommandResponse
@@ -18,13 +16,14 @@ import com.tangem.tap.domain.TapSdkError
import com.tangem.tap.domain.TapWorkarounds.isExcluded
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.domain.twins.isTwinCard
+import com.tangem.tap.store
import com.tangem.tasks.ScanTask
data class ScanNoteResponse(
val walletManager: WalletManager?,
val card: Card,
val verifyResponse: VerifyCardResponse? = null,
- val secondTwinPublicKey: String? = null
+ val secondTwinPublicKey: String? = null,
) : CommandResponse
class ScanNoteTask(val card: Card? = null) : CardSessionRunnable {
@@ -53,7 +52,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable {
callback(CompletionResult.Success(ScanNoteResponse(
- walletManager, card, verifyResult.data, publicKey
- )))
+ walletManager, card, verifyResult.data, publicKey)))
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(TangemSdkError.CardVerificationFailed()))
@@ -95,7 +93,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable> = moshi.adapter(
+ Types.newParameterizedType(List::class.java, TokenDao::class.java)
+ )
+ private val blockchainsAdapter: JsonAdapter> = moshi.adapter(
+ Types.newParameterizedType(List::class.java, Blockchain::class.java)
+ )
+
+ fun loadCardCurrencies(cardId: String): CardCurrencies? {
+ val blockchains = loadSavedBlockchains(cardId)
+ if (blockchains.isEmpty()) return null
+
+ return CardCurrencies(loadSavedTokens(cardId), blockchains)
+ }
+
+ fun saveCardCurrencies(cardId: String, currencies: CardCurrencies) {
+ saveTokens(cardId, currencies.tokens)
+ saveBlockchains(cardId, currencies.blockchains)
+ }
+
+ fun saveAddedToken(cardId: String, token: Token) {
+ val tokens = loadSavedTokens(cardId) + token
+ saveTokens(cardId, tokens)
+ }
+
+ fun saveAddedBlockchain(cardId: String, blockchain: Blockchain) {
+ val blockchains = loadSavedBlockchains(cardId) + blockchain
+ saveBlockchains(cardId, blockchains)
+ }
+
+ fun removeToken(cardId: String, token: Token) {
+ val tokens = loadSavedTokens(cardId).filterNot { it == token }
+ saveTokens(cardId, tokens)
+ }
+
+ fun removeBlockchain(cardId: String, blockchain: Blockchain) {
+ val blockchains = loadSavedBlockchains(cardId).filterNot { it == blockchain }
+ saveBlockchains(cardId, blockchains)
+ }
+
+ private fun loadSavedTokens(cardId: String): List {
+ return try {
+ val json = context.readFileText(getFileNameForTokens(cardId))
+ tokensAdapter.fromJson(json)!!.map { it.toToken() }
+ } catch (exception: Exception) {
+ emptyList()
+ }
+ }
+
+ private fun saveTokens(cardId: String, tokens: List) {
+ val json = tokensAdapter.toJson(tokens.map { TokenDao.fromToken(it) })
+ context.rewriteFile(json, getFileNameForTokens(cardId))
+ }
+
+ private fun loadSavedBlockchains(cardId: String): List {
+ return try {
+ val json = context.readFileText(getFileNameForBlockchains(cardId))
+ blockchainsAdapter.fromJson(json) ?: emptyList()
+ } catch (exception: Exception) {
+ emptyList()
+ }
+ }
+
+ private fun saveBlockchains(cardId: String, blockchains: List) {
+ val json = blockchainsAdapter.toJson(blockchains)
+ context.rewriteFile(json, getFileNameForBlockchains(cardId))
+ }
+
+ private fun Context.readFileText(fileName: String): String =
+ this.openFileInput(fileName).bufferedReader().readText()
+
+ private fun Context.rewriteFile(content: String, fileName: String) {
+ this.openFileOutput(fileName, Context.MODE_PRIVATE).use {
+ it.write(content.toByteArray(), 0, content.length)
+ }
+ }
+
+ fun getPopularTokens(): List {
+ val json = context.assets.readJsonFileToString(POPULAR_TOKENS_FILE_NAME)
+ return tokensAdapter.fromJson(json)!!.map { it.toToken() }
+ }
+
+ fun getBlockchains(): List {
+ return listOf(
+ Blockchain.Bitcoin, Blockchain.BitcoinCash, Blockchain.Binance, Blockchain.Litecoin,
+ Blockchain.XRP, Blockchain.Tezos,
+ Blockchain.Ethereum, Blockchain.RSK)
+ }
+
+ companion object {
+ private const val POPULAR_TOKENS_FILE_NAME = "erc20_tokens"
+ private const val FILE_NAME_PREFIX_TOKENS = "tokens"
+ private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
+ fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
+ fun getFileNameForBlockchains(cardId: String): String = "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
+ }
+}
+
+
+@JsonClass(generateAdapter = true)
+data class TokenDao(
+ val name: String,
+ val symbol: String,
+ val contractAddress: String,
+ val decimalCount: Int
+) {
+ fun toToken(): Token {
+ return Token(name = name, symbol = symbol, contractAddress = contractAddress, decimals = decimalCount)
+ }
+
+ companion object {
+ fun fromToken(token: Token): TokenDao {
+ return TokenDao(name = token.name, symbol = token.symbol, contractAddress = token.contractAddress, decimalCount = token.decimals)
+
+ }
+ }
+}
+
+@JsonClass(generateAdapter = true)
+data class CardCurrenciesDao(
+ val tokens: List,
+ val blockchains: List
+) {
+ fun toCardCurrencies(): CardCurrencies {
+ return CardCurrencies(tokens = tokens.map { it.toToken() }, blockchains = blockchains)
+ }
+
+ companion object {
+ fun fromCardCurrencies(cardCurrencies: CardCurrencies): CardCurrenciesDao {
+ return CardCurrenciesDao(tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }, blockchains = cardCurrencies.blockchains)
+ }
+ }
+}
+
+data class CardCurrencies(
+ val tokens: List,
+ val blockchains: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
index 65a2c7dff6..f37ef1c696 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
@@ -18,7 +18,7 @@ sealed class DetailsAction : Action {
data class PrepareScreen(
val card: Card,
val scanNoteResponse: ScanNoteResponse,
- val wallet: Wallet?,
+ val wallets: List,
val isCreatingTwinWalletAllowed: Boolean?,
val cardTou: CardTou,
val fiatCurrencyName: FiatCurrencyName,
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
index 95a0a3952c..b66aa958e9 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
@@ -104,7 +104,7 @@ class DetailsMiddleware {
is DetailsAction.AppCurrencyAction.SelectAppCurrency -> {
preferencesStorage.saveAppCurrency(action.fiatCurrencyName)
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrencyName))
- store.dispatch(WalletAction.LoadFiatRate)
+ store.dispatch(WalletAction.LoadFiatRate())
}
}
}
@@ -152,9 +152,7 @@ class DetailsMiddleware {
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)
}
}
-
}
-
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
index ebb0485c03..ee44e36c9d 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
@@ -66,8 +66,9 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta
} else {
null
}
+
return DetailsState(
- card = action.card, wallet = action.wallet,
+ card = action.card, wallets = action.wallets,
cardInfo = action.card.toCardInfo(),
appCurrencyState = AppCurrencyState(
action.fiatCurrencyName
@@ -82,8 +83,9 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS
return when (action) {
DetailsAction.EraseWallet.Check -> {
val notAllowedByCard = state.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true
- val notEmpty = state.wallet?.recentTransactions?.isNullOrEmpty() != true ||
- state.wallet.amounts.toSendableAmounts().isNotEmpty()
+ val notEmpty = state.wallets.any {
+ !it.recentTransactions.isNullOrEmpty() || it.amounts.toSendableAmounts().isNotEmpty()
+ }
val eraseWalletState = when {
notAllowedByCard -> EraseWalletState.NotAllowedByCard
notEmpty -> EraseWalletState.NotEmpty
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
index 7995acdcdb..5ef1033e2f 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
@@ -13,7 +13,7 @@ import java.util.*
data class DetailsState(
val card: Card? = null,
- val wallet: Wallet? = null,
+ val wallets: List = emptyList(),
val cardInfo: CardInfo? = null,
val appCurrencyState: AppCurrencyState = AppCurrencyState(),
val eraseWalletState: EraseWalletState? = null,
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
index 8578a60304..895f379b09 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
@@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
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.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.twins.getTwinCardIdForUser
import com.tangem.tap.domain.twins.isTwinCard
@@ -15,6 +16,7 @@ import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
+import com.tangem.tap.features.feedback.FeedbackEmail
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_details.*
@@ -78,7 +80,6 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber
+ emailFilterIntentResults.any {
+ originalResult.activityInfo.packageName == it.activityInfo.packageName
+ }
+ }
+ .map {
+ createEmailShareIntent(email, subject, message, fileLog).apply {
+ setPackage(it.activityInfo.packageName)
+ }
+ }
+ .toMutableList()
+ try {
+ val chooserIntent = Intent.createChooser(targetedIntents.removeAt(0), "Send mail...")
+ chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
+ chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ ContextCompat.startActivity(activity, chooserIntent, null)
+ } catch (ex: ActivityNotFoundException) {
+ Timber.e(ex)
+ }
+ }
+
+ private fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent {
+ val builder = ShareCompat.IntentBuilder.from(activity)
+ .setType("message/rfc822")
+ .setEmailTo(arrayOf(recipient))
+ .setSubject(subject)
+ .setText(text)
+ file?.let { builder.setStream(FileProvider.getUriForFile(activity, "${activity.packageName}.provider", it)) }
+ return builder.intent
+ }
+
+ private fun createLogFile(): File? {
+ return try {
+ val file = File(activity.filesDir, "logs.txt")
+ file.delete()
+ file.createNewFile()
+
+ val stringWriter = StringWriter()
+ logCollector.getLogs().forEach { stringWriter.append(it) }
+ val fileWriter = FileWriter(file)
+ fileWriter.write(stringWriter.toString())
+ fileWriter.close()
+ logCollector.clearLogs()
+ file
+ } catch (ex: Exception) {
+ Timber.e(ex, "Can't create a file for email attachment")
+ null
+ }
+ }
+}
+
+class TangemLogCollector : TangemSdkLogger {
+ private val dateFormatter = SimpleDateFormat("HH:mm:ss.SSS")
+ private val logs = mutableListOf()
+
+ override fun log(message: () -> String, level: Log.Level) {
+ val time = dateFormatter.format(Date())
+ logs.add("$time: ${message()}\n")
+ }
+
+ fun getLogs(): List = logs.toList()
+
+ fun clearLogs() {
+ logs.clear()
+ }
+}
+
+class AdditionalEmailInfo {
+ class EmailWalletInfo(
+ var blockchain: Blockchain = Blockchain.Unknown,
+ var address: String = "",
+ var explorerLink: String = "",
+ // var outputsCount: String = ""
+ // var transactionHex: String = ""
+ )
+
+ var appVersion: String = ""
+
+ // card
+ var cardId: String = ""
+ var cardFirmwareVersion: String = ""
+
+ // wallets
+ internal val walletsInfo = mutableListOf()
+ internal var onSendErrorWalletInfo: EmailWalletInfo? = null
+ var signedHashesCount: String = ""
+
+ // device
+ var phoneModel: String = Build.MODEL
+ var osVersion: String = Build.VERSION.SDK_INT.toString()
+
+ // send error
+ var destinationAddress: String = ""
+ var amount: String = ""
+ var fee: String = ""
+ var token: String = ""
+
+ fun setAppVersion(context: Context) {
+ try {
+ val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
+ appVersion = pInfo.versionName
+ } catch (e: PackageManager.NameNotFoundException) {
+ e.printStackTrace()
+ }
+ }
+
+ fun setCardInfo(card: Card) {
+ cardId = card.cardId
+ cardFirmwareVersion = card.firmwareVersion.version
+ signedHashesCount = card.walletSignedHashes?.toString() ?: "0"
+ }
+
+ fun setWalletsInfo(wallets: List) {
+ walletsInfo.clear()
+ wallets.forEach { walletsInfo.add(EmailWalletInfo(it.blockchain, getAddress(it), getExploreUri(it))) }
+ }
+
+ fun updateOnSendError(wallet: Wallet, amountToSend: Amount, feeAmount: Amount, destinationAddress: String) {
+ val amountState = store.state.sendState.amountState
+ onSendErrorWalletInfo = EmailWalletInfo(wallet.blockchain, getAddress(wallet), getExploreUri(wallet))
+
+ this.destinationAddress = destinationAddress
+ amount = amountToSend.value?.stripZeroPlainString() ?: "0"
+ fee = feeAmount.value?.stripZeroPlainString() ?: "0"
+ if (amountState.typeOfAmount is AmountType.Token) {
+ token = amountState.amountToExtract?.currencySymbol ?: ""
+ }
+ }
+
+ private fun getAddress(wallet: Wallet): String {
+ return if (wallet.addresses.size == 1) {
+ wallet.address
+ } else {
+ val addresses = wallet.addresses.joinToString(", ") {
+ "${it.type.javaClass.simpleName} - ${it.value}"
+ }
+ "Multiple address: $addresses"
+ }
+ }
+
+ private fun getExploreUri(wallet: Wallet): String {
+ return if (wallet.addresses.size == 1) {
+ wallet.getExploreUrl(wallet.address)
+ } else {
+ val links = wallet.addresses.joinToString(", ") {
+ "${it.type.javaClass.simpleName} - ${wallet.getExploreUrl(it.value)}"
+ }
+ "Multiple explorers links: $links"
+ }
+ }
+}
+
+interface EmailData {
+ val subject: String
+ val mainMessage: String
+ fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String
+
+ fun joinTogether(infoHolder: AdditionalEmailInfo): String {
+ return "$mainMessage\n\n\n\n\n" +
+ "Following information is optional. You can erase it if you don’t want to share it.\n" +
+ createOptionalMessage(infoHolder)
+ }
+}
+
+class RateCanBeBetterEmail : EmailData {
+ override val subject: String = "My suggestions"
+ override val mainMessage: String = "Tell us what functions you are missing, and we will try to help you."
+
+ override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
+ val walletInfo = infoHolder.walletsInfo[0]
+ return StringBuilder().apply {
+ appendKeyValue("Card ID", infoHolder.cardId)
+ appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
+ appendKeyValue("Phone model", infoHolder.phoneModel)
+ appendKeyValue("OS version", infoHolder.osVersion)
+ appendKeyValue("App version", infoHolder.appVersion)
+ }.toString()
+ }
+}
+
+class ScanFailsEmail : EmailData {
+ override val subject: String = "Can’t scan a card"
+ override val mainMessage: String = "Please tell us what card do you have?"
+ override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
+ return StringBuilder().apply {
+ appendKeyValue("Phone model", infoHolder.phoneModel)
+ appendKeyValue("OS version", infoHolder.osVersion)
+ appendKeyValue("App version", infoHolder.appVersion)
+ }.toString()
+ }
+}
+
+class SendTransactionFailedEmail(private val error: String) : EmailData {
+ override val subject: String = "Can’t send a transaction"
+ override val mainMessage: String = "Please tell us more about your issue. Every small detail can help."
+ override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
+ val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo()
+ return StringBuilder().apply {
+ appendKeyValue("Error", error)
+ appendKeyValue("Card ID", infoHolder.cardId)
+ appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
+ appendKeyValue("Token", infoHolder.token)
+ appendKeyValue("Source address", walletInfo.address)
+ appendKeyValue("Destination address", infoHolder.destinationAddress)
+ appendKeyValue("Amount", infoHolder.amount)
+ appendKeyValue("Fee", infoHolder.fee)
+ appendKeyValue("Phone model", infoHolder.phoneModel)
+ appendKeyValue("OS version", infoHolder.osVersion)
+ appendKeyValue("App version", infoHolder.appVersion)
+ appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
+// appendKeyValue("Transaction HEX", infoHolder.transactionHex)
+ }.toString()
+ }
+}
+
+class FeedbackEmail : EmailData {
+ override val subject: String = "Tangem Tap feedback"
+ override val mainMessage: String = "Hi Tangem,"
+ override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String {
+ val builder = StringBuilder()
+ builder.appendKeyValue("Card ID", infoHolder.cardId)
+ builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
+ builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
+
+ infoHolder.walletsInfo.forEach {
+ builder.appendKeyValue("Blockchain", it.blockchain.fullName)
+ builder.appendKeyValue("Wallet address", it.address)
+ builder.appendKeyValue("Explorer link", it.explorerLink)
+ }
+// appendKeyValue("Outputs count", infoHolder.outputsCount)
+ builder.appendKeyValue("Phone model", infoHolder.phoneModel)
+ builder.appendKeyValue("OS version", infoHolder.osVersion)
+ return builder.toString()
+ }
+}
+
+fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder {
+ return this.append("$key: $value\n")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
index b8b8aa52fc..c93543613e 100644
--- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
@@ -1,11 +1,15 @@
package com.tangem.tap.features.home
+import android.app.Dialog
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
+import com.tangem.tap.common.redux.global.StateDialog
import com.tangem.tap.features.home.redux.HomeAction
+import com.tangem.tap.features.home.redux.HomeDialog
import com.tangem.tap.features.home.redux.HomeState
+import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_home.*
@@ -13,6 +17,8 @@ import org.rekotlin.StoreSubscriber
class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber {
+ private var dialog: Dialog? = null
+
override fun onStart() {
super.onStart()
store.subscribe(this) { state ->
@@ -39,6 +45,19 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber {
+ if (dialog == null) dialog = ScanFailsDialog.create(requireContext()).apply { show() }
+ }
+ else -> {
+ dialog?.dismiss()
+ dialog = null
+ }
+ }
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
index 5a9edf9fe7..4a575343c7 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
@@ -9,4 +9,10 @@ sealed class HomeAction : Action {
object CheckIfFirstLaunch : HomeAction() {
data class Result(val firstLaunch: Boolean) : HomeAction()
}
+
+ object ShowDialog : HomeAction() {
+ object ScanFails : HomeAction()
+ }
+
+ object HideDialog : HomeAction()
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
index aaf52f3564..b084b208cb 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
@@ -32,6 +32,7 @@ class HomeMiddleware {
scope.launch {
val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler)
withContext(Dispatchers.Main) {
+ store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
when (result) {
is CompletionResult.Success -> {
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
index b88da93641..3c031ed7b2 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt
@@ -18,6 +18,12 @@ private fun internalReduce(action: Action, state: AppState): HomeState {
is HomeAction.CheckIfFirstLaunch.Result -> {
homeState = homeState.copy(firstLaunch = action.firstLaunch)
}
+ is HomeAction.ShowDialog.ScanFails -> {
+ homeState = homeState.copy(dialog = HomeDialog.ScanFailsDialog)
+ }
+ is HomeAction.HideDialog -> {
+ homeState = homeState.copy(dialog = null)
+ }
}
return homeState
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
index b167e66785..c9fce9b492 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
@@ -1,7 +1,13 @@
package com.tangem.tap.features.home.redux
+import com.tangem.tap.common.redux.global.StateDialog
import org.rekotlin.StateType
data class HomeState(
- val firstLaunch: Boolean = true
+ val firstLaunch: Boolean = true,
+ val dialog: StateDialog? = null
) : StateType
+
+sealed class HomeDialog: StateDialog {
+ object ScanFailsDialog: HomeDialog()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
index bcb227de3b..08c3b35a09 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
@@ -3,9 +3,12 @@ package com.tangem.tap.features.send.redux
import com.tangem.Message
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.WalletManager
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.ToastNotificationAction
+import com.tangem.tap.common.redux.global.StateDialog
import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.redux.states.SendButtonState
@@ -23,7 +26,10 @@ object ReleaseSendState : Action
data class PrepareSendScreen(
val coinAmount: Amount?,
+ val coinRate: BigDecimal?,
+ val walletManager: WalletManager?,
val tokenAmount: Amount? = null,
+ val tokenRate: BigDecimal? = null
) : SendScreenAction
// Address or PayId
@@ -137,13 +143,19 @@ sealed class SendAction : SendScreenAction {
data class SendError(override val error: TapError) : SendAction(), ErrorAction
- sealed class Dialog : SendAction() {
- data class ShowTezosWarningDialog(
+ sealed class Dialog : SendAction(), StateDialog {
+ data class TezosWarningDialog(
val reduceCallback: () -> Unit,
val sendAllCallback: () -> Unit,
val reduceAmount: BigDecimal,
) : Dialog()
+ data class SendTransactionFails(val errorMessage: String) : Dialog()
object Hide : Dialog()
}
+
+ sealed class Warnings : SendAction() {
+ object Update : SendAction()
+ data class Set(val warningList: List) : SendAction()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
index 2af762a25c..5a5f927b30 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
@@ -15,14 +15,18 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TapWorkarounds
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.minimalAmount
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
import com.tangem.tap.features.send.redux.states.SendButtonState
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
+import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
+import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Action
@@ -43,6 +47,7 @@ val sendMiddleware: Middleware = { dispatch, appState ->
is SendActionUi.SendAmountToRecipient ->
verifyAndSendTransaction(action, appState(), dispatch)
is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch)
+ is SendAction.Warnings.Update -> updateWarnings(dispatch)
}
nextDispatch(action)
}
@@ -50,11 +55,11 @@ val sendMiddleware: Middleware = { dispatch, appState ->
}
private fun verifyAndSendTransaction(
- action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit
+ action: SendActionUi.SendAmountToRecipient, appState: AppState?, dispatch: (Action) -> Unit,
) {
val sendState = appState?.sendState ?: return
- val walletManager = appState.globalState.scanNoteResponse?.walletManager ?: return
- val card = appState.globalState.scanNoteResponse.card
+ val walletManager = sendState.walletManager ?: return
+ val card = appState.globalState.scanNoteResponse?.card ?: return
val destinationAddress = sendState.addressPayIdState.destinationWalletAddress ?: return
val typedAmount = sendState.amountState.amountToExtract ?: return
val feeAmount = sendState.feeState.currentFee ?: return
@@ -66,7 +71,7 @@ private fun verifyAndSendTransaction(
when {
hadTezosError -> {
val reduceAmount = walletManager.wallet.blockchain.minimalAmount()
- dispatch(SendAction.Dialog.ShowTezosWarningDialog(reduceCallback = {
+ dispatch(SendAction.Dialog.TezosWarningDialog(reduceCallback = {
dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
dispatch(AmountActionUi.CheckAmountToSend)
}, sendAllCallback = {
@@ -92,7 +97,7 @@ private fun sendTransaction(
destinationAddress: String,
transactionExtras: TransactionExtrasState,
card: Card,
- dispatch: (Action) -> Unit
+ dispatch: (Action) -> Unit,
) {
dispatch(SendAction.ChangeSendButtonState(SendButtonState.PROGRESS))
var txData = walletManager.createTransaction(amountToSend, feeAmount, destinationAddress)
@@ -116,6 +121,15 @@ private fun sendTransaction(
dispatch(SendAction.SendSuccess)
dispatch(GlobalAction.UpdateWalletSignedHashes(result.data.walletSignedHashes))
dispatch(NavigationAction.PopBackTo())
+ scope.launch(Dispatchers.IO) {
+ withContext(Dispatchers.Main) {
+ dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain.currency))
+ }
+ delay(10000)
+ withContext(Dispatchers.Main) {
+ dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain.currency))
+ }
+ }
}
is Result.Failure -> {
when (result.error) {
@@ -133,22 +147,27 @@ private fun sendTransaction(
is Throwable -> {
val throwable = result.error as Throwable
val message = throwable.message
+ val infoHolder = store.state.globalState.feedbackManager?.infoHolder
when {
message == null -> {
dispatch(SendAction.SendError(TapError.UnknownError))
+ infoHolder?.updateOnSendError(walletManager.wallet, amountToSend, feeAmount, destinationAddress)
+ dispatch(SendAction.Dialog.SendTransactionFails("unknown error"))
}
message.contains("50002") -> {
// user was cancelled the operation by closing the Sdk bottom sheet
}
// make it easier latter by handling an appropriate enumError or, like on iOS,
// accept a string identifier of the error message
- message.contains("Target account is not created. To create account send 1+ XLM.")-> {
+ message.contains("Target account is not created. To create account send 1+ XLM.") -> {
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
else -> {
Timber.e(throwable)
FirebaseCrashlytics.getInstance().recordException(throwable)
dispatch(SendAction.SendError(TapError.CustomError(message)))
+ infoHolder?.updateOnSendError(walletManager.wallet, amountToSend, feeAmount, destinationAddress)
+ dispatch(SendAction.Dialog.SendTransactionFails(message))
}
}
}
@@ -207,3 +226,11 @@ private fun setIfSendingToPayIdEnabled(appState: AppState?, dispatch: (Action) -
dispatch(AddressPayIdActionUi.ChangePayIdState(isSendingToPayIdEnabled))
}
+private fun updateWarnings(dispatch: (Action) -> Unit) {
+ val warningsManager = store.state.globalState.warningManager ?: return
+ val blockchain = store.state.sendState.walletManager?.wallet?.blockchain ?: return
+
+ val warnings = warningsManager.getWarnings(WarningMessage.Location.SendScreen, listOf(blockchain))
+ dispatch(SendAction.Warnings.Set(warnings))
+}
+
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
index 16da618931..9db3d6e7ca 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
@@ -42,8 +42,10 @@ private class SendReducer : SendInternalReducer {
override fun handle(action: SendScreenAction, sendState: SendState): SendState {
val result = when (action) {
is SendAction.ChangeSendButtonState -> sendState.copy(sendButtonState = action.state)
- is SendAction.Dialog.ShowTezosWarningDialog -> sendState.copy(dialog = action)
+ is SendAction.Dialog.TezosWarningDialog -> sendState.copy(dialog = action)
+ is SendAction.Dialog.SendTransactionFails -> sendState.copy(dialog = action)
is SendAction.Dialog.Hide -> sendState.copy(dialog = null)
+ is SendAction.Warnings.Set -> sendState.copy(sendWarningsList = action.warningList)
else -> return sendState
}
@@ -58,16 +60,15 @@ private class EmptyReducer : SendInternalReducer {
private class PrepareSendScreenStatesReducer : SendInternalReducer {
override fun handle(action: SendScreenAction, sendState: SendState): SendState {
val prepareAction = action as PrepareSendScreen
- val walletManager = store.state.globalState.scanNoteResponse!!.walletManager!!
+ val walletManager = action.walletManager
+ ?: store.state.globalState.scanNoteResponse!!.walletManager!!
val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!!
val decimals = amountToExtract.decimals
- val coinConverter = createCurrencyConverter(walletManager.wallet.blockchain.currency, decimals)
- val tokenConverter = createCurrencyConverter(prepareAction.tokenAmount?.currencySymbol ?: "", decimals)
return sendState.copy(
walletManager = walletManager,
- coinConverter = coinConverter,
- tokenConverter = tokenConverter,
+ coinConverter = action.coinRate?.let { CurrencyConverter(it, decimals) },
+ tokenConverter = action.tokenRate?.let { CurrencyConverter(it, decimals) },
amountState = sendState.amountState.copy(
amountToExtract = amountToExtract,
typeOfAmount = amountToExtract.type,
@@ -78,11 +79,6 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer {
)
)
}
-
- private fun createCurrencyConverter(currency: String, decimals: Int): CurrencyConverter? {
- val rate = store.state.globalState.conversionRates.getRate(currency)
- return if (rate == null) null else CurrencyConverter(rate, decimals)
- }
}
internal fun updateLastState(sendState: SendState, lastChangedState: IdStateHolder): SendState {
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
index b85eb86bdc..1e4e8d5072 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
@@ -6,9 +6,10 @@ import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.common.entities.TapCurrency
+import com.tangem.tap.common.redux.global.StateDialog
import com.tangem.tap.common.text.DecimalDigitsInputFilter
import com.tangem.tap.domain.TapError
-import com.tangem.tap.features.send.redux.SendAction
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.store
import org.rekotlin.StateType
import java.math.BigDecimal
@@ -37,8 +38,9 @@ data class SendState(
val amountState: AmountState = AmountState(),
val feeState: FeeState = FeeState(),
val receiptState: ReceiptState = ReceiptState(),
+ val sendWarningsList: List = listOf(),
val sendButtonState: SendButtonState = SendButtonState.DISABLED,
- val dialog: SendAction.Dialog? = null
+ val dialog: StateDialog? = null
) : SendScreenState {
override val stateId: StateId = StateId.SEND_SCREEN
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
index 6dae105162..080114705d 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
@@ -9,9 +9,12 @@ import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.core.view.postDelayed
import androidx.core.widget.addTextChangedListener
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputEditText
import com.tangem.Message
import com.tangem.merchant.common.toggleWidget.ToggleWidget
+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
@@ -30,10 +33,14 @@ import com.tangem.tap.features.send.redux.FeeActionUi.*
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.ui.adapters.SpacesItemDecoration
+import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_send.*
+import kotlinx.android.synthetic.main.fragment_send.rv_warning_messages
+import kotlinx.android.synthetic.main.fragment_wallet.*
import kotlinx.android.synthetic.main.layout_send_address_payid.*
import kotlinx.android.synthetic.main.layout_send_amount.*
import kotlinx.android.synthetic.main.layout_send_fee.*
@@ -50,6 +57,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
lateinit var sendBtn: ToggleWidget
private lateinit var etAmountToSend: TextInputEditText
+ private lateinit var warningsAdapter: WarningMessagesAdapter
private fun initSendButtonStates() {
sendBtn = ToggleWidget(flSendButtonContainer, btnSend, progress, ProgressState.None())
@@ -70,6 +78,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
setupTransactionExtrasLayout()
setupAmountLayout()
setupFeeLayout()
+ setupWarningMessages()
btnSend.setOnClickListener {
store.dispatch(SendActionUi.SendAmountToRecipient(
@@ -232,6 +241,16 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
+ private fun setupWarningMessages() {
+ warningsAdapter = WarningMessagesAdapter()
+ val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
+ rv_warning_messages.layoutManager = layoutManager
+ rv_warning_messages.addItemDecoration(SpacesItemDecoration(rv_warning_messages.dpToPx(16f).toInt()))
+ rv_warning_messages.adapter = warningsAdapter
+
+ store.dispatch(SendAction.Warnings.Update)
+ }
+
override fun subscribeToStore() {
store.subscribe(sendSubscriber) { appState ->
appState.skipRepeats { oldState, newState ->
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
new file mode 100644
index 0000000000..cb65943200
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
@@ -0,0 +1,30 @@
+package com.tangem.tap.features.send.ui.dialogs
+
+import android.content.Context
+import androidx.appcompat.app.AlertDialog
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.features.feedback.SendTransactionFailedEmail
+import com.tangem.tap.features.send.redux.SendAction
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+class SendTransactionFailsDialog {
+
+ companion object {
+ fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails): AlertDialog {
+ return AlertDialog.Builder(context).apply {
+ setTitle(R.string.alert_failed_to_send_transaction_title)
+ setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, dialog.errorMessage))
+ setNeutralButton(R.string.alert_button_send_feedback) { _, _ ->
+ store.dispatch(GlobalAction.SendFeedback(SendTransactionFailedEmail(dialog.errorMessage)))
+ }
+ setPositiveButton(R.string.common_no) { _, _ -> }
+ setOnDismissListener { store.dispatch(WalletAction.HideDialog) }
+ }.create()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt
index 45b092ea8c..afb6276cb7 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/TezosWarningDialog.kt
@@ -9,10 +9,10 @@ import com.tangem.wallet.R
class TezosWarningDialog(context: Context) : AlertDialog(context) {
companion object {
- fun create(context: Context, showDialogData: SendAction.Dialog.ShowTezosWarningDialog): AlertDialog {
+ fun create(context: Context, showDialogData: SendAction.Dialog.TezosWarningDialog): AlertDialog {
val reduceAmount = showDialogData.reduceAmount.toPlainString()
return Builder(context).apply {
- setTitle(context.getString(R.string.common_warning))
+ setTitle(R.string.common_warning)
setMessage(context.getString(R.string.xtz_withdrawal_message_warning, reduceAmount))
setNegativeButton(R.string.xtz_withdrawal_message_ignore) { _, _ ->
showDialogData.sendAllCallback()
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
index 3476f1ae19..973ed08f8b 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
@@ -21,7 +21,9 @@ import com.tangem.tap.features.send.redux.reducers.ReceiptReducer
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.send.ui.FeeUiHelper
import com.tangem.tap.features.send.ui.SendFragment
+import com.tangem.tap.features.send.ui.dialogs.SendTransactionFailsDialog
import com.tangem.tap.features.send.ui.dialogs.TezosWarningDialog
+import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.btn_expand_collapse.*
@@ -96,12 +98,18 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
val sendFragment = (fg as? SendFragment) ?: return
when (state.dialog) {
- is SendAction.Dialog.ShowTezosWarningDialog -> {
+ is SendAction.Dialog.TezosWarningDialog -> {
if (dialog == null) {
dialog = TezosWarningDialog.create(fg.requireContext(), state.dialog)
dialog?.show()
}
}
+ is SendAction.Dialog.SendTransactionFails -> {
+ if (dialog == null) {
+ dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)
+ dialog?.show()
+ }
+ }
else -> {
dialog?.dismiss()
dialog = null
@@ -122,6 +130,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
sendFragment.sendBtn.setState(ProgressState.Progress(), true)
}
}
+
+ val rv = fg.rv_warning_messages
+ val adapter = rv.adapter as? WarningMessagesAdapter ?: return
+
+ adapter.submitList(state.sendWarningsList)
+ rv.show(state.sendWarningsList.isNotEmpty())
}
private fun handleAddressPayIdState(fg: BaseStoreFragment, state: AddressPayIdState) {
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt
new file mode 100644
index 0000000000..e0c9abe119
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt
@@ -0,0 +1,21 @@
+package com.tangem.tap.features.tokens.redux
+
+import com.tangem.blockchain.common.Token
+import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
+import com.tangem.tap.features.wallet.redux.WalletData
+import org.rekotlin.Action
+
+sealed class TokensAction : Action {
+
+ object LoadCurrencies : TokensAction() {
+ data class Success(val currencies: List) : TokensAction()
+ }
+
+ object LoadCardTokens : TokensAction() {
+ data class Success(val tokens: List) : TokensAction()
+ }
+
+ data class SetAddedCurrencies(val wallets: List) : TokensAction()
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
new file mode 100644
index 0000000000..7923d17bd4
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
@@ -0,0 +1,28 @@
+package com.tangem.tap.features.tokens.redux
+
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.currenciesRepository
+import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
+import com.tangem.tap.store
+import org.rekotlin.Middleware
+
+class TokensMiddleware {
+
+ val tokensMiddleware: Middleware = { dispatch, state ->
+ { next ->
+ { action ->
+ when (action) {
+ is TokensAction.LoadCurrencies -> {
+ val tokens = currenciesRepository.getPopularTokens()
+ val blockchains = currenciesRepository.getBlockchains()
+ val currencies = CurrencyListItem.createListOfCurrencies(
+ blockchains, tokens
+ )
+ store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
+ }
+ }
+ next(action)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt
new file mode 100644
index 0000000000..e0c5600ad1
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt
@@ -0,0 +1,30 @@
+package com.tangem.tap.features.tokens.redux
+
+import com.tangem.tap.common.redux.AppState
+import org.rekotlin.Action
+
+class TokensReducer {
+ companion object {
+ fun reduce(action: Action, state: AppState): TokensState = internalReduce(action, state)
+ }
+}
+
+private fun internalReduce(action: Action, state: AppState): TokensState {
+ if (action !is TokensAction) return state.tokensState
+
+ val tokensState = state.tokensState
+ return when (action) {
+ is TokensAction.LoadCurrencies.Success -> {
+ tokensState.copy(currencies = action.currencies)
+ }
+ is TokensAction.SetAddedCurrencies -> {
+ tokensState.copy(addedCurrencies = action.wallets.mapNotNull { it.currencyData.currencySymbol })
+ }
+ is TokensAction.LoadCardTokens.Success -> {
+ tokensState.copy(addedTokens = LinkedHashSet(
+ action.tokens.map { TokenWithAmount(it, null) }
+ ))
+ }
+ else -> tokensState
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt
new file mode 100644
index 0000000000..5169e63b96
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt
@@ -0,0 +1,18 @@
+package com.tangem.tap.features.tokens.redux
+
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.Token
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.features.tokens.ui.adapters.CurrencyListItem
+import org.rekotlin.StateType
+
+data class TokensState(
+ val addedTokens: LinkedHashSet = LinkedHashSet(),
+ val addedCurrencies: List = emptyList(),
+ val currencies: List = emptyList(),
+) : StateType
+
+data class TokenWithAmount(
+ val token: Token,
+ val amount: Amount? = null
+)
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt
new file mode 100644
index 0000000000..e3d8eb80fb
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt
@@ -0,0 +1,119 @@
+package com.tangem.tap.features.tokens.ui
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import androidx.activity.OnBackPressedCallback
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.SearchView
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.transition.TransitionInflater
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.tokens.redux.TokensState
+import com.tangem.tap.features.tokens.ui.adapters.CurrenciesAdapter
+import com.tangem.tap.mainScope
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.fragment_add_tokens.*
+import kotlinx.android.synthetic.main.fragment_details_twin_cards.toolbar
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.*
+import org.rekotlin.StoreSubscriber
+
+
+class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
+ StoreSubscriber {
+
+ private lateinit var viewAdapter: CurrenciesAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ store.dispatch(NavigationAction.PopBackTo())
+ }
+ })
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(R.transition.slide_right)
+ exitTransition = inflater.inflateTransition(R.transition.fade)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.tokensState == newState.tokensState
+ }.select { it.tokensState }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ (activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
+ toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
+ setupPopularTokensRecyclerView()
+ }
+
+
+ private fun setupPopularTokensRecyclerView() {
+ viewAdapter = CurrenciesAdapter()
+ rv_popular_tokens.layoutManager = LinearLayoutManager(context)
+ rv_popular_tokens.adapter = viewAdapter
+ }
+
+
+ override fun newState(state: TokensState) {
+ if (activity == null) return
+ viewAdapter.addedCurrencies = state.addedCurrencies
+ viewAdapter.submitUnfilteredList(state.currencies)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.menu_search -> {
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.popular_tokens, menu)
+
+ val menuItem = menu.findItem(R.id.menu_search)
+ val searchView: SearchView = menuItem.actionView as SearchView
+ searchView.queryHint = "Type here to search"
+ searchView.maxWidth = android.R.attr.width
+ searchView.inputtedTextAsFlow()
+ .debounce(400)
+ .distinctUntilChanged()
+ .onEach {
+ viewAdapter.filter(searchView.query)
+ }
+ .launchIn(mainScope)
+ return super.onCreateOptionsMenu(menu, inflater);
+ }
+}
+
+fun SearchView.inputtedTextAsFlow(): Flow = callbackFlow {
+ val watcher = setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String?): Boolean {
+ return false
+ }
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ offer(newText ?: "")
+ return false
+ }
+ })
+ awaitClose { (watcher) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt
new file mode 100644
index 0000000000..9fe1ba05bd
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/adapters/CurrenciesAdapter.kt
@@ -0,0 +1,165 @@
+package com.tangem.tap.features.tokens.ui.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.StringRes
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.item_currency_subtitle.view.*
+import kotlinx.android.synthetic.main.item_popular_token.view.*
+import java.util.*
+
+class CurrenciesAdapter : ListAdapter(DiffUtilCallback) {
+
+ var addedCurrencies: List = emptyList()
+
+ private var unfilteredList = listOf()
+
+ fun submitUnfilteredList(list: List) {
+ unfilteredList = list
+ submitList(list)
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return when (currentList[position]) {
+ is CurrencyListItem.TitleListItem -> 0
+ is CurrencyListItem.BlockchainListItem, is CurrencyListItem.TokenListItem -> 1
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return when (viewType) {
+ 0 -> TitleViewHolder(LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_currency_subtitle, parent, false))
+ 1 -> CurrenciesViewHolder(LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_popular_token, parent, false))
+ else -> CurrenciesViewHolder(LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_popular_token, parent, false))
+ }
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ val listItem = currentList[position]
+ if (holder is TitleViewHolder && listItem is CurrencyListItem.TitleListItem) {
+ holder.bind(listItem)
+ } else if (holder is CurrenciesViewHolder) {
+ holder.bind(listItem, addedCurrencies)
+ }
+ }
+
+ object DiffUtilCallback : DiffUtil.ItemCallback() {
+ override fun areContentsTheSame(
+ oldItem: CurrencyListItem, newItem: CurrencyListItem
+ ) = oldItem == newItem
+
+ override fun areItemsTheSame(
+ oldItem: CurrencyListItem, newItem: CurrencyListItem
+ ) = oldItem == newItem
+ }
+
+ fun filter(query: CharSequence?) {
+ val list = mutableListOf()
+
+ if (!query.isNullOrEmpty()) {
+ val queryNormalized = query.toString().toLowerCase(Locale.US)
+ list.addAll(
+ unfilteredList.filter { element ->
+ when (element) {
+ is CurrencyListItem.BlockchainListItem -> {
+ element.blockchain.currency.toLowerCase(Locale.US).contains(queryNormalized)
+ || element.blockchain.fullName.toLowerCase(Locale.US).contains(queryNormalized)
+
+ }
+ is CurrencyListItem.TokenListItem -> {
+ element.token.name.toLowerCase(Locale.US).contains(queryNormalized)
+ || element.token.symbol.toLowerCase(Locale.US).contains(queryNormalized)
+ }
+ else -> false
+ }
+ })
+ } else {
+ list.addAll(unfilteredList)
+ }
+ submitList(list)
+ }
+
+ class CurrenciesViewHolder(val view: View) :
+ RecyclerView.ViewHolder(view) {
+ fun bind(currency: CurrencyListItem, addedCurrencies: List) {
+ when (currency) {
+ is CurrencyListItem.BlockchainListItem -> {
+ val blockchain = currency.blockchain
+ view.tv_currency_name.text = blockchain.fullName
+ view.tv_currency_symbol.text = blockchain.currency
+ val isAdded = addedCurrencies.any { it == blockchain.currency }
+ view.btn_add_token.show(!isAdded)
+ view.btn_token_added.show(isAdded)
+
+ view.iv_currency.setImageResource(currency.blockchain.getIconRes())
+ view.iv_currency.colorFilter = null
+ view.tv_token_letter.text = null
+
+ view.btn_add_token.setOnClickListener {
+ store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain))
+ view.btn_add_token.hide()
+ view.btn_token_added.show()
+ }
+ }
+ is CurrencyListItem.TokenListItem -> {
+ val token = currency.token
+ view.tv_currency_name.text = token.name
+ view.tv_currency_symbol.text = token.symbol
+
+ val isAdded = addedCurrencies.any { it == token.symbol }
+ view.btn_add_token.show(!isAdded)
+ view.btn_token_added.show(isAdded)
+
+ view.iv_currency.setImageResource(R.drawable.shape_circle)
+ view.iv_currency.setColorFilter(token.getColor())
+ view.tv_token_letter.text = token.symbol.take(1)
+
+ view.btn_add_token.setOnClickListener {
+ store.dispatch(WalletAction.MultiWallet.AddToken(token))
+ view.btn_add_token.hide()
+ view.btn_token_added.show()
+ }
+ }
+ }
+ }
+ }
+
+ class TitleViewHolder(val view: View) :
+ RecyclerView.ViewHolder(view) {
+ fun bind(title: CurrencyListItem.TitleListItem) {
+ view.tv_subtitle.text = view.getString(title.titleResId).toUpperCase(Locale.US)
+ }
+ }
+}
+
+
+sealed class CurrencyListItem {
+ data class TokenListItem(val token: Token) : CurrencyListItem()
+ data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
+ data class TitleListItem(@StringRes val titleResId: Int) : CurrencyListItem()
+
+ companion object {
+ fun createListOfCurrencies(blockchains: List, tokens: List): List {
+ val blockchainsTitle = R.string.add_tokens_subtitle_blockchains
+ val tokensTitle = R.string.add_tokens_subtitle_tokens
+ return listOf(TitleListItem(blockchainsTitle)) +
+ blockchains.map { BlockchainListItem(it) } +
+ listOf(TitleListItem(tokensTitle)) +
+ tokens.map { TokenListItem(it) }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt
index 21cdc9d649..0ade915979 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt
@@ -1,5 +1,6 @@
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.tap.common.extensions.toFormattedString
@@ -47,4 +48,13 @@ fun List.toPendingTransactions(walletAddress: String): List.removeUnknownTransactions(): List {
return this.filter { it.type != PendingTransactionType.Unknown }
+}
+
+fun TransactionData.toPendingTransactionForToken(token: Token, walletAddress: String): PendingTransaction? {
+ if (this.amount.currencySymbol != token.symbol) return null
+ return this.toPendingTransaction(walletAddress)
+}
+
+fun List.toPendingTransactionsForToken(token: Token, walletAddress: String): List {
+ return this.mapNotNull { it.toPendingTransactionForToken(token, walletAddress) }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
index 0656a9583f..5b3877338d 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
@@ -1,14 +1,15 @@
package com.tangem.tap.features.wallet.redux
import android.content.Context
-import com.tangem.blockchain.common.Amount
-import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.AddressType
import com.tangem.commands.common.card.Card
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.domain.tokens.CardCurrencies
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.wallet.R
import org.rekotlin.Action
@@ -22,71 +23,100 @@ sealed class WalletAction : Action {
data class Failure(val error: TapError) : WalletAction()
}
- data class LoadWallet(
- val wallet: Wallet, val artworkId: String?, val allowTopUp: Boolean
- ) : WalletAction() {
+
+ data class LoadWallet(val allowTopUp: Boolean? = null, val currency: CryptoCurrencyName? = null) : WalletAction() {
data class Success(val wallet: Wallet) : WalletAction()
- data class NoAccount(val amountToCreateAccount: String) : WalletAction()
- data class Failure(val errorMessage: String? = null) : WalletAction()
+ data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : WalletAction()
+ data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction()
}
- object CheckIfWarningNeeded : WalletAction()
- object CheckHashesCountOnline : WalletAction()
- object NeedToCheckHashesCountOnline : WalletAction()
- object ConfirmHashesCount : WalletAction()
- data class ShowWarning(val warningType: WarningType) : WalletAction()
- object SaveCardId : WalletAction()
+ data class SetArtworkId(val artworkId: String?) : WalletAction()
- object UpdateWallet : WalletAction() {
+
+ sealed class MultiWallet : WalletAction() {
+ data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet()
+ data class AddWalletManagers(val walletManagers: List) : MultiWallet() {
+ constructor(walletManager: WalletManager) : this(listOf(walletManager))
+ }
+ data class AddBlockchain(val blockchain: Blockchain) : MultiWallet()
+ data class AddBlockchains(val blockchains: List) : MultiWallet()
+ data class AddTokens(val tokens: List) : MultiWallet()
+ data class AddToken(val token: Token) : MultiWallet()
+ data class SaveCurrencies(val cardCurrencies: CardCurrencies) : MultiWallet()
+ object FindTokensInUse : MultiWallet()
+ data class FindBlockchainsInUse(val card: Card, val factory: WalletManagerFactory) : MultiWallet()
+ data class TokenLoaded(val amount: Amount) : MultiWallet()
+ data class SelectWallet(val walletData: WalletData?) : MultiWallet()
+ data class RemoveWallet(val walletData: WalletData) : MultiWallet()
+ data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet()
+ data class SetPrimaryToken(val token: Token) : MultiWallet()
+ }
+
+ sealed class Warnings : WalletAction() {
+ object CheckHashesCount : Warnings() {
+ object CheckHashesCountOnline : Warnings()
+ object NeedToCheckHashesCountOnline : Warnings()
+ object ConfirmHashesCount : Warnings()
+ object SaveCardId : Warnings()
+ }
+
+ object CheckIfNeeded : Warnings()
+ object Update : Warnings()
+ data class Set(val warningList: List) : Warnings()
+
+ object AppRating : Warnings() {
+ object SetNeverToShow : Warnings()
+ object RemindLater : Warnings()
+ }
+ }
+
+ data class UpdateWallet(val currency: CryptoCurrencyName? = null) : WalletAction() {
object ScheduleUpdatingWallet : WalletAction()
data class Success(val wallet: Wallet) : WalletAction()
data class Failure(val errorMessage: String? = null) : WalletAction()
}
- object LoadFiatRate : WalletAction() {
- data class Success(val fiatRates: Pair) : WalletAction()
+ data class LoadFiatRate(
+ val wallet: Wallet? = null, val currency: CryptoCurrencyName? = null
+ ) : WalletAction() {
+ data class Success(val fiatRate: Pair) : WalletAction()
object Failure : WalletAction()
}
- object LoadPayId : WalletAction() {
- data class Success(val payId: String) : WalletAction()
- object NotCreated : WalletAction()
- object Failure : WalletAction()
- }
-
- object DisablePayId : WalletAction()
-
data class LoadArtwork(val card: Card, val artworkId: String?) : WalletAction() {
data class Success(val artwork: Artwork) : WalletAction()
object Failure : WalletAction()
}
object Scan : WalletAction()
+
data class Send(val amount: Amount? = null) : WalletAction() {
data class ChooseCurrency(val amounts: List?) : WalletAction()
object Cancel : WalletAction()
}
- object CreatePayId : WalletAction() {
- data class CompleteCreatingPayId(val payId: String) : WalletAction()
- data class Success(val payId: String) : WalletAction()
- object EmptyField : WalletAction(), ErrorAction {
- override val error = TapError.PayIdEmptyField
- }
- class Failure(override val error: TapError) : WalletAction(), ErrorAction
- object Cancel : WalletAction()
+ object EmptyField : WalletAction(), ErrorAction {
+ override val error = TapError.PayIdEmptyField
}
- data class CopyAddress(val context: Context) : WalletAction() {
+ data class CopyAddress(val address: String, val context: Context) : WalletAction() {
object Success : WalletAction(), NotificationAction {
override val messageResource = R.string.wallet_notification_address_copied
}
}
- object ShowQrCode : WalletAction()
+ data class ShareAddress(val address: String, val context: Context) : WalletAction()
+
+ object ShowDialog : WalletAction() {
+ object QrCode : WalletAction()
+ object ScanFails : WalletAction()
+ }
+
object HideDialog : WalletAction()
- data class ExploreAddress(val context: Context) : WalletAction()
+
+ data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction()
+
object CreateWallet : WalletAction()
object EmptyWallet : WalletAction()
@@ -101,7 +131,7 @@ sealed class WalletAction : Action {
object SetOnboardingShown : TwinsAction()
data class SetTwinCard(
val secondCardId: String, val number: TwinCardNumber,
- val isCreatingTwinCardsAllowed: Boolean
+ val isCreatingTwinCardsAllowed: Boolean,
) : TwinsAction()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt
deleted file mode 100644
index 2f2c041924..0000000000
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt
+++ /dev/null
@@ -1,304 +0,0 @@
-package com.tangem.tap.features.wallet.redux
-
-import android.content.Intent
-import android.net.Uri
-import androidx.browser.customtabs.CustomTabsIntent
-import androidx.core.content.ContextCompat
-import com.tangem.blockchain.common.*
-import com.tangem.blockchain.extensions.SimpleResult
-import com.tangem.commands.common.card.Card
-import com.tangem.commands.common.card.CardType
-import com.tangem.commands.common.network.Result
-import com.tangem.common.CompletionResult
-import com.tangem.common.extensions.getType
-import com.tangem.common.extensions.toHexString
-import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
-import com.tangem.tap.common.extensions.copyToClipboard
-import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.common.redux.navigation.AppScreen
-import com.tangem.tap.common.redux.navigation.NavigationAction
-import com.tangem.tap.domain.PayIdManager
-import com.tangem.tap.domain.TapError
-import com.tangem.tap.domain.TopUpHelper
-import com.tangem.tap.domain.extensions.toSendableAmounts
-import com.tangem.tap.domain.twins.TwinsHelper
-import com.tangem.tap.domain.twins.isTwinCard
-import com.tangem.tap.features.details.redux.DetailsAction
-import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
-import com.tangem.tap.features.send.redux.PrepareSendScreen
-import com.tangem.tap.features.wallet.models.toPendingTransactions
-import com.tangem.tap.network.NetworkConnectivity
-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
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import org.rekotlin.Action
-import org.rekotlin.Middleware
-
-class WalletMiddleware {
- private val topUpMiddleware = TopUpMiddleware()
-
- val walletMiddleware: Middleware = { dispatch, state ->
- { next ->
- { action ->
- when (action) {
- is WalletAction.TopUpAction -> topUpMiddleware.handle(action)
- is WalletAction.LoadWallet -> {
- scope.launch {
- store.state.globalState.tapWalletManager.loadWalletData()
- }
- }
- is WalletAction.LoadPayId -> {
- scope.launch {
- store.state.globalState.tapWalletManager.loadPayId()
- }
- }
- is WalletAction.LoadFiatRate -> {
- scope.launch {
- store.state.globalState.tapWalletManager.loadFiatRate(store.state.globalState.appCurrency)
- }
- }
- is WalletAction.CreateWallet -> {
- if (store.state.walletState.twinCardsState != null) {
- store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning(
- store.state.globalState.scanNoteResponse?.card?.cardId?.let {
- TwinsHelper.getTwinCardNumber(it)
- },
- CreateTwinWallet.CreateWallet
- ))
- } else {
- scope.launch {
- val result = tangemSdkManager.createWallet(
- store.state.globalState.scanNoteResponse?.card?.cardId
- )
- when (result) {
- is CompletionResult.Success -> {
- store.state.globalState.tapWalletManager
- .onCardScanned(result.data)
- }
-
- }
- }
- }
- }
- is WalletAction.UpdateWallet -> {
- if (store.state.walletState.state == ProgressState.Done) {
- scope.launch { store.state.globalState.tapWalletManager.updateWallet() }
- }
- }
- is WalletAction.UpdateWallet.Success -> setupWalletUpdate(action.wallet)
- is WalletAction.LoadWallet.Success -> {
- store.dispatch(WalletAction.CheckHashesCountOnline)
- if (!store.state.walletState.updatingWallet) setupWalletUpdate(action.wallet)
- }
- is WalletAction.CreatePayId.CompleteCreatingPayId -> {
- scope.launch {
- val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
- val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet
- val publicKey = store.state.globalState.scanNoteResponse?.card?.cardPublicKey
- if (cardId != null && wallet != null && publicKey != null) {
- val result = PayIdManager().setPayId(
- cardId, publicKey.toHexString(),
- action.payId, wallet.address, wallet.blockchain
- )
- withContext(Dispatchers.Main) {
- when (result) {
- is Result.Success ->
- store.dispatch(WalletAction.CreatePayId.Success(action.payId))
- is Result.Failure -> {
- val error = result.error as? TapError
- ?: TapError.PayIdCreatingError
- store.dispatch(WalletAction.CreatePayId.Failure(error))
- }
- }
- }
- }
- }
- }
- is WalletAction.Scan -> {
- scope.launch {
- val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler)
- when (result) {
- is CompletionResult.Success -> {
- tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
- store.state.globalState.tapWalletManager
- .onCardScanned(result.data, true)
- if (store.state.walletState.twinCardsState != null) {
- val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
- if (showOnboarding) {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
- }
- }
- }
- }
- }
- }
- is WalletAction.LoadData -> {
- scope.launch {
- store.state.globalState.scanNoteResponse?.let {
- store.state.globalState.tapWalletManager.loadData(it)
- }
- }
- }
- is NetworkStateChanged -> {
- store.state.globalState.scanNoteResponse?.let { scanNoteResponse ->
- store.dispatch(WalletAction.CheckHashesCountOnline)
- scope.launch {
- store.state.globalState.tapWalletManager.onCardScanned(scanNoteResponse)
- }
- }
- }
- is WalletAction.CopyAddress -> {
- store.state.walletState.walletAddresses?.selectedAddress?.address?.let {
- action.context.copyToClipboard(it)
- store.dispatch(WalletAction.CopyAddress.Success)
- }
- }
- is WalletAction.ExploreAddress -> {
- val uri = Uri.parse(store.state.walletState.walletAddresses?.selectedAddress?.exploreUrl)
- val intent = Intent(Intent.ACTION_VIEW, uri)
- ContextCompat.startActivity(action.context, intent, null)
- }
- is WalletAction.Send -> {
- val newAction = prepareSendAction(action.amount)
- store.dispatch(newAction)
- if (newAction is PrepareSendScreen) {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
- }
- }
- is WalletAction.CheckIfWarningNeeded -> {
- val card = store.state.globalState.scanNoteResponse?.card
- val validator = store.state.globalState.scanNoteResponse?.walletManager
- as? SignatureCountValidator
- if (card != null && !preferencesStorage.wasCardScannedBefore(card.cardId)) {
- val result = checkIfWarningNeeded(card, validator)
- if (result != null) store.dispatch(WalletAction.ShowWarning(result))
- }
- }
- is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline()
- is WalletAction.SaveCardId -> {
- val cardId = store.state.globalState.scanNoteResponse?.card?.cardId
- cardId?.let { preferencesStorage.saveScannedCardId(it) }
- }
- is WalletAction.TwinsAction.SetTwinCard -> {
- val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
- if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding)
- }
- is WalletAction.TwinsAction.SetOnboardingShown -> {
- preferencesStorage.saveTwinsOnboardingShown()
- }
- }
- next(action)
- }
- }
- }
-
- private fun setupWalletUpdate(wallet: Wallet) {
- if (!wallet.recentTransactions.toPendingTransactions(wallet.address).isNullOrEmpty()) {
- store.dispatch(WalletAction.UpdateWallet.ScheduleUpdatingWallet)
- scope.launch(Dispatchers.IO) {
- delay(10000)
- withContext(Dispatchers.Main) {
- store.dispatch(WalletAction.UpdateWallet)
- }
- }
- }
- }
-
-
- private fun prepareSendAction(amount: Amount?): Action {
- return if (amount != null) {
- if (amount.type is AmountType.Token) {
- PrepareSendScreen(store.state.walletState.wallet?.amounts?.get(AmountType.Coin), amount)
- } else {
- PrepareSendScreen(amount)
- }
- } else {
- val amounts = store.state.walletState.wallet?.amounts?.toSendableAmounts()
- if (amounts?.size ?: 0 > 1) {
- WalletAction.Send.ChooseCurrency(amounts)
- } else {
- val amountToSend = amounts?.first()
- PrepareSendScreen(amountToSend)
- }
- }
- }
-
- private fun checkIfWarningNeeded(
- card: Card, signatureCountValidator: SignatureCountValidator? = null
- ): WarningType? {
-
- if (card.getType() != CardType.Release) {
- return WarningType.DevCard
- }
- if (card.isTwinCard()) return null
-
- return if (signatureCountValidator == null) {
- if (card.walletSignedHashes ?: 0 > 0) {
- WarningType.CardSignedHashesBefore
- } else {
- store.dispatch(WalletAction.SaveCardId)
- null
- }
- } else {
- store.dispatch(WalletAction.NeedToCheckHashesCountOnline)
- null
- }
- }
-
- private fun checkHashesCountOnline() {
- if (store.state.walletState.hashesCountVerified != false) return
- if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return
-
- val card = store.state.globalState.scanNoteResponse?.card
- if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return
-
- if (card.isTwinCard()) return
-
- val validator = store.state.globalState.scanNoteResponse?.walletManager
- as? SignatureCountValidator
- scope.launch {
- val result = validator?.validateSignatureCount(card.walletSignedHashes ?: 0)
- withContext(Dispatchers.Main) {
- when (result) {
- SimpleResult.Success -> {
- store.dispatch(WalletAction.ConfirmHashesCount)
- store.dispatch(WalletAction.SaveCardId)
- }
- is SimpleResult.Failure ->
- if (result.error is BlockchainSdkError.SignatureCountNotMatched) {
- store.dispatch(WalletAction.ShowWarning(WarningType.CardSignedHashesBefore))
- } else if (card.walletSignedHashes ?: 0 > 0) {
- store.dispatch(WalletAction.ShowWarning(WarningType.CardSignedHashesBefore))
- }
- }
- }
- }
- }
-}
-
-private class TopUpMiddleware {
- fun handle(action: WalletAction.TopUpAction) {
- when (action) {
- is WalletAction.TopUpAction.TopUp -> {
- val config = store.state.globalState.configManager?.config ?: return
- val defaultAddress = store.state.walletState.walletAddresses!!.list[0].address
- val url = TopUpHelper.getUrl(
- store.state.walletState.currencyData.currencySymbol!!,
- defaultAddress,
- config.moonPayApiKey,
- config.moonPayApiSecretKey
- )
- val customTabsIntent = CustomTabsIntent.Builder()
- .setToolbarColor(action.toolbarColor)
- .build()
- customTabsIntent.launchUrl(action.context, Uri.parse(url));
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt
deleted file mode 100644
index 533121bbe8..0000000000
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt
+++ /dev/null
@@ -1,337 +0,0 @@
-package com.tangem.tap.features.wallet.redux
-
-import com.tangem.blockchain.common.AmountType
-import com.tangem.blockchain.common.Wallet
-import com.tangem.commands.common.network.TangemService
-import com.tangem.common.extensions.isZero
-import com.tangem.common.extensions.toHexString
-import com.tangem.tap.common.extensions.toFiatString
-import com.tangem.tap.common.extensions.toFormattedCurrencyString
-import com.tangem.tap.common.extensions.toFormattedString
-import com.tangem.tap.common.extensions.toQrCode
-import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.domain.TapError
-import com.tangem.tap.domain.getFirstToken
-import com.tangem.tap.domain.twins.TwinCardNumber
-import com.tangem.tap.features.wallet.models.removeUnknownTransactions
-import com.tangem.tap.features.wallet.models.toPendingTransactions
-import com.tangem.tap.features.wallet.ui.BalanceStatus
-import com.tangem.tap.features.wallet.ui.BalanceWidgetData
-import com.tangem.tap.features.wallet.ui.TokenData
-import com.tangem.tap.store
-import org.rekotlin.Action
-
-class WalletReducer {
- companion object {
- fun reduce(action: Action, state: AppState): WalletState = internalReduce(action, state)
- }
-}
-
-private fun internalReduce(action: Action, state: AppState): WalletState {
-
- if (action !is WalletAction) return state.walletState
-
- var newState = state.walletState
-
- when (action) {
- is WalletAction.ResetState -> newState = WalletState()
- is WalletAction.EmptyWallet -> {
- val creatingWalletAllowed = !(newState.twinCardsState != null &&
- newState.twinCardsState?.isCreatingTwinCardsAllowed != true)
-
- newState = newState.copy(
- state = ProgressState.Done,
- currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
- mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed),
- topUpState = TopUpState(false)
- )
- }
- is WalletAction.LoadData.Failure -> {
- when (action.error) {
- is TapError.NoInternetConnection -> {
- val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet
- newState = newState.copy(
- state = ProgressState.Error,
- error = ErrorType.NoInternetConnection,
- walletAddresses = createAddressList(wallet, newState.walletAddresses),
- currencyData = BalanceWidgetData(
- status = BalanceStatus.Unreachable,
- currency = wallet?.blockchain?.fullName,
- token = wallet?.getFirstToken()?.symbol?.let {
- TokenData("", tokenSymbol = it)
- }),
- mainButton = WalletMainButton.SendButton(false),
- topUpState = TopUpState(false)
- )
- }
- is TapError.UnknownBlockchain -> {
- newState = newState.copy(
- state = ProgressState.Done,
- currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
- topUpState = TopUpState(false)
- )
- }
- }
-
- }
- is WalletAction.LoadWallet -> {
- val wallet = action.wallet
- val cardImage = if (newState.cardImage?.artworkId == action.artworkId) {
- newState.cardImage
- } else {
- null
- }
- newState = newState.copy(
- state = ProgressState.Loading,
- cardImage = cardImage,
- currencyData = BalanceWidgetData(
- BalanceStatus.Loading,
- wallet.blockchain.fullName,
- currencySymbol = wallet.blockchain.currency,
- token = wallet.getFirstToken()?.symbol?.let {
- TokenData("", tokenSymbol = it)
- }
- ),
- walletAddresses = createAddressList(wallet, newState.walletAddresses),
- mainButton = WalletMainButton.SendButton(false),
- topUpState = TopUpState(allowed = action.allowTopUp)
- )
- }
- is WalletAction.LoadWallet.Success -> newState = onWalletLoaded(action.wallet, newState)
-
- is WalletAction.LoadWallet.NoAccount -> {
- val wallet = state.globalState.scanNoteResponse?.walletManager?.wallet
- newState = newState.copy(
- state = ProgressState.Done, wallet = wallet,
- currencyData = BalanceWidgetData(
- BalanceStatus.NoAccount, wallet?.blockchain?.fullName,
- currencySymbol = wallet?.blockchain?.currency,
- amountToCreateAccount = action.amountToCreateAccount
- )
- )
- }
- is WalletAction.LoadWallet.Failure -> newState = newState.copy(
- state = ProgressState.Done,
- currencyData = newState.currencyData.copy(
- status = BalanceStatus.Unreachable,
- errorMessage = action.errorMessage
- ),
- topUpState = TopUpState(false)
- )
- is WalletAction.UpdateWallet -> {
- if (store.state.walletState.state == ProgressState.Done) {
- newState = newState.copy(updatingWallet = true)
- }
- }
- is WalletAction.UpdateWallet.ScheduleUpdatingWallet ->
- newState = newState.copy(updatingWallet = true)
- is WalletAction.UpdateWallet.Success -> {
- newState = onWalletLoaded(action.wallet, newState)
- newState = newState.copy(updatingWallet = newState.pendingTransactions.isNotEmpty())
- }
- is WalletAction.UpdateWallet.Failure -> newState = newState.copy(updatingWallet = false)
- is WalletAction.LoadFiatRate -> {
- newState.copy(currencyData = newState.currencyData.copy(
- fiatAmount = null,
- token = newState.currencyData.token?.copy(fiatAmount = null))
- )
- }
- is WalletAction.LoadFiatRate.Success -> {
- val rate = action.fiatRates.second ?: return newState
- val currency = action.fiatRates.first
- val fiatAmount = if (currency == newState.wallet?.blockchain?.currency) {
- newState.wallet?.amounts?.get(AmountType.Coin)?.value
- ?.toFiatString(rate, state.globalState.appCurrency)
- } else {
- newState.currencyData.fiatAmount
- }
- val token = newState.wallet?.getFirstToken()
- val tokenFiatAmount = if (currency == token?.symbol) {
- newState.wallet?.getTokenAmount(token)?.value?.toFiatString(rate, state.globalState.appCurrency)
- } else {
- newState.currencyData.token?.fiatAmount
- }
- newState = newState.copy(currencyData = newState.currencyData.copy(
- fiatAmount = fiatAmount,
- token = newState.currencyData.token?.copy(fiatAmount = tokenFiatAmount)
- ))
- }
- is WalletAction.LoadArtwork -> {
- val cardId = action.card.cardId
- val cardPublicKey = action.card.cardPublicKey?.toHexString()
- val artworkUrl = if (cardPublicKey != null && action.artworkId != null) {
- TangemService.getUrlForArtwork(cardId, cardPublicKey, action.artworkId)
- } else if (action.card.cardId.startsWith(Artwork.SERGIO_CARD_ID)) {
- Artwork.SERGIO_CARD_URL
- } else if (action.card.cardId.startsWith(Artwork.MARTA_CARD_ID)) {
- Artwork.MARTA_CARD_URL
- } else if (newState.twinCardsState?.cardNumber != null) {
- when (newState.twinCardsState?.cardNumber) {
- TwinCardNumber.First -> Artwork.TWIN_CARD_1
- TwinCardNumber.Second -> Artwork.TWIN_CARD_2
- null -> Artwork.DEFAULT_IMG_URL
- }
- } else {
- Artwork.DEFAULT_IMG_URL
- }
- newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl))
- }
- is WalletAction.ShowQrCode -> {
- newState = newState.copy(
- walletDialog = WalletDialog.QrDialog(
- newState.walletAddresses?.selectedAddress?.shareUrl?.toQrCode(),
- newState.walletAddresses?.selectedAddress?.shareUrl,
- newState.currencyData.currency
- )
- )
- }
- is WalletAction.HideDialog -> {
- newState = newState.copy(walletDialog = null)
- }
- is WalletAction.LoadPayId.Success -> newState = newState.copy(
- payIdData = PayIdData(PayIdState.Created, action.payId)
- )
- is WalletAction.LoadPayId.NotCreated -> newState = newState.copy(
- payIdData = PayIdData(PayIdState.NotCreated, null)
- )
- is WalletAction.DisablePayId -> newState = newState.copy(
- payIdData = PayIdData(PayIdState.Disabled, null)
- )
- is WalletAction.CreatePayId, is WalletAction.CreatePayId.Failure ->
- newState = newState.copy(
- walletDialog = WalletDialog.CreatePayIdDialog(CreatingPayIdState.EnterPayId)
- )
- is WalletAction.CreatePayId.CompleteCreatingPayId -> newState = newState.copy(
- walletDialog = WalletDialog.CreatePayIdDialog(CreatingPayIdState.Waiting)
- )
- is WalletAction.CreatePayId.Success -> newState = newState.copy(
- payIdData = PayIdData(PayIdState.Created, action.payId),
- walletDialog = null
- )
- is WalletAction.CreatePayId.Cancel -> 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.ShowWarning ->
- newState = newState.copy(walletDialog = WalletDialog.WarningDialog(action.warningType))
- is WalletAction.NeedToCheckHashesCountOnline ->
- newState = newState.copy(hashesCountVerified = false)
- is WalletAction.ConfirmHashesCount ->
- newState = newState.copy(hashesCountVerified = true)
- is WalletAction.TopUpAction -> {
- newState = newState.copy(topUpState = handleTopUpActions(action, newState.topUpState))
- }
- is WalletAction.ChangeSelectedAddress -> {
- val walletAddresses = newState.walletAddresses ?: return newState
- val address = walletAddresses.list.firstOrNull { it.type == action.type }
- ?: return newState
-
- newState = newState.copy(walletAddresses = WalletAddresses(address, walletAddresses.list))
- }
- is WalletAction.TwinsAction.SetTwinCard -> {
- newState = newState.copy(
- twinCardsState = TwinCardsState(
- secondCardId = action.secondCardId,
- cardNumber = action.number,
- showTwinOnboarding = newState.twinCardsState?.showTwinOnboarding
- ?: false,
- isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed
- )
- )
- }
- is WalletAction.TwinsAction.ShowOnboarding -> {
- newState = newState.copy(
- twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = true)
- ?: TwinCardsState(null, null,
- showTwinOnboarding = true,
- isCreatingTwinCardsAllowed = false)
- )
- }
- is WalletAction.TwinsAction.SetOnboardingShown -> {
- newState = newState.copy(
- twinCardsState = newState.twinCardsState?.copy(showTwinOnboarding = false)
- )
- }
- }
- return newState
-}
-
-fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? {
- if (wallet == null) return null
-
- val listOfAddressData = mutableListOf()
- // put a defaultAddress at the first place
- wallet.addresses.forEach {
- val addressData = AddressData(it.value, it.type, wallet.getShareUri(it.value), wallet.getExploreUrl(it.value))
- if (it.type == wallet.blockchain.defaultAddressType()) {
- listOfAddressData.add(0, addressData)
- } else {
- listOfAddressData.add(addressData)
- }
- }
-
- // restore a selected wallet address
- var indexOfSelectedWallet = 0
- walletAddresses?.let {
- val index = listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address }
- if (index != -1) indexOfSelectedWallet = index
- }
- return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData)
-}
-
-private fun handleTopUpActions(action: WalletAction.TopUpAction, state: TopUpState): TopUpState {
- return when (action) {
- is WalletAction.TopUpAction.TopUp -> state
- }
-}
-
-private fun onWalletLoaded(
- wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null
-): WalletState {
- val fiatCurrencySymbol = store.state.globalState.appCurrency
- val token = wallet.getFirstToken()
- val tokenData = if (token != null) {
- val tokenAmount = wallet.getTokenAmount(token)
- if (tokenAmount != null) {
- val tokenFiatRate = store.state.globalState.conversionRates.getRate(tokenAmount.currencySymbol)
- val tokenFiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
- TokenData(tokenAmount.value?.toFormattedString(tokenAmount.decimals) ?: "",
- tokenAmount.currencySymbol, tokenFiatAmount)
- } else {
- null
- }
- } else {
- null
- }
- val amount = wallet.amounts[AmountType.Coin]?.value
- val formattedAmount = amount?.toFormattedCurrencyString(
- wallet.blockchain.decimals(),
- wallet.blockchain.currency)
- val fiatRate = store.state.globalState.conversionRates.getRate(wallet.blockchain.currency)
- val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
-
- val pendingTransactions = wallet.recentTransactions
- .toPendingTransactions(wallet.address)
-
- val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
- val balanceStatus = if (pendingTransactions.isNotEmpty()) {
- BalanceStatus.TransactionInProgress
- } else {
- BalanceStatus.VerifiedOnline
- }
- return walletState.copy(
- state = ProgressState.Done, wallet = wallet,
- currencyData = BalanceWidgetData(
- balanceStatus, wallet.blockchain.fullName,
- currencySymbol = wallet.blockchain.currency,
- formattedAmount,
- token = tokenData,
- fiatAmount = fiatAmount
- ),
- pendingTransactions = pendingTransactions.removeUnknownTransactions(),
- mainButton = WalletMainButton.SendButton(sendButtonEnabled)
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
index ec4c134bab..244f7f026d 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
@@ -1,74 +1,124 @@
package com.tangem.tap.features.wallet.redux
import android.graphics.Bitmap
-import com.tangem.blockchain.common.Amount
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.AddressType
+import com.tangem.blockchain.extensions.isAboveZero
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.common.redux.global.StateDialog
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.domain.twins.TwinCardNumber
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.ui.BalanceWidgetData
+import com.tangem.tap.store
import org.rekotlin.StateType
+import java.math.BigDecimal
data class WalletState(
val state: ProgressState = ProgressState.Done,
val error: ErrorType? = null,
val cardImage: Artwork? = null,
- val wallet: Wallet? = null,
- val pendingTransactions: List = emptyList(),
val hashesCountVerified: Boolean? = null,
- val walletAddresses: WalletAddresses? = null,
- val currencyData: BalanceWidgetData = BalanceWidgetData(),
- val payIdData: PayIdData = PayIdData(),
- val walletDialog: WalletDialog? = null,
- val updatingWallet: Boolean = false,
- val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
- val topUpState: TopUpState = TopUpState(),
+ val walletDialog: StateDialog? = null,
val twinCardsState: TwinCardsState? = null,
+ val mainWarningsList: List = mutableListOf(),
+ val wallets: List = emptyList(),
+ val walletManagers: List = emptyList(),
+ val isMultiwalletAllowed: Boolean = false,
+ val cardCurrency: CryptoCurrencyName? = null,
+ val selectedWallet: CryptoCurrencyName? = null,
+ val primaryBlockchain: Blockchain? = null,
+ val primaryToken: Token? = null
) : StateType {
- val showDetails: Boolean =
- currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard &&
- currencyData.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain
- val showMultipleAddress: Boolean
- get() {
- val listOfAddresses = walletAddresses?.list ?: return false
- return (wallet?.blockchain == Blockchain.Bitcoin ||
- wallet?.blockchain == Blockchain.BitcoinTestnet ||
- wallet?.blockchain == Blockchain.CardanoShelley) &&
- listOfAddresses.size > 1
+ val primaryWallet = if (wallets.isNotEmpty()) wallets[0] else null
+// val primaryWalletManager = if (walletManagers.isNotEmpty()) walletManagers[0] else null
+
+ val shouldShowDetails: Boolean =
+ primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.EmptyCard &&
+ primaryWallet?.currencyData?.status != com.tangem.tap.features.wallet.ui.BalanceStatus.UnknownBlockchain
+
+ val blockchains: List
+ get() = walletManagers.map { it.wallet.blockchain }
+
+ fun getWalletManager(currencyName: CryptoCurrencyName?): WalletManager? {
+ if (currencyName == null) return null
+ val walletManager = walletManagers.find { it.wallet.blockchain.currency == currencyName }
+ return walletManager ?: getWalletManagerForToken(currencyName)
+ }
+
+ fun getWalletManagerForToken(currencyName: CryptoCurrencyName?): WalletManager? {
+ val ethereumWalletManager = walletManagers.find { it.wallet.blockchain == Blockchain.Ethereum }
+ return if (ethereumWalletManager?.presetTokens?.find { it.symbol == currencyName } != null) {
+ ethereumWalletManager
+ } else {
+ val primaryWalletManager = walletManagers.find { it.wallet.blockchain == primaryBlockchain }
+ if (primaryWalletManager?.presetTokens?.find { it.symbol == currencyName } != null) {
+ primaryWalletManager
+ } else {
+ ethereumWalletManager
+ }
}
+ }
+
+ fun getWalletData(currencyName: CryptoCurrencyName?): WalletData? {
+ if (currencyName == null) return null
+ return wallets.find { it.currencyData.currencySymbol == currencyName }
+ }
+
+ fun getSelectedWalletData(): WalletData? {
+ return wallets.find { it.currencyData.currencySymbol == selectedWallet }
+ }
+
+ fun canBeRemoved(walletData: WalletData?): Boolean {
+ if (walletData == null) return false
+
+ if (walletData.blockchain != store.state.walletState.primaryBlockchain
+ && (walletData.token == null || walletData.token != store.state.walletState.primaryToken)) {
+ val walletManager = getWalletManager(walletData.currencyData.currencySymbol)
+ ?: return true
+ val wallet = walletManager.wallet
+ if (walletData.blockchain != null) {
+ return wallet.recentTransactions.toPendingTransactions(wallet.address).isEmpty() &&
+ wallet.amounts.toSendableAmounts().isEmpty()
+ } else if (walletData.token != null) (
+ return wallet.recentTransactions.toPendingTransactionsForToken(
+ walletData.token, wallet.address).isEmpty()
+ && wallet.amounts[AmountType.Token(token = walletData.token)]?.isAboveZero() != true
+ )
+ }
+ return false
+ }
+
+ fun replaceWalletInWallets(walletData: WalletData?): List {
+ if (walletData == null) return wallets
+ return wallets.filter { it.currencyData.currency != walletData.currencyData.currency } + walletData
+ }
+
+ fun replaceSomeWallets(newWallets: List): List {
+ val currencies = newWallets.map { it.currencyData.currency }
+ return wallets.filter { !currencies.contains(it.currencyData.currency) } + newWallets
+ }
+
}
-sealed class WalletDialog {
+sealed class WalletDialog: StateDialog {
data class QrDialog(
val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName?
) : WalletDialog()
- data class CreatePayIdDialog(val creatingPayIdState: CreatingPayIdState?) : WalletDialog()
data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog()
- data class WarningDialog(val type: WarningType) : WalletDialog()
- data class TwinsOnboardingFragment(val secondCardId: String): WalletDialog()
+ object ScanFailsDialog: WalletDialog()
}
-enum class WarningType { CardSignedHashesBefore, DevCard }
-
-
enum class ProgressState { Loading, Done, Error }
enum class ErrorType { NoInternetConnection }
-enum class PayIdState { Disabled, Loading, NotCreated, Created, ErrorLoading }
-
-data class PayIdData(
- val payIdState: PayIdState = PayIdState.Loading,
- val payId: String? = null
-)
-
-enum class CreatingPayIdState { EnterPayId, Waiting }
-
sealed class WalletMainButton(enabled: Boolean) : Button(enabled) {
class SendButton(enabled: Boolean) : WalletMainButton(enabled)
class CreateWalletButton(enabled: Boolean) : WalletMainButton(enabled)
@@ -83,7 +133,8 @@ data class AddressData(
val address: String,
val type: AddressType,
val shareUrl: String,
- val exploreUrl: String
+ val exploreUrl: String,
+ val qrCode: Bitmap? = null
)
data class Artwork(
@@ -112,4 +163,27 @@ data class TwinCardsState(
val cardNumber: TwinCardNumber?,
val showTwinOnboarding: Boolean,
val isCreatingTwinCardsAllowed: Boolean
-)
\ No newline at end of file
+)
+
+data class WalletData(
+ val pendingTransactions: List = emptyList(),
+ val hashesCountVerified: Boolean? = null,
+ val walletAddresses: WalletAddresses? = null,
+ val currencyData: BalanceWidgetData = BalanceWidgetData(),
+ val updatingWallet: Boolean = false,
+ val topUpState: TopUpState = TopUpState(),
+ val allowToSend: Boolean = true,
+ val fiatRateString: String? = null,
+ val fiatRate: BigDecimal? = null,
+ val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
+ val token: Token? = null,
+ val blockchain: Blockchain? = null,
+) {
+ fun shouldShowMultipleAddress(): Boolean {
+ val listOfAddresses = walletAddresses?.list ?: return false
+ return (currencyData.currencySymbol == Blockchain.Bitcoin.currency ||
+ currencyData.currencySymbol == Blockchain.BitcoinTestnet.currency ||
+ currencyData.currencySymbol == Blockchain.CardanoShelley.currency) &&
+ listOfAddresses.size > 1
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
new file mode 100644
index 0000000000..6daa182e35
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
@@ -0,0 +1,134 @@
+package com.tangem.tap.features.wallet.redux.middlewares
+
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.TokenFinder
+import com.tangem.blockchain.extensions.Result
+import com.tangem.common.extensions.isZero
+import com.tangem.tap.common.redux.global.GlobalState
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.currenciesRepository
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class MultiWalletMiddleware {
+ fun handle(
+ action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?,
+ ) {
+ when (action) {
+ is WalletAction.MultiWallet.AddWalletManagers -> {
+ val wallets = action.walletManagers.map { it.wallet }
+ store.state.globalState.feedbackManager?.infoHolder?.setWalletsInfo(wallets)
+ }
+ is WalletAction.MultiWallet.SelectWallet -> {
+ if (action.walletData != null) {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletDetails))
+ }
+ }
+ is WalletAction.MultiWallet.AddToken -> {
+ globalState?.scanNoteResponse?.card?.cardId?.let {
+ currenciesRepository.saveAddedToken(it, action.token)
+ }
+ addToken(action.token, walletState)
+ }
+ is WalletAction.MultiWallet.AddTokens -> {
+ action.tokens.map { addToken(it, walletState) }
+ }
+ is WalletAction.MultiWallet.AddBlockchain -> {
+ globalState?.scanNoteResponse?.card?.let { card ->
+ currenciesRepository.saveAddedBlockchain(card.cardId, action.blockchain)
+ globalState.tapWalletManager.walletManagerFactory
+ .makeWalletManager(card, action.blockchain)?.let {
+ store.dispatch(WalletAction.MultiWallet.AddWalletManagers(it))
+ }
+ }
+ store.dispatch(WalletAction.LoadFiatRate(currency = action.blockchain.currency))
+ store.dispatch(WalletAction.LoadWallet(currency = action.blockchain.currency))
+ }
+ is WalletAction.MultiWallet.SaveCurrencies -> {
+ val cardId = globalState?.scanNoteResponse?.card?.cardId
+ cardId?.let { currenciesRepository.saveCardCurrencies(it, action.cardCurrencies) }
+ }
+ is WalletAction.MultiWallet.RemoveWallet -> {
+ val cardId = globalState?.scanNoteResponse?.card?.cardId
+ if (action.walletData.token != null) {
+ cardId?.let { currenciesRepository.removeToken(it, action.walletData.token) }
+ } else if (action.walletData.blockchain != null) {
+ cardId?.let { currenciesRepository.removeBlockchain(it, action.walletData.blockchain) }
+ }
+ }
+ is WalletAction.MultiWallet.FindBlockchainsInUse -> {
+ val blockchains = currenciesRepository.getBlockchains()
+ .filterNot { walletState?.blockchains?.contains(it) == true }
+ val walletManagers = action.factory.makeWalletManagers(action.card, blockchains)
+
+ scope.launch {
+ walletManagers.map { walletManager ->
+ async(Dispatchers.IO) {
+ try {
+ walletManager.update()
+ val wallet = walletManager.wallet
+ val coinAmount = wallet.amounts[AmountType.Coin]?.value
+ if (coinAmount != null && !coinAmount.isZero()) {
+ if (walletState?.getWalletData(wallet.blockchain.currency) == null) {
+ scope.launch(Dispatchers.Main) {
+ store.dispatch(WalletAction.MultiWallet.AddWalletManagers(
+ listOfNotNull(walletManager)))
+ store.dispatch(WalletAction.MultiWallet.AddBlockchain(
+ wallet.blockchain
+ ))
+ store.dispatch(WalletAction.LoadWallet.Success(wallet))
+ }
+ }
+ }
+ } catch (exception: Exception) {
+ }
+ }
+ }
+ }
+ }
+ is WalletAction.MultiWallet.FindTokensInUse -> {
+ val walletManager = walletState?.getWalletManager(Blockchain.Ethereum.currency)
+ ?: return
+ val tokenFinder = walletManager as TokenFinder
+ scope.launch {
+ val result = tokenFinder.findTokens()
+ withContext(Dispatchers.Main) {
+ when (result) {
+ is Result.Success -> {
+ if (result.data.isNotEmpty()) {
+ store.dispatch(WalletAction.MultiWallet.AddTokens(
+ walletManager.presetTokens.toList()
+ ))
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun addToken(token: Token, walletState: WalletState?) {
+ val walletManager = walletState?.getWalletManager(token.symbol)
+ scope.launch {
+ val result = walletManager?.addToken(token)
+ withContext(Dispatchers.Main) {
+ when (result) {
+ is Result.Success -> {
+ store.dispatch(WalletAction.LoadFiatRate(currency = token.symbol))
+ store.dispatch(WalletAction.MultiWallet.TokenLoaded(result.data))
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TopUpMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TopUpMiddleware.kt
new file mode 100644
index 0000000000..e0706570f7
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TopUpMiddleware.kt
@@ -0,0 +1,32 @@
+package com.tangem.tap.features.wallet.redux.middlewares
+
+import android.net.Uri
+import androidx.browser.customtabs.CustomTabsIntent
+import com.tangem.tap.domain.TopUpHelper
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+
+class TopUpMiddleware {
+ fun handle(action: WalletAction.TopUpAction) {
+ when (action) {
+ is WalletAction.TopUpAction.TopUp -> {
+ val selectedWalletData = store.state.walletState.getSelectedWalletData()
+ val config = store.state.globalState.configManager?.config ?: return
+ val addresses = selectedWalletData?.walletAddresses ?: return
+ if (addresses.list.isEmpty()) return
+
+ val defaultAddress = addresses.list[0].address
+ val url = TopUpHelper.getUrl(
+ selectedWalletData.currencyData.currencySymbol!!,
+ defaultAddress,
+ config.moonPayApiKey,
+ config.moonPayApiSecretKey
+ )
+ val customTabsIntent = CustomTabsIntent.Builder()
+ .setToolbarColor(action.toolbarColor)
+ .build()
+ customTabsIntent.launchUrl(action.context, Uri.parse(url));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt
new file mode 100644
index 0000000000..88ba60d1d1
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TwinsMiddleware.kt
@@ -0,0 +1,19 @@
+package com.tangem.tap.features.wallet.redux.middlewares
+
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.preferencesStorage
+import com.tangem.tap.store
+
+class TwinsMiddleware {
+ fun handle(action: WalletAction.TwinsAction) {
+ when (action) {
+ is WalletAction.TwinsAction.SetTwinCard -> {
+ val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
+ if (showOnboarding) store.dispatch(WalletAction.TwinsAction.ShowOnboarding)
+ }
+ WalletAction.TwinsAction.SetOnboardingShown -> {
+ preferencesStorage.saveTwinsOnboardingShown()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
new file mode 100644
index 0000000000..05993a08ef
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
@@ -0,0 +1,239 @@
+package com.tangem.tap.features.wallet.redux.middlewares
+
+import android.content.Intent
+import android.net.Uri
+import androidx.core.content.ContextCompat
+import com.tangem.blockchain.common.*
+import com.tangem.common.CompletionResult
+import com.tangem.common.extensions.isZero
+import com.tangem.tap.*
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.common.extensions.copyToClipboard
+import com.tangem.tap.common.extensions.shareText
+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.twins.TwinsHelper
+import com.tangem.tap.features.details.redux.DetailsAction
+import com.tangem.tap.features.details.redux.twins.CreateTwinWallet
+import com.tangem.tap.features.send.redux.PrepareSendScreen
+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.network.NetworkStateChanged
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.rekotlin.Action
+import org.rekotlin.Middleware
+
+class WalletMiddleware {
+ private val topUpMiddleware = TopUpMiddleware()
+ private val twinsMiddleware = TwinsMiddleware()
+ private val warningsMiddleware = WarningsMiddleware()
+ private val multiWalletMiddleware = MultiWalletMiddleware()
+
+ val walletMiddleware: Middleware = { dispatch, state ->
+ { next ->
+ { action ->
+ val globalState = state()?.globalState
+ val walletState = state()?.walletState
+ when (action) {
+ is WalletAction.TopUpAction -> topUpMiddleware.handle(action)
+ is WalletAction.TwinsAction -> twinsMiddleware.handle(action)
+ is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
+ is WalletAction.MultiWallet ->
+ multiWalletMiddleware.handle(action, walletState, globalState)
+
+ is WalletAction.LoadWallet -> {
+ scope.launch {
+ if (action.currency == null) {
+ walletState?.walletManagers?.map { walletManager ->
+ globalState?.tapWalletManager?.loadWalletData(walletManager)
+ }
+ } else {
+ val walletManager = walletState?.getWalletManager(action.currency)
+ walletManager?.let { globalState?.tapWalletManager?.loadWalletData(it) }
+ }
+ }
+ }
+ is WalletAction.LoadWallet.Success -> {
+ val coinAmount = action.wallet.amounts[AmountType.Coin]?.value
+ if (coinAmount != null && !coinAmount.isZero()) {
+ if (walletState?.getWalletData(action.wallet.blockchain.currency) == null) {
+ store.dispatch(WalletAction.MultiWallet.AddBlockchain(action.wallet.blockchain))
+ store.dispatch(WalletAction.LoadWallet.Success(action.wallet))
+ }
+ }
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
+ warningsMiddleware.tryToShowAppRatingWarning(action.wallet)
+ }
+ is WalletAction.LoadFiatRate -> {
+ scope.launch {
+ if (action.wallet != null) {
+ globalState?.tapWalletManager?.loadFiatRate(
+ globalState.appCurrency, action.wallet
+ )
+ } else if (action.currency != null) {
+ globalState?.tapWalletManager?.loadFiatRate(
+ globalState.appCurrency, action.currency
+ )
+ } else {
+ walletState?.wallets?.filter { it.blockchain != null }
+ ?.map { walletState.getWalletManager(it.blockchain?.currency) }
+ ?.mapNotNull { walletManager ->
+ walletManager?.let {
+ globalState?.tapWalletManager?.loadFiatRate(
+ globalState.appCurrency, walletManager.wallet
+ )
+ }
+ }
+ }
+ }
+ }
+ is WalletAction.CreateWallet -> {
+ if (walletState?.twinCardsState != null) {
+ store.dispatch(DetailsAction.CreateTwinWalletAction.ShowWarning(
+ globalState?.scanNoteResponse?.card?.cardId?.let {
+ TwinsHelper.getTwinCardNumber(it)
+ },
+ CreateTwinWallet.CreateWallet
+ ))
+ } else {
+ scope.launch {
+ val result = tangemSdkManager.createWallet(
+ globalState?.scanNoteResponse?.card?.cardId
+ )
+ when (result) {
+ is CompletionResult.Success -> {
+ globalState?.tapWalletManager?.onCardScanned(result.data)
+ }
+ }
+ }
+ }
+ }
+ is WalletAction.UpdateWallet -> {
+ if (action.currency != null) {
+ scope.launch {
+ val walletManager = walletState?.getWalletManager(action.currency)
+ walletManager?.let { globalState?.tapWalletManager?.updateWallet(it) }
+ }
+ } else {
+ scope.launch {
+ if (walletState?.state == ProgressState.Done) {
+ walletState.walletManagers.map { walletManager ->
+ globalState?.tapWalletManager?.updateWallet(walletManager)
+ }
+ }
+ }
+ }
+
+ }
+ is WalletAction.Scan -> {
+ scope.launch {
+ val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler)
+ scope.launch(Dispatchers.Main) {
+ store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
+ when (result) {
+ is CompletionResult.Success -> {
+ tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
+ globalState?.tapWalletManager?.onCardScanned(result.data, true)
+ }
+ }
+ }
+ }
+ }
+ is WalletAction.LoadData -> {
+ scope.launch {
+ val scanNoteResponse = globalState?.scanNoteResponse
+ ?: return@launch
+ if (!walletState?.wallets.isNullOrEmpty()) {
+ globalState.tapWalletManager.reloadData(scanNoteResponse)
+ } else {
+ globalState.tapWalletManager.loadData(scanNoteResponse)
+ }
+ }
+ }
+ is NetworkStateChanged -> {
+ globalState?.scanNoteResponse?.let { scanNoteResponse ->
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
+ scope.launch {
+ globalState.tapWalletManager.loadData(scanNoteResponse)
+ }
+ }
+ }
+ is WalletAction.CopyAddress -> {
+ action.context.copyToClipboard(action.address)
+ store.dispatch(WalletAction.CopyAddress.Success)
+ }
+ is WalletAction.ShareAddress -> {
+ action.context.shareText(action.address)
+ }
+ is WalletAction.ExploreAddress -> {
+ val uri = Uri.parse(action.exploreUrl)
+ val intent = Intent(Intent.ACTION_VIEW, uri)
+ ContextCompat.startActivity(action.context, intent, null)
+ }
+ is WalletAction.Send -> {
+ val newAction = prepareSendAction(action.amount, state()?.walletState)
+ store.dispatch(newAction)
+ if (newAction is PrepareSendScreen) {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
+ }
+ }
+ }
+ next(action)
+ }
+ }
+ }
+
+ private fun prepareSendAction(amount: Amount?, state: WalletState?): Action {
+ val selectedWalletData = state?.getSelectedWalletData()
+ val currency = selectedWalletData?.currencyData?.currencySymbol
+ val walletManager = state?.getWalletManager(currency)
+ val wallet = walletManager?.wallet
+
+ return if (amount != null) {
+ if (amount.type is AmountType.Token) {
+ prepareSendActionForToken(amount, state, selectedWalletData, wallet, walletManager)
+ } else {
+ PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletManager)
+ }
+ } else {
+ val amounts = wallet?.amounts?.toSendableAmounts()
+ if (currency != null && state.isMultiwalletAllowed) {
+ val amountToSend = amounts?.find { it.currencySymbol == currency }
+ ?: return WalletAction.Send.ChooseCurrency(amounts)
+ if (amountToSend.type is AmountType.Token) {
+ prepareSendActionForToken(amountToSend, state, selectedWalletData, wallet, walletManager)
+ } else {
+ PrepareSendScreen(amountToSend, selectedWalletData.fiatRate, walletManager)
+ }
+ } else {
+ if (amounts?.size ?: 0 > 1) {
+ WalletAction.Send.ChooseCurrency(amounts)
+ } else {
+ val amountToSend = amounts?.first()
+ PrepareSendScreen(amountToSend, selectedWalletData?.fiatRate, walletManager)
+ }
+ }
+ }
+ }
+
+ private fun prepareSendActionForToken(
+ amount: Amount, state: WalletState?, selectedWalletData: WalletData?, wallet: Wallet?,
+ walletManager: WalletManager?
+ ): PrepareSendScreen {
+ val coinRate = state?.getWalletData(wallet?.blockchain?.currency)?.fiatRate
+ val tokenRate = if (state?.isMultiwalletAllowed == true) {
+ selectedWalletData?.fiatRate
+ } else {
+ selectedWalletData?.currencyData?.token?.fiatRate
+ }
+ return PrepareSendScreen(
+ wallet?.amounts?.get(AmountType.Coin), coinRate, walletManager,
+ amount, tokenRate)
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
new file mode 100644
index 0000000000..d158d3d323
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
@@ -0,0 +1,145 @@
+package com.tangem.tap.features.wallet.redux.middlewares
+
+import com.tangem.blockchain.common.BlockchainSdkError
+import com.tangem.blockchain.common.SignatureCountValidator
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.extensions.SimpleResult
+import com.tangem.commands.common.card.Card
+import com.tangem.commands.common.card.CardType
+import com.tangem.commands.verifycard.VerifyCardState
+import com.tangem.common.extensions.getType
+import com.tangem.tap.common.analytics.AnalyticsEvent
+import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.common.extensions.isGreaterThan
+import com.tangem.tap.common.redux.global.GlobalState
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
+import com.tangem.tap.domain.isMultiwalletAllowed
+import com.tangem.tap.domain.twins.isTwinCard
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.network.NetworkConnectivity
+import com.tangem.tap.preferencesStorage
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.math.BigDecimal
+
+class WarningsMiddleware {
+ fun handle(action: WalletAction.Warnings, globalState: GlobalState?) {
+ when (action) {
+ WalletAction.Warnings.Update -> setWarningMessages()
+ is WalletAction.Warnings.CheckIfNeeded -> {
+ showCardWarningsIfNeeded(globalState)
+ val readyToShow = preferencesStorage.appRatingLaunchObserver.isReadyToShow()
+ if (readyToShow) addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
+ }
+ is WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline -> checkHashesCountOnline()
+ is WalletAction.Warnings.CheckHashesCount.SaveCardId -> {
+ val cardId = globalState?.scanNoteResponse?.card?.cardId
+ cardId?.let { preferencesStorage.saveScannedCardId(it) }
+ }
+ is WalletAction.Warnings.AppRating.RemindLater -> {
+ preferencesStorage.appRatingLaunchObserver.applyDelayedShowing()
+ }
+ is WalletAction.Warnings.AppRating.SetNeverToShow -> {
+ preferencesStorage.appRatingLaunchObserver.setNeverToShow()
+ }
+ }
+ }
+
+ fun tryToShowAppRatingWarning(wallet: Wallet) {
+ val nonZeroWalletsCount = wallet.amounts.filter {
+ it.value.value?.isGreaterThan(BigDecimal.ZERO) ?: false
+ }.size
+ if (nonZeroWalletsCount > 0) {
+ preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds()
+ }
+ if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) {
+ FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISPLAYED)
+ addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
+ }
+ }
+
+ private fun showCardWarningsIfNeeded(globalState: GlobalState?) {
+ val validator = globalState?.scanNoteResponse?.walletManager as? SignatureCountValidator
+ globalState?.scanNoteResponse?.card?.let { card ->
+ globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
+ if (card.getType() != CardType.Release) {
+ addWarningMessage(WarningMessagesManager.devCardWarning())
+ } else if (!preferencesStorage.wasCardScannedBefore(card.cardId)) {
+ checkIfWarningNeeded(card, validator)?.let { addWarningMessage(it) }
+ }
+ if (card.getType() == CardType.Release) {
+ if (globalState.scanNoteResponse.verifyResponse?.verificationState ==
+ VerifyCardState.VerifiedOffline) {
+ addWarningMessage(WarningMessagesManager.onlineVerificationFailed())
+ }
+ }
+ setWarningMessages()
+ }
+ }
+
+ private fun checkIfWarningNeeded(
+ card: Card, signatureCountValidator: SignatureCountValidator? = null,
+ ): WarningMessage? {
+ if (card.isTwinCard() || card.isMultiwalletAllowed) return null
+
+ return if (signatureCountValidator == null) {
+ if (card.walletSignedHashes ?: 0 > 0) {
+ WarningMessagesManager.alreadySignedHashesWarning()
+ } else {
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
+ null
+ }
+ } else {
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline)
+ null
+ }
+ }
+
+ private fun checkHashesCountOnline() {
+ if (store.state.walletState.hashesCountVerified != false) return
+ if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return
+
+ val card = store.state.globalState.scanNoteResponse?.card
+ if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return
+
+ if (card.isTwinCard() || card.isMultiwalletAllowed) return
+
+ val validator = store.state.globalState.scanNoteResponse?.walletManager
+ as? SignatureCountValidator
+ scope.launch {
+ val result = validator?.validateSignatureCount(card.walletSignedHashes ?: 0)
+ withContext(Dispatchers.Main) {
+ when (result) {
+ SimpleResult.Success -> {
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.ConfirmHashesCount)
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
+ }
+ is SimpleResult.Failure ->
+ if (result.error is BlockchainSdkError.SignatureCountNotMatched) {
+ addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
+ } else if (card.walletSignedHashes ?: 0 > 0) {
+ addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
+ }
+ }
+ }
+ }
+ }
+
+ private fun addWarningMessage(warning: WarningMessage, autoUpdate: Boolean = false) {
+ store.state.globalState.warningManager?.addWarning(warning)
+ if (autoUpdate) setWarningMessages()
+ }
+
+ private fun setWarningMessages() {
+ store.dispatch(WalletAction.Warnings.Set(getWarnings()))
+ }
+
+ private fun getWarnings(): List {
+ val warningManager = store.state.globalState.warningManager ?: return emptyList()
+ return warningManager.getWarnings(WarningMessage.Location.MainScreen, store.state.walletState.blockchains)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
new file mode 100644
index 0000000000..158d7e61ff
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
@@ -0,0 +1,169 @@
+package com.tangem.tap.features.wallet.redux.reducers
+
+import com.tangem.blockchain.common.AmountType
+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.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.ui.BalanceStatus
+import com.tangem.tap.features.wallet.ui.BalanceWidgetData
+import com.tangem.tap.features.wallet.ui.TokenData
+import com.tangem.tap.store
+
+class MultiWalletReducer {
+ fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState {
+ return when (action) {
+ is WalletAction.MultiWallet.AddWalletManagers -> {
+ state.copy(
+ walletManagers = state.walletManagers + action.walletManagers
+ )
+ }
+ is WalletAction.MultiWallet.AddBlockchains -> {
+ val wallets = action.blockchains.map { blockchain ->
+ val wallet = state.getWalletManager(blockchain.currency)?.wallet
+ val cardToken = if (!state.isMultiwalletAllowed) {
+ wallet?.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
+ } else {
+ null
+ }
+ WalletData(
+ currencyData = BalanceWidgetData(
+ BalanceStatus.Loading,
+ blockchain.fullName,
+ currencySymbol = blockchain.currency,
+ token = cardToken
+ ),
+ walletAddresses = createAddressList(wallet),
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(allowed = false),
+ blockchain = blockchain
+ )
+ }
+
+ val selectedWallet = if (!state.isMultiwalletAllowed) {
+ wallets[0].currencyData.currencySymbol
+ } else {
+ state.selectedWallet
+ }
+ state.copy(
+ wallets = wallets,
+ selectedWallet = selectedWallet
+ )
+ }
+ is WalletAction.MultiWallet.AddBlockchain -> {
+ val wallet = state.getWalletManager(action.blockchain.currency)?.wallet
+ val walletData = WalletData(
+ currencyData = BalanceWidgetData(
+ BalanceStatus.Loading,
+ action.blockchain.fullName,
+ currencySymbol = action.blockchain.currency,
+ ),
+ walletAddresses = createAddressList(wallet),
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(allowed = false),
+ blockchain = action.blockchain
+ )
+ val newState = state.copy(wallets = state.replaceWalletInWallets(walletData))
+ if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) {
+ OnWalletLoadedReducer().reduce(wallet, newState)
+ } else {
+ newState
+ }
+ }
+ is WalletAction.MultiWallet.AddTokens -> {
+ if (!state.isMultiwalletAllowed) return state
+ val wallets = action.tokens.map { token ->
+ WalletData(
+ currencyData = BalanceWidgetData(
+ BalanceStatus.Loading,
+ currency = token.name,
+ currencySymbol = token.symbol
+ ),
+ walletAddresses = createAddressList(
+ state.getWalletManagerForToken(token.symbol)?.wallet
+ ),
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(allowed = false),
+ token = token
+ )
+ }
+ state.copy(wallets = state.replaceSomeWallets(wallets))
+ }
+ is WalletAction.MultiWallet.AddToken -> {
+ if (!state.isMultiwalletAllowed) return state
+
+ val walletAddresses = createAddressList(
+ state.getWalletManagerForToken(action.token.symbol)?.wallet
+
+ )
+ val wallet = WalletData(
+ currencyData = BalanceWidgetData(
+ BalanceStatus.Loading,
+ currency = action.token.name,
+ currencySymbol = action.token.symbol
+ ),
+ walletAddresses = walletAddresses,
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(allowed = false),
+ token = action.token
+ )
+ val wallets = state.replaceWalletInWallets(wallet)
+ state.copy(wallets = wallets)
+ }
+ is WalletAction.MultiWallet.TokenLoaded -> {
+ val pendingTransactions = state.getWalletManagerForToken(action.amount.currencySymbol)
+ ?.wallet?.let { wallet ->
+ wallet.recentTransactions.toPendingTransactions(wallet.address)
+ } ?: emptyList()
+
+ val sendButtonEnabled = action.amount.value?.isZero() == false && pendingTransactions.isEmpty()
+ val tokenPendingTransactions = pendingTransactions
+ .filter { it.currency == action.amount.currencySymbol }
+ val tokenBalanceStatus = when {
+ tokenPendingTransactions.isNotEmpty() -> BalanceStatus.TransactionInProgress
+ pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress
+ else -> BalanceStatus.VerifiedOnline
+ }
+ val tokenWalletData = state.getWalletData(action.amount.currencySymbol)
+ val newTokenWalletData = tokenWalletData?.copy(
+ currencyData = tokenWalletData.currencyData.copy(
+ status = tokenBalanceStatus,
+ amount = action.amount.value?.toFormattedCurrencyString(
+ action.amount.decimals, action.amount.currencySymbol
+ ),
+ fiatAmount = tokenWalletData.fiatRate?.let {
+ action.amount.value
+ ?.toFiatString(it, store.state.globalState.appCurrency)
+ }
+ ),
+ pendingTransactions = pendingTransactions.removeUnknownTransactions(),
+ mainButton = WalletMainButton.SendButton(sendButtonEnabled)
+ )
+ val wallets = state.replaceWalletInWallets(newTokenWalletData)
+ state.copy(wallets = wallets)
+ }
+ is WalletAction.MultiWallet.SetIsMultiwalletAllowed ->
+ state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed)
+
+ is WalletAction.MultiWallet.SelectWallet ->
+ state.copy(selectedWallet = action.walletData?.currencyData?.currencySymbol)
+
+ is WalletAction.MultiWallet.RemoveWallet -> {
+ state.copy(wallets = state.wallets.filterNot {
+ it.currencyData.currencySymbol == action.walletData.currencyData.currencySymbol
+ })
+ }
+ is WalletAction.MultiWallet.SetPrimaryBlockchain ->
+ state.copy(primaryBlockchain = action.blockchain)
+
+ is WalletAction.MultiWallet.SetPrimaryToken ->
+ state.copy(primaryToken = action.token)
+ is WalletAction.MultiWallet.FindTokensInUse -> state
+ is WalletAction.MultiWallet.FindBlockchainsInUse -> state
+ is WalletAction.MultiWallet.SaveCurrencies -> state
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
new file mode 100644
index 0000000000..2b3ad57454
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
@@ -0,0 +1,155 @@
+package com.tangem.tap.features.wallet.redux.reducers
+
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Wallet
+import com.tangem.common.extensions.isZero
+import com.tangem.tap.common.extensions.toFiatString
+import com.tangem.tap.common.extensions.toFormattedCurrencyString
+import com.tangem.tap.common.extensions.toFormattedString
+import com.tangem.tap.domain.getFirstToken
+import com.tangem.tap.features.wallet.models.removeUnknownTransactions
+import com.tangem.tap.features.wallet.models.toPendingTransactions
+import com.tangem.tap.features.wallet.redux.ProgressState
+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.BalanceStatus
+import com.tangem.tap.features.wallet.ui.BalanceWidgetData
+import com.tangem.tap.features.wallet.ui.TokenData
+import com.tangem.tap.store
+import java.math.RoundingMode
+
+class OnWalletLoadedReducer {
+
+ fun reduce(wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null): WalletState {
+ return if (!walletState.isMultiwalletAllowed) {
+ onSingleWalletLoaded(wallet, walletState, topUpAllowed)
+ } else {
+ onMultiWalletLoaded(wallet, walletState, topUpAllowed)
+ }
+ }
+
+ private fun onMultiWalletLoaded(
+ wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null
+ ): WalletState {
+ val fiatCurrencySymbol = store.state.globalState.appCurrency
+ val amount = wallet.amounts[AmountType.Coin]?.value
+ if (walletState.getWalletData(wallet.blockchain.currency) == null) {
+ return walletState
+ }
+ val formattedAmount = amount?.toFormattedCurrencyString(
+ wallet.blockchain.decimals(),
+ wallet.blockchain.currency)
+
+ val pendingTransactions = wallet.recentTransactions
+ .toPendingTransactions(wallet.address)
+
+ val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
+ val balanceStatus = if (pendingTransactions.isNotEmpty()) {
+ BalanceStatus.TransactionInProgress
+ } else {
+ BalanceStatus.VerifiedOnline
+ }
+ val walletData = walletState.getWalletData(wallet.blockchain.currency)
+ ?: WalletData()
+ val newWalletData = walletData.copy(
+ currencyData = walletData.currencyData.copy(
+ status = balanceStatus, currency = wallet.blockchain.fullName,
+ currencySymbol = wallet.blockchain.currency,
+ amount = formattedAmount,
+ fiatAmount = walletData.fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
+ ),
+ pendingTransactions = pendingTransactions.removeUnknownTransactions(),
+ mainButton = WalletMainButton.SendButton(sendButtonEnabled)
+ )
+
+ val tokens = wallet.getTokens().mapNotNull { token ->
+ val tokenWalletData = walletState.getWalletData(token.symbol)
+ val tokenPendingTransactions = pendingTransactions.filter { it.currency == token.symbol }
+ val tokenBalanceStatus = when {
+ tokenPendingTransactions.isNotEmpty() -> BalanceStatus.TransactionInProgress
+ pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress
+ else -> BalanceStatus.VerifiedOnline
+ }
+ tokenWalletData?.copy(
+ currencyData = tokenWalletData.currencyData.copy(
+ status = tokenBalanceStatus,
+ amount = wallet.getTokenAmount(token)?.value?.toFormattedCurrencyString(
+ token.decimals, token.symbol
+ ),
+ fiatAmount = tokenWalletData.fiatRate?.let {
+ wallet.getTokenAmount(token)?.value
+ ?.toFiatString(it, fiatCurrencySymbol)
+ }
+ ),
+ pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(),
+ mainButton = WalletMainButton.SendButton(sendButtonEnabled)
+ )
+ }
+ val wallets = walletState.replaceSomeWallets((tokens + newWalletData))
+
+ val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
+ ProgressState.Loading
+ } else {
+ ProgressState.Done
+ }
+ return walletState.copy(
+ state = state, wallets = wallets, error = null
+ )
+ }
+
+ private fun onSingleWalletLoaded(
+ wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null
+ ): WalletState {
+ if (wallet.blockchain != walletState.primaryBlockchain) return walletState
+
+ val fiatCurrencySymbol = store.state.globalState.appCurrency
+ val token = wallet.getFirstToken()
+ val tokenData = if (token != null) {
+ val tokenAmount = wallet.getTokenAmount(token)
+ if (tokenAmount != null) {
+ val tokenFiatRate = walletState.primaryWallet?.currencyData?.token?.fiatRate
+ val tokenFiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
+ TokenData(tokenAmount.value?.toFormattedString(tokenAmount.decimals) ?: "",
+ tokenAmount.currencySymbol, tokenFiatAmount)
+ } else {
+ null
+ }
+ } else {
+ null
+ }
+ val amount = wallet.amounts[AmountType.Coin]?.value
+ val formattedAmount = amount?.toFormattedCurrencyString(
+ wallet.blockchain.decimals(),
+ wallet.blockchain.currency)
+ val fiatRate = walletState.primaryWallet?.fiatRate
+ val fiatAmountRaw = fiatRate?.multiply(amount)?.setScale(2, RoundingMode.DOWN)
+ val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
+
+ val pendingTransactions = wallet.recentTransactions
+ .toPendingTransactions(wallet.address)
+
+ val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty()
+ val balanceStatus = if (pendingTransactions.isNotEmpty()) {
+ BalanceStatus.TransactionInProgress
+ } else {
+ BalanceStatus.VerifiedOnline
+ }
+ val walletData = walletState.primaryWallet?.copy(
+ currencyData = BalanceWidgetData(
+ balanceStatus, wallet.blockchain.fullName,
+ currencySymbol = wallet.blockchain.currency,
+ formattedAmount,
+ token = tokenData,
+ fiatAmount = fiatAmount,
+ fiatAmountRaw = fiatAmountRaw
+ ),
+ pendingTransactions = pendingTransactions.removeUnknownTransactions(),
+ mainButton = WalletMainButton.SendButton(sendButtonEnabled)
+ )
+ val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
+ return walletState.copy(
+ state = ProgressState.Done, wallets = wallets, error = null
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt
new file mode 100644
index 0000000000..79025ca6cc
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TwinsReducer.kt
@@ -0,0 +1,36 @@
+package com.tangem.tap.features.wallet.redux.reducers
+
+import com.tangem.tap.features.wallet.redux.TwinCardsState
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletState
+
+class TwinsReducer {
+ fun reduce(action: WalletAction.TwinsAction, state: WalletState): WalletState {
+ return when (action) {
+ is WalletAction.TwinsAction.SetTwinCard -> {
+ state.copy(
+ twinCardsState = TwinCardsState(
+ secondCardId = action.secondCardId,
+ cardNumber = action.number,
+ showTwinOnboarding = state.twinCardsState?.showTwinOnboarding
+ ?: false,
+ isCreatingTwinCardsAllowed = action.isCreatingTwinCardsAllowed
+ )
+ )
+ }
+ is WalletAction.TwinsAction.ShowOnboarding -> {
+ state.copy(
+ twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = true)
+ ?: TwinCardsState(null, null,
+ showTwinOnboarding = true,
+ isCreatingTwinCardsAllowed = false)
+ )
+ }
+ is WalletAction.TwinsAction.SetOnboardingShown -> {
+ state.copy(
+ twinCardsState = state.twinCardsState?.copy(showTwinOnboarding = false)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
new file mode 100644
index 0000000000..957e9a32d3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
@@ -0,0 +1,361 @@
+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.commands.common.network.TangemService
+import com.tangem.common.extensions.toHexString
+import com.tangem.tap.common.extensions.toFiatString
+import com.tangem.tap.common.extensions.toFormattedCurrencyString
+import com.tangem.tap.common.extensions.toQrCode
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.common.redux.global.FiatCurrencyName
+import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.getFirstToken
+import com.tangem.tap.domain.twins.TwinCardNumber
+import com.tangem.tap.features.wallet.redux.*
+import com.tangem.tap.features.wallet.ui.BalanceStatus
+import com.tangem.tap.features.wallet.ui.BalanceWidgetData
+import org.rekotlin.Action
+import java.math.BigDecimal
+
+class WalletReducer {
+ companion object {
+ fun reduce(action: Action, state: AppState): WalletState = internalReduce(action, state)
+ }
+}
+
+private fun internalReduce(action: Action, state: AppState): WalletState {
+
+ val multiWalletReducer = MultiWalletReducer()
+ val twinsReducer = TwinsReducer()
+ val onWalletLoadedReducer = OnWalletLoadedReducer()
+
+ if (action !is WalletAction) return state.walletState
+
+ var newState = state.walletState
+
+ when (action) {
+ is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState)
+ is WalletAction.TwinsAction -> newState = twinsReducer.reduce(action, newState)
+ is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState)
+
+ is WalletAction.ResetState -> newState = WalletState()
+ is WalletAction.EmptyWallet -> {
+ val creatingWalletAllowed = !(newState.twinCardsState != null &&
+ newState.twinCardsState?.isCreatingTwinCardsAllowed != true)
+
+ newState = newState.copy(
+ state = ProgressState.Done,
+ wallets = listOf(WalletData(
+ currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
+ mainButton = WalletMainButton.CreateWalletButton(creatingWalletAllowed),
+ topUpState = TopUpState(false)
+ )))
+ }
+ is WalletAction.LoadData.Failure -> {
+ when (action.error) {
+ is TapError.NoInternetConnection -> {
+ val wallets = newState.wallets
+ .map {
+ it.copy(currencyData = it.currencyData.copy(
+ status = BalanceStatus.Unreachable
+ ))
+ }
+ newState = newState.copy(
+ state = ProgressState.Error,
+ error = ErrorType.NoInternetConnection,
+ wallets = wallets
+ )
+ }
+ is TapError.UnknownBlockchain -> {
+ newState = newState.copy(
+ state = ProgressState.Done,
+ wallets = listOf(WalletData(
+ currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
+ topUpState = TopUpState(false)
+ ))
+ )
+ }
+ }
+ }
+
+ is WalletAction.LoadData -> {
+ newState = newState.copy(
+ state = ProgressState.Loading,
+ error = null,
+ )
+ }
+ is WalletAction.LoadWallet -> {
+ if (action.currency == null) {
+ val wallets = newState.wallets.map { wallet ->
+ wallet.copy(
+ currencyData = wallet.currencyData.copy(
+ status = BalanceStatus.Loading,
+ currency = wallet.currencyData.currency,
+ currencySymbol = wallet.currencyData.currencySymbol,
+ ),
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(allowed = action.allowTopUp
+ ?: wallet.topUpState.allowed)
+ )
+ }
+ newState = newState.copy(
+ state = ProgressState.Loading,
+ wallets = wallets
+ )
+ } else {
+ val walletManager = newState.getWalletManager(action.currency) ?: return newState
+ val currencies = listOf(walletManager.wallet.blockchain.currency) + walletManager.presetTokens.map { it.symbol }
+ val newWallets = newState.wallets.filter { currencies.contains(it.currencyData.currencySymbol) }
+ .map { wallet ->
+ wallet.copy(
+ currencyData = wallet.currencyData.copy(
+ status = BalanceStatus.Loading,
+ currency = wallet.currencyData.currency,
+ currencySymbol = wallet.currencyData.currencySymbol,
+ ),
+ mainButton = WalletMainButton.SendButton(false),
+ topUpState = TopUpState(
+ allowed = action.allowTopUp ?: wallet.topUpState.allowed
+ )
+ )
+ }
+ val wallets = newState.replaceSomeWallets(newWallets)
+ newState = newState.copy(wallets = wallets)
+ }
+ }
+ is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce(action.wallet, newState)
+ is WalletAction.UpdateWallet.Success -> {
+ newState = onWalletLoadedReducer.reduce(action.wallet, newState)
+ }
+ is WalletAction.LoadWallet.NoAccount -> {
+ val walletData = newState.getWalletData(action.wallet.blockchain.currency)?.copy(
+ currencyData = BalanceWidgetData(
+ BalanceStatus.NoAccount, action.wallet.blockchain.fullName,
+ currencySymbol = action.wallet.blockchain.currency,
+ amountToCreateAccount = action.amountToCreateAccount
+ )
+ )
+ val wallets = newState.replaceWalletInWallets(walletData)
+ val progressState = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
+ ProgressState.Loading
+ } else {
+ ProgressState.Done
+ }
+ newState = newState.copy(
+ state = progressState,
+ wallets = wallets
+ )
+ }
+ is WalletAction.LoadWallet.Failure -> {
+ val message = if (newState.error == ErrorType.NoInternetConnection) {
+ null
+ } else {
+ action.errorMessage
+ }
+ val walletData = newState.getWalletData(action.wallet.blockchain.currency)
+ val newWalletData = walletData?.copy(
+ currencyData = walletData.currencyData.copy(
+ status = BalanceStatus.Unreachable,
+ errorMessage = message
+ ),
+ topUpState = TopUpState(false)
+ )
+ val tokenWallets = action.wallet.getTokens()
+ .mapNotNull { newState.getWalletData(it.symbol) }
+ .map {
+ it.copy(currencyData = it.currencyData.copy(
+ status = BalanceStatus.Unreachable, errorMessage = message
+ ))
+ }
+ val wallets = newState.replaceSomeWallets(listOfNotNull(newWalletData) + tokenWallets)
+
+ val progressState = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
+ ProgressState.Loading
+ } else {
+ ProgressState.Done
+ }
+ newState = newState.copy(
+ state = progressState, wallets = wallets
+ )
+ }
+ is WalletAction.SetArtworkId -> {
+ val cardImage = if (newState.cardImage?.artworkId == action.artworkId) {
+ newState.cardImage
+ } else {
+ null
+ }
+ newState = newState.copy(cardImage = cardImage)
+ }
+
+ is WalletAction.LoadFiatRate.Success ->
+ newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState)
+ is WalletAction.LoadArtwork -> {
+ val cardId = action.card.cardId
+ val cardPublicKey = action.card.cardPublicKey?.toHexString()
+ val artworkUrl = if (cardPublicKey != null && action.artworkId != null) {
+ TangemService.getUrlForArtwork(cardId, cardPublicKey, action.artworkId)
+ } else if (action.card.cardId.startsWith(Artwork.SERGIO_CARD_ID)) {
+ Artwork.SERGIO_CARD_URL
+ } else if (action.card.cardId.startsWith(Artwork.MARTA_CARD_ID)) {
+ Artwork.MARTA_CARD_URL
+ } else if (newState.twinCardsState?.cardNumber != null) {
+ when (newState.twinCardsState?.cardNumber) {
+ TwinCardNumber.First -> Artwork.TWIN_CARD_1
+ TwinCardNumber.Second -> Artwork.TWIN_CARD_2
+ null -> Artwork.DEFAULT_IMG_URL
+ }
+ } else {
+ Artwork.DEFAULT_IMG_URL
+ }
+ newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl))
+ }
+ is WalletAction.ShowDialog.QrCode -> {
+ val selectedWalletData = newState.getWalletData(newState.selectedWallet)
+ newState = newState.copy(
+ walletDialog = WalletDialog.QrDialog(
+ selectedWalletData?.walletAddresses?.selectedAddress?.shareUrl?.toQrCode(),
+ selectedWalletData?.walletAddresses?.selectedAddress?.shareUrl,
+ selectedWalletData?.currencyData?.currency
+ )
+ )
+ }
+ is WalletAction.ShowDialog.ScanFails -> {
+ newState = newState.copy(walletDialog = WalletDialog.ScanFailsDialog)
+ }
+ 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.TopUpAction -> return newState
+ is WalletAction.ChangeSelectedAddress -> {
+ val selectedWalletData = newState.getWalletData(newState.selectedWallet)
+
+ val walletAddresses = newState.getWalletData(selectedWalletData?.currencyData?.currencySymbol)?.walletAddresses
+ ?: return newState
+ val address = walletAddresses.list.firstOrNull { it.type == action.type }
+ ?: return newState
+ val wallets = newState.replaceWalletInWallets(
+ selectedWalletData?.copy(walletAddresses = WalletAddresses(address, walletAddresses.list))
+ )
+ newState = newState.copy(wallets = wallets)
+ }
+ }
+ return newState
+}
+
+fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? {
+ if (wallet == null) return null
+
+ val listOfAddressData = mutableListOf()
+ // put a defaultAddress at the first place
+ wallet.addresses.forEach {
+ val addressData = AddressData(it.value, it.type, wallet.getShareUri(it.value), wallet.getExploreUrl(it.value))
+ if (it.type == wallet.blockchain.defaultAddressType()) {
+ listOfAddressData.add(0, addressData)
+ } else {
+ listOfAddressData.add(addressData)
+ }
+ }
+
+ // restore a selected wallet address
+ var indexOfSelectedWallet = 0
+ walletAddresses?.let {
+ val index = listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address }
+ if (index != -1) indexOfSelectedWallet = index
+ }
+ return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData)
+}
+
+private fun handleCheckSignedHashesActions(action: WalletAction.Warnings, state: WalletState): WalletState {
+ return when (action) {
+ WalletAction.Warnings.CheckHashesCount.ConfirmHashesCount -> state.copy(hashesCountVerified = true)
+ WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline -> state.copy(hashesCountVerified = false)
+ is WalletAction.Warnings.Set -> state.copy(mainWarningsList = action.warningList)
+ else -> state
+ }
+}
+
+
+private fun setNewFiatRate(
+ fiatRate: Pair,
+ appCurrency: FiatCurrencyName, state: WalletState
+): WalletState {
+ val rate = fiatRate.second ?: return state
+ val rateFormatted = rate.toFormattedCurrencyString(2, appCurrency)
+ val currency = fiatRate.first
+
+ if (!state.isMultiwalletAllowed) {
+ return setSingeWalletFiatRate(rate, rateFormatted, currency, appCurrency, state)
+ } else {
+ return setMultiWalletFiatRate(rate, rateFormatted, currency, appCurrency, state)
+ }
+}
+
+private fun setMultiWalletFiatRate(
+ rate: BigDecimal, rateFormatted: String, currency: CryptoCurrencyName,
+ appCurrency: FiatCurrencyName, state: WalletState
+): WalletState {
+ val walletData = state.getWalletData(currency) ?: return state
+ val wallet = state.walletManagers.find { it.wallet.blockchain.currency == currency }?.wallet
+ if (wallet != null) {
+ val newWalletData = state.getWalletData(currency)?.copy(
+ currencyData = walletData.currencyData.copy(
+ fiatAmount = wallet.amounts[AmountType.Coin]?.value
+ ?.toFiatString(rate, appCurrency)),
+ fiatRate = rate, fiatRateString = rateFormatted
+ )
+ return state.copy(wallets = state.replaceWalletInWallets(newWalletData))
+ } else {
+ val ethereumWallet = state.walletManagers.find { it.wallet.blockchain == Blockchain.Ethereum }?.wallet
+ val token = ethereumWallet?.getTokens()?.find { it.symbol == currency } ?: return state
+ val tokenFiatAmount = ethereumWallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency)
+ val newWalletData = state.getWalletData(currency)?.copy(
+ currencyData = walletData.currencyData.copy(
+ fiatAmount = tokenFiatAmount),
+ fiatRate = rate, fiatRateString = rateFormatted
+ )
+ return state.copy(wallets = state.replaceWalletInWallets(newWalletData))
+ }
+}
+
+private fun setSingeWalletFiatRate(
+ rate: BigDecimal, rateFormatted: String, currency: CryptoCurrencyName,
+ appCurrency: FiatCurrencyName, state: WalletState
+): WalletState {
+ val wallet = state.walletManagers[0].wallet
+ val token = wallet.getFirstToken()
+
+ if (currency == state.primaryWallet?.currencyData?.currencySymbol) {
+ val fiatAmount = wallet.amounts[AmountType.Coin]?.value
+ ?.toFiatString(rate, appCurrency)
+ val walletData = state.primaryWallet.copy(
+ currencyData = state.primaryWallet.currencyData.copy(fiatAmount = fiatAmount),
+ fiatRate = rate,
+ fiatRateString = rateFormatted
+ )
+ return state.copy(wallets = listOf(walletData))
+ } else if (currency == token?.symbol) {
+ val tokenFiatAmount = wallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency)
+ val tokenData = state.primaryWallet?.currencyData?.token?.copy(
+ fiatAmount = tokenFiatAmount,
+ fiatRate = rate,
+ fiatRateString = rateFormatted
+ )
+
+ val walletData = state.primaryWallet?.copy(
+ currencyData = state.primaryWallet.currencyData.copy(
+ token = tokenData
+ ))
+ val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
+ return state.copy(wallets = wallets)
+ }
+ return state
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt
index ce7fec6959..508a62b08c 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt
@@ -8,10 +8,12 @@ import com.tangem.wallet.R
import kotlinx.android.synthetic.main.card_balance.*
import kotlinx.android.synthetic.main.layout_balance.*
import kotlinx.android.synthetic.main.layout_balance_error.*
+import java.math.BigDecimal
enum class BalanceStatus {
VerifiedOnline,
TransactionInProgress,
+ SameCurrencyTransactionInProgress,
Unreachable,
Loading,
NoAccount,
@@ -25,6 +27,7 @@ data class BalanceWidgetData(
val currencySymbol: String? = null,
val amount: String? = null,
val fiatAmount: String? = null,
+ val fiatAmountRaw: BigDecimal? = null,
val token: TokenData? = null,
val amountToCreateAccount: String? = null,
val errorMessage: String? = null
@@ -33,14 +36,16 @@ data class BalanceWidgetData(
data class TokenData(
val amount: String,
val tokenSymbol: String,
- val fiatAmount: String? = null
+ val fiatAmount: String? = null,
+ val fiatRateString: String? = null,
+ val fiatRate: BigDecimal? = null,
)
class BalanceWidget(
val fragment: Fragment,
val data: BalanceWidgetData,
- val isTwinCard: Boolean,
+ private val isTwinCard: Boolean,
) {
fun setup() {
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt
new file mode 100644
index 0000000000..66425c74a7
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt
@@ -0,0 +1,42 @@
+package com.tangem.tap.features.wallet.ui
+
+import android.view.View
+import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
+import com.tangem.blockchain.blockchains.cardano.CardanoAddressType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.address.AddressType
+import com.tangem.wallet.R
+
+class MultipleAddressUiHelper {
+ companion object {
+ fun typeToId(type: AddressType): Int {
+ return when (type) {
+ is BitcoinAddressType.Legacy -> R.id.chip_legacy
+ is BitcoinAddressType.Segwit -> R.id.chip_default
+ is CardanoAddressType.Byron -> R.id.chip_legacy
+ is CardanoAddressType.Shelley -> R.id.chip_default
+ else -> View.NO_ID
+ }
+ }
+
+ fun idToType(id: Int, blockchain: Blockchain?): AddressType? {
+ return when (id) {
+ R.id.chip_default -> {
+ when (blockchain) {
+ Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> BitcoinAddressType.Segwit
+ Blockchain.CardanoShelley -> CardanoAddressType.Shelley
+ else -> null
+ }
+ }
+ R.id.chip_legacy -> {
+ when (blockchain) {
+ Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> BitcoinAddressType.Legacy
+ Blockchain.CardanoShelley -> CardanoAddressType.Byron
+ else -> null
+ }
+ }
+ else -> null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
new file mode 100644
index 0000000000..5b59b03015
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
@@ -0,0 +1,303 @@
+package com.tangem.tap.features.wallet.ui
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.*
+import androidx.activity.OnBackPressedCallback
+import androidx.annotation.IdRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.transition.TransitionInflater
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tap.MainActivity
+import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.redux.global.StateDialog
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.wallet.models.PendingTransaction
+import com.tangem.tap.features.wallet.redux.*
+import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
+import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.fragment_details_twin_cards.*
+import kotlinx.android.synthetic.main.fragment_wallet_details.*
+import kotlinx.android.synthetic.main.fragment_wallet_details.toolbar
+import kotlinx.android.synthetic.main.item_currency_wallet.view.*
+import kotlinx.android.synthetic.main.layout_balance_error.*
+import kotlinx.android.synthetic.main.layout_balance_wallet_details.*
+import kotlinx.android.synthetic.main.layout_wallet_details.*
+import org.rekotlin.StoreSubscriber
+
+class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreSubscriber {
+
+ private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
+ private var dialog: Dialog? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
+ store.dispatch(NavigationAction.PopBackTo())
+ }
+ })
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(R.transition.slide_right)
+ exitTransition = inflater.inflateTransition(R.transition.fade)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.walletState == newState.walletState
+ }.select { it.walletState }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ (activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
+ toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
+
+
+ setupTransactionsRecyclerView()
+ setupButtons()
+ }
+
+
+ private fun setupTransactionsRecyclerView() {
+ pendingTransactionAdapter = PendingTransactionsAdapter()
+ rv_pending_transaction.layoutManager = LinearLayoutManager(requireContext())
+ rv_pending_transaction.adapter = pendingTransactionAdapter
+ }
+
+ private fun setupButtons() {
+ btn_confirm.text = getString(R.string.wallet_button_send)
+ btn_confirm.setOnClickListener { store.dispatch(WalletAction.Send()) }
+
+ btn_share.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
+
+ btn_top_up.setOnClickListener {
+ store.dispatch(
+ WalletAction.TopUpAction.TopUp(requireContext(), R.color.backgroundLightGray)
+ )
+ }
+ }
+
+ override fun newState(state: WalletState) {
+ if (activity == null) return
+ if (state.selectedWallet == null) return
+ val selectedWallet = state.getSelectedWalletData() ?: return
+
+ tv_currency_title.text = selectedWallet.currencyData.currency
+
+ showPendingTransactionsIfPresent(selectedWallet.pendingTransactions)
+ setupAddressCard(selectedWallet)
+ setupNoInternetHandling(state)
+ setupBalanceData(selectedWallet.currencyData)
+
+ btn_confirm.isEnabled = selectedWallet.mainButton.enabled
+ btn_copy.setOnClickListener {
+ selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
+ store.dispatch(WalletAction.CopyAddress(addressString, requireContext()))
+ }
+ }
+ btn_share.setOnClickListener {
+ selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
+ store.dispatch(WalletAction.ShareAddress(addressString, requireContext()))
+ }
+ }
+
+ handleDialogs(state.walletDialog)
+ handleCurrencyIcon(selectedWallet)
+
+ srl_wallet_details.setOnRefreshListener {
+ if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
+ store.dispatch(WalletAction.LoadWallet(
+ selectedWallet.topUpState.allowed,
+ selectedWallet.currencyData.currencySymbol
+ ))
+ }
+ }
+
+ if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
+ srl_wallet_details.isRefreshing = false
+ }
+ }
+
+ private fun handleCurrencyIcon(wallet: WalletData) {
+ val blockchain = wallet.currencyData.currencySymbol?.let { Blockchain.fromCurrency(it) }
+ if (blockchain != null && blockchain != Blockchain.Unknown) {
+ tv_token_letter.text = null
+ iv_currency.colorFilter = null
+ iv_currency.setImageResource(blockchain.getIconRes())
+ } else {
+ tv_token_letter.text = wallet.currencyData.currencySymbol?.take(1)
+ wallet.token?.getColor()?.let { iv_currency.setColorFilter(it) }
+ iv_currency.setImageResource(R.drawable.shape_circle)
+ }
+ }
+
+
+ private fun showPendingTransactionsIfPresent(pendingTransactions: List) {
+ pendingTransactionAdapter.submitList(pendingTransactions)
+ rv_pending_transaction?.show(pendingTransactions.isNotEmpty())
+ }
+
+ private fun setupAddressCard(state: WalletData) {
+ if (state.walletAddresses != null) {
+ if (state.shouldShowMultipleAddress() && state.blockchain != null) {
+ (card_balance as? ViewGroup)?.beginDelayedTransition()
+ chip_group_address_type.show()
+ chip_group_address_type.fitChipsByGroupWidth()
+
+ val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
+ if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
+
+ chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
+ if (checkedId == -1) return@setOnCheckedChangeListener
+ val type = MultipleAddressUiHelper.idToType(checkedId, state.blockchain)
+ type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
+ }
+ } else {
+ chip_group_address_type.hide()
+ }
+ tv_address.text = state.walletAddresses.selectedAddress.address
+ tv_explore?.setOnClickListener {
+ store.dispatch(WalletAction.ExploreAddress(
+ state.walletAddresses.selectedAddress.exploreUrl,
+ requireContext()))
+ }
+ iv_qr_code.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode())
+ }
+ }
+
+ private fun setupNoInternetHandling(state: WalletState) {
+ if (state.state == ProgressState.Error) {
+ if (state.error == ErrorType.NoInternetConnection) {
+ srl_wallet_details?.isRefreshing = false
+ (activity as? MainActivity)?.showSnackbar(
+ text = R.string.wallet_notification_no_internet,
+ buttonTitle = R.string.common_retry
+ ) { store.dispatch(WalletAction.LoadData) }
+ }
+ } else {
+ (activity as? MainActivity)?.dismissSnackbar()
+ }
+ }
+
+ private fun setupBalanceData(data: BalanceWidgetData) {
+ when (data.status) {
+ BalanceStatus.Loading -> {
+ l_balance.show()
+ l_balance_error.hide()
+ tv_fiat_amount.hide()
+ tv_amount.text = ""
+
+ showStatus(R.id.tv_status_loading)
+ showBalanceWithoutToken(data, false)
+ }
+ BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress,
+ BalanceStatus.TransactionInProgress -> {
+ l_balance.show()
+ l_balance_error.hide()
+ val statusView = if (data.status == BalanceStatus.VerifiedOnline ||
+ data.status == BalanceStatus.SameCurrencyTransactionInProgress) {
+ R.id.tv_status_verified
+ } else {
+ tv_status_error.text =
+ getText(R.string.wallet_balance_tx_in_progress)
+ R.id.group_error
+ }
+ showStatus(statusView)
+ tv_status_error_message.hide()
+ showBalanceWithoutToken(data, true)
+ }
+ BalanceStatus.Unreachable -> {
+ l_balance.show()
+ l_balance_error.hide()
+ tv_fiat_amount.hide()
+
+ tv_amount.text = ""
+
+ tv_status_error_message.text = data.errorMessage
+ tv_status_error.text =
+ getString(R.string.wallet_balance_blockchain_unreachable)
+
+ showStatus(R.id.group_error)
+ tv_status_error_message.show(!data.errorMessage.isNullOrBlank())
+ }
+
+ BalanceStatus.NoAccount -> {
+ l_balance.hide()
+ l_balance_error.show()
+ tv_error_title.text = getText(R.string.wallet_error_no_account)
+ tv_error_descriptions.text =
+ getString(
+ R.string.wallet_error_no_account_subtitle_format,
+ data.amountToCreateAccount, data.currencySymbol
+ )
+ }
+ }
+ card_pending_transaction_warning.show(data.status == BalanceStatus.SameCurrencyTransactionInProgress)
+ }
+
+ private fun showStatus(@IdRes viewRes: Int) {
+ group_error.show(viewRes == R.id.group_error)
+ tv_status_loading.show(viewRes == R.id.tv_status_loading)
+ tv_status_verified.show(viewRes == R.id.tv_status_verified)
+ }
+
+ private fun showBalanceWithoutToken(data: BalanceWidgetData, showAmount: Boolean) {
+ tv_amount.text = if (showAmount) data.amount else ""
+ if (showAmount) {
+ tv_fiat_amount.show()
+ tv_fiat_amount.text = data.fiatAmount
+ }
+ }
+
+ 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.meny_remove -> {
+ store.state.walletState.getSelectedWalletData()?.let { walletData ->
+ store.dispatch(WalletAction.MultiWallet.RemoveWallet(walletData))
+ store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
+ store.dispatch(NavigationAction.PopBackTo())
+ true
+ }
+ false
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ if (store.state.walletState.canBeRemoved(store.state.walletState.getSelectedWalletData())) {
+ inflater.inflate(R.menu.wallet_details, menu)
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
index eedcf10ca5..580bb34141 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
@@ -1,48 +1,45 @@
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.Menu.NONE
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionInflater
-import com.google.android.material.snackbar.Snackbar
import com.squareup.picasso.Picasso
-import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
-import com.tangem.blockchain.blockchains.cardano.CardanoAddressType
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.address.AddressType
-import com.tangem.tap.common.extensions.*
+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.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.termsOfUse.CardTou
-import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.wallet.redux.*
-import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
-import com.tangem.tap.features.wallet.ui.dialogs.PayIdDialog
-import com.tangem.tap.features.wallet.ui.dialogs.QrDialog
-import com.tangem.tap.features.wallet.ui.dialogs.WarningDialog
+import com.tangem.tap.features.wallet.ui.adapters.SpacesItemDecoration
+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
+import com.tangem.tap.features.wallet.ui.wallet.WalletView
import com.tangem.tap.store
import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.card_balance.*
import kotlinx.android.synthetic.main.fragment_wallet.*
-import kotlinx.android.synthetic.main.layout_address.*
-import kotlinx.android.synthetic.main.layout_send_fee.*
-import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
-import kotlinx.android.synthetic.main.layout_wallet_short_buttons.*
+import kotlinx.android.synthetic.main.fragment_wallet.toolbar
+import kotlinx.android.synthetic.main.fragment_wallet_details.*
import org.rekotlin.StoreSubscriber
class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber {
- private var dialog: Dialog? = null
- private var snackbar: Snackbar? = null
+ private lateinit var warningsAdapter: WarningMessagesAdapter
- private lateinit var viewAdapter: PendingTransactionsAdapter
+ private var walletView: WalletView = SingleWalletView()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -64,12 +61,14 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber
- tv_twin_card_number.show()
- iv_twin_card.show()
- val number = when (cardNumber) {
- TwinCardNumber.First -> "1"
- TwinCardNumber.Second -> "2"
- }
- tv_twin_card_number.text = getString(R.string.wallet_twins_chip_format, number)
- }
- if (state.twinCardsState?.cardNumber == null) {
- tv_twin_card_number.hide()
- iv_twin_card.hide()
+ if (state.isMultiwalletAllowed && walletView is SingleWalletView) {
+ walletView = MultiWalletView()
+ walletView.changeWalletView(this)
+ } else if (!state.isMultiwalletAllowed && walletView is MultiWalletView) {
+ walletView = SingleWalletView()
+ walletView.changeWalletView(this)
}
+ walletView.onNewState(state)
- if (!state.showDetails) {
+ if (!state.shouldShowDetails) {
toolbar.menu.removeItem(R.id.details_menu)
} else if (toolbar.menu.findItem(R.id.details_menu) == null) {
toolbar.menu.add(R.menu.wallet, R.id.details_menu, NONE, R.string.details_title)
}
- srl_wallet.setOnRefreshListener {
- store.dispatch(WalletAction.LoadData)
- }
-
setupNoInternetHandling(state)
-
- setupButtons(state)
- setupAddressCard(state)
setupCardImage(state.cardImage)
- viewAdapter.submitList(state.pendingTransactions)
- if (state.pendingTransactions.isEmpty()) {
- rv_pending_transaction.hide()
- } else {
- rv_pending_transaction.show()
+ showWarningsIfPresent(state.mainWarningsList)
+
+ srl_wallet.setOnRefreshListener {
+ if (state.state != ProgressState.Loading) {
+ store.dispatch(WalletAction.LoadData)
+ }
}
- handleDialogs(state.walletDialog)
-
- l_balance.show()
- BalanceWidget(this, state.currencyData, state.twinCardsState != null).setup()
-
- if (state.state == ProgressState.Done) {
+ if (state.state != ProgressState.Loading) {
srl_wallet.isRefreshing = false
}
+ }
- when (state.payIdData.payIdState) {
- PayIdState.Disabled, PayIdState.Loading -> group_payid.hide()
- PayIdState.NotCreated -> {
- group_payid.show()
- tv_create_payid.show()
- tv_create_payid.setOnClickListener { store.dispatch(WalletAction.CreatePayId) }
- tv_payid_address.hide()
- }
- PayIdState.Created -> {
- group_payid.show()
- tv_create_payid.hide()
- tv_payid_address.show()
- tv_payid_address.text = state.payIdData.payId
-
- }
- PayIdState.ErrorLoading -> {
- }
- }
+ private fun showWarningsIfPresent(warnings: List) {
+ warningsAdapter.submitList(warnings)
+ rv_warning_messages.show(warnings.isNotEmpty())
}
private fun setupNoInternetHandling(state: WalletState) {
if (state.state == ProgressState.Error) {
if (state.error == ErrorType.NoInternetConnection) {
- srl_wallet?.isRefreshing = false
- snackbar = Snackbar.make(
- coordinator_wallet, getString(R.string.wallet_notification_no_internet),
- Snackbar.LENGTH_INDEFINITE
- ).setAction(getString(R.string.common_retry))
- { store.dispatch(WalletAction.LoadData) }
- .also { it.show() }
+ srl_wallet_details?.isRefreshing = false
+ (activity as? MainActivity)?.showSnackbar(
+ text = R.string.wallet_notification_no_internet,
+ buttonTitle = R.string.common_retry
+ ) { store.dispatch(WalletAction.LoadData) }
}
} else {
- snackbar?.dismiss()
- }
- }
-
- private fun setupButtons(state: WalletState) {
- if (state.topUpState.allowed) {
- l_buttons_long.hide()
- l_buttons_short.show()
- } else {
- l_buttons_long.show()
- l_buttons_short.hide()
- }
-
- val btnConfirm = if (state.topUpState.allowed) {
- btn_confirm_short
- } else {
- btn_confirm_long
- }
- val btnScan = if (state.topUpState.allowed) {
- btn_scan_short
- } else {
- btn_scan_long
- }
-
- btnScan.setOnClickListener {
- store.dispatch(WalletAction.Scan)
- }
-
- btn_copy.setOnClickListener { store.dispatch(WalletAction.CopyAddress(requireContext())) }
- btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowQrCode) }
-
- val buttonTitle = when (state.mainButton) {
- is WalletMainButton.SendButton -> R.string.wallet_button_send
- is WalletMainButton.CreateWalletButton -> {
- if (state.twinCardsState == null) {
- R.string.wallet_button_create_wallet
- } else {
- R.string.wallet_button_create_twin_wallet
- }
- }
- }
- btnConfirm.text = getString(buttonTitle)
- btnConfirm.isEnabled = state.mainButton.enabled
-
- btnConfirm.setOnClickListener {
- when (state.mainButton) {
- is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send())
- is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet)
- }
- }
- btn_top_up.setOnClickListener {
- store.dispatch(
- WalletAction.TopUpAction.TopUp(requireContext(), R.color.backgroundLightGray)
- )
- }
- }
-
- private fun setupAddressCard(state: WalletState) {
- if (state.walletAddresses != null) {
- l_address?.show()
- if (state.showMultipleAddress) {
- (l_address as? ViewGroup)?.beginDelayedTransition()
- chip_group_address_type.show()
- chip_group_address_type.fitChipsByGroupWidth()
-
- val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
- if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
-
- chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
- if (checkedId == -1) return@setOnCheckedChangeListener
-
- MultipleAddressUiHelper.idToType(checkedId, state.wallet?.blockchain)?.let {
- store.dispatch(WalletAction.ChangeSelectedAddress(it))
- }
- }
- } else {
- chip_group_address_type.hide()
- }
- tv_address.text = state.walletAddresses.selectedAddress.address
- tv_explore?.setOnClickListener {
- store.dispatch(WalletAction.ExploreAddress(requireContext()))
- }
- } else {
- l_address?.hide()
+ (activity as? MainActivity)?.dismissSnackbar()
}
}
@@ -266,52 +152,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber {
- if (walletDialog.qrCode != null && walletDialog.shareUrl != null) {
- if (dialog == null) dialog = QrDialog(requireContext()).apply {
- this.showQr(
- walletDialog.qrCode, walletDialog.shareUrl, walletDialog.currencyName
- )
- }
- }
- }
- is WalletDialog.CreatePayIdDialog -> {
- when (walletDialog.creatingPayIdState) {
- CreatingPayIdState.EnterPayId -> {
- if (dialog == null) dialog = PayIdDialog(requireContext()).apply {
- this.show()
- }
- (dialog as? PayIdDialog)?.stopProgress()
- }
- CreatingPayIdState.Waiting -> (dialog as? PayIdDialog)?.showProgress()
- }
- }
- is WalletDialog.SelectAmountToSendDialog -> {
- if (dialog == null) dialog = AmountToSendDialog(requireContext()).apply {
- this.show(walletDialog.amounts)
- }
- }
- is WalletDialog.WarningDialog -> {
- if (dialog == null) dialog = WarningDialog(requireContext()).apply {
- this.show(walletDialog.type)
- }
- }
- null -> {
- dialog?.dismiss()
- dialog = null
- }
- }
- }
-
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.details_menu -> {
store.state.globalState.scanNoteResponse?.let { scanNoteResponse ->
store.dispatch(DetailsAction.PrepareScreen(
scanNoteResponse.card, scanNoteResponse,
- store.state.walletState.wallet,
+ store.state.walletState.walletManagers.map { it.wallet },
store.state.globalState.configManager?.config?.isCreatingTwinCardsAllowed,
CardTou(),
store.state.globalState.appCurrency
@@ -326,41 +173,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber R.id.chip_legacy
- is BitcoinAddressType.Segwit -> R.id.chip_default
- is CardanoAddressType.Byron -> R.id.chip_legacy
- is CardanoAddressType.Shelley -> R.id.chip_default
- else -> View.NO_ID
- }
- }
-
- fun idToType(id: Int, blockchain: Blockchain?): AddressType? {
- return when (id) {
- R.id.chip_default -> {
- when (blockchain) {
- Blockchain.Bitcoin -> BitcoinAddressType.Segwit
- Blockchain.CardanoShelley -> CardanoAddressType.Shelley
- else -> null
- }
- }
- R.id.chip_legacy -> {
- when (blockchain) {
- Blockchain.Bitcoin -> BitcoinAddressType.Legacy
- Blockchain.CardanoShelley -> CardanoAddressType.Byron
- else -> null
- }
- }
- else -> null
- }
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/PendingTransactionsAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/PendingTransactionsAdapter.kt
similarity index 98%
rename from app/src/main/java/com/tangem/tap/features/wallet/ui/PendingTransactionsAdapter.kt
rename to app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/PendingTransactionsAdapter.kt
index 93e0202458..09c6094d38 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/PendingTransactionsAdapter.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/PendingTransactionsAdapter.kt
@@ -1,4 +1,4 @@
-package com.tangem.tap.features.wallet.ui
+package com.tangem.tap.features.wallet.ui.adapters
import android.view.LayoutInflater
import android.view.View
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt
new file mode 100644
index 0000000000..c14d364abf
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt
@@ -0,0 +1,136 @@
+package com.tangem.tap.features.wallet.ui.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.tap.common.extensions.getColor
+import com.tangem.tap.common.extensions.getIconRes
+import com.tangem.tap.common.extensions.show
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletData
+import com.tangem.tap.features.wallet.ui.BalanceStatus
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.item_currency_wallet.view.*
+import java.math.BigDecimal
+
+class WalletAdapter
+ : ListAdapter(DiffUtilCallback) {
+
+ override fun getItemId(position: Int): Long {
+ return currentList[position].currencyData.currencySymbol?.hashCode()?.toLong() ?: 0
+ }
+
+ fun submitList(list: List, primaryBlockchain: Blockchain?, primaryToken: Token? = null) {
+ val listModified = list.toMutableList()
+ val primaryBlockchainWallet = when (
+ val index = listModified.indexOfFirst { it.blockchain == primaryBlockchain }
+ ) {
+ -1 -> null
+ else -> listModified.removeAt(index)
+ }
+
+
+ val primaryTokenWallet = if (primaryToken == null) null else when (
+ val index = listModified.indexOfFirst { it.token == primaryToken }
+ ) {
+ -1 -> null
+ else -> listModified.removeAt(index)
+ }
+
+ if (list.all { it.currencyData.fiatAmount == null }) {
+ val sortedList = listOfNotNull(primaryBlockchainWallet, primaryTokenWallet) + listModified
+ super.submitList(sortedList)
+ return
+ }
+
+ val sorted = listModified.sortedWith(
+ compareByDescending { it.currencyData.fiatAmountRaw ?: BigDecimal.ZERO }
+ .thenBy { it.currencyData.currencySymbol }
+ )
+ val sortedList = listOfNotNull(primaryBlockchainWallet, primaryTokenWallet) + sorted
+ super.submitList(sortedList)
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder {
+ val layout = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_currency_wallet, parent, false)
+ return WalletsViewHolder(layout)
+ }
+
+ override fun onBindViewHolder(holder: WalletsViewHolder, position: Int) {
+ holder.bind(currentList[position])
+ }
+
+ object DiffUtilCallback : DiffUtil.ItemCallback() {
+ override fun areContentsTheSame(
+ oldItem: WalletData, newItem: WalletData
+ ) = oldItem == newItem
+
+ override fun areItemsTheSame(
+ oldItem: WalletData, newItem: WalletData
+ ) = oldItem == newItem
+ }
+
+ class WalletsViewHolder(val view: View) :
+ RecyclerView.ViewHolder(view) {
+
+ fun bind(wallet: WalletData) {
+ view.tv_currency.text = wallet.currencyData.currency
+ view.tv_amount.text = wallet.currencyData.amount?.takeWhile { !it.isWhitespace() }
+ view.tv_currency_symbol.text = wallet.currencyData.amount?.takeLastWhile { !it.isWhitespace() }
+ view.tv_amount_fiat.text = wallet.currencyData.fiatAmount
+ view.tv_exchange_rate.text = wallet.fiatRateString
+ view.card_wallet.setOnClickListener {
+ store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
+ }
+ val blockchain = wallet.currencyData.currencySymbol?.let { Blockchain.fromCurrency(it) }
+ if (blockchain != null && blockchain != Blockchain.Unknown) {
+ view.tv_token_letter.text = null
+ view.iv_currency.colorFilter = null
+ view.iv_currency.setImageResource(blockchain.getIconRes())
+ } else {
+ view.tv_token_letter.text = wallet.currencyData.currencySymbol?.take(1)
+ wallet.token?.getColor()?.let { view.iv_currency.setColorFilter(it) }
+ view.iv_currency.setImageResource(R.drawable.shape_circle)
+ }
+ when (wallet.currencyData.status) {
+ BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress -> hideWarning()
+ BalanceStatus.Loading -> {
+ hideWarning()
+ if (wallet.currencyData.amount == null) {
+ view.tv_exchange_rate.text = view.context.getText(R.string.wallet_balance_loading)
+ }
+ }
+ BalanceStatus.TransactionInProgress ->
+ showWarning(view.context.getString(R.string.wallet_balance_tx_in_progress))
+ BalanceStatus.Unreachable ->
+ showWarning(view.context.getString(R.string.wallet_balance_blockchain_unreachable))
+
+ BalanceStatus.NoAccount ->
+ showWarning(view.context.getString(R.string.wallet_error_no_account))
+ else -> {
+ }
+ }
+ }
+
+ private fun showWarning(message: String) {
+ toggleWarning(true)
+ view.tv_status_error_message.text = message
+ }
+
+ private fun hideWarning() {
+ toggleWarning(false)
+ }
+
+ private fun toggleWarning(show: Boolean) {
+ view.tv_exchange_rate.show(!show)
+ view.tv_status_error_message.show(show)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
new file mode 100644
index 0000000000..a15a4e3e9a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
@@ -0,0 +1,133 @@
+package com.tangem.tap.features.wallet.ui.adapters
+
+import android.graphics.Rect
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+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.analytics.FirebaseAnalyticsHandler
+import com.tangem.tap.common.extensions.getActivity
+import com.tangem.tap.common.extensions.getString
+import com.tangem.tap.common.extensions.hide
+import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.features.feedback.RateCanBeBetterEmail
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.layout_warning.view.*
+import timber.log.Timber
+
+class WarningMessagesAdapter : ListAdapter(DiffUtilCallback) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WarningMessageVH {
+ val inflater = LayoutInflater.from(parent.context)
+ val layout = inflater.inflate(R.layout.layout_warning, parent, false)
+ return WarningMessageVH(layout)
+ }
+
+ override fun onBindViewHolder(holder: WarningMessageVH, position: Int) {
+ holder.bind(currentList[position])
+ }
+
+ object DiffUtilCallback : DiffUtil.ItemCallback() {
+ override fun areContentsTheSame(oldItem: WarningMessage, newItem: WarningMessage) = oldItem == newItem
+
+ override fun areItemsTheSame(oldItem: WarningMessage, newItem: WarningMessage) = oldItem == newItem
+ }
+}
+
+class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
+
+ fun bind(warning: WarningMessage) {
+ setBgColor(warning.priority)
+ setText(warning)
+ setupControlButtons(warning)
+ }
+
+ private fun setText(warning: WarningMessage) {
+ fun getString(resId: Int?, default: String) = if (resId == null) default else view.getString(resId)
+
+ view.tv_title.text = getString(warning.titleResId, warning.title)
+ view.tv_message.text = getString(warning.messageResId, warning.message)
+ }
+
+ private fun setBgColor(priority: WarningMessage.Priority) {
+ val color = when (priority) {
+ WarningMessage.Priority.Info -> R.color.warning_info
+ WarningMessage.Priority.Warning -> R.color.warning_warning
+ WarningMessage.Priority.Critical -> R.color.warning_critical
+ }
+ view.card_view.setCardBackgroundColor(view.context.resources.getColor(color))
+ }
+
+ private fun setupControlButtons(warning: WarningMessage) {
+ when (warning.type) {
+ WarningMessage.Type.Permanent -> {
+ view.group_controls_temporary.hide()
+ view.group_controls_rating.hide()
+ }
+ WarningMessage.Type.Temporary -> {
+ view.group_controls_rating.hide()
+ view.group_controls_temporary.show()
+ view.btn_got_it.setOnClickListener { store.dispatch(GlobalAction.HideWarningMessage(warning)) }
+ }
+ WarningMessage.Type.AppRating -> {
+ view.group_controls_temporary.hide()
+ view.group_controls_rating.show()
+ view.btn_close.setOnClickListener {
+ FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS)
+ store.dispatch(GlobalAction.HideWarningMessage(warning))
+ store.dispatch(WalletAction.Warnings.AppRating.RemindLater)
+ }
+ view.btn_can_be_better.setOnClickListener {
+ FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
+ store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
+ store.dispatch(GlobalAction.HideWarningMessage(warning))
+ store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail()))
+ }
+ view.btn_really_cool.setOnClickListener {
+ val activity = view.context.getActivity() ?: return@setOnClickListener
+
+ FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
+ store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
+ val reviewManager = ReviewManagerFactory.create(activity)
+ val task = reviewManager.requestReviewFlow()
+ task.addOnCompleteListener {
+ if (it.isSuccessful) {
+ val reviewFlow = reviewManager.launchReviewFlow(activity, it.result)
+ reviewFlow.addOnCompleteListener {
+ if (it.isSuccessful) {
+ // send review was succeed
+ } else {
+ // send fails
+ }
+ }
+ } else {
+ Timber.e(task.exception)
+ }
+ }.addOnFailureListener {
+ Timber.e(it)
+ }
+ store.dispatch(GlobalAction.HideWarningMessage(warning))
+ }
+ }
+ }
+ }
+}
+
+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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/PayIdDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/PayIdDialog.kt
deleted file mode 100644
index dd1944ac74..0000000000
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/PayIdDialog.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.tangem.tap.features.wallet.ui.dialogs
-
-import android.app.Dialog
-import android.content.Context
-import android.view.View
-import com.tangem.tap.common.extensions.hide
-import com.tangem.tap.common.extensions.show
-import com.tangem.tap.features.wallet.redux.WalletAction
-import com.tangem.tap.notificationsHandler
-import com.tangem.tap.store
-import com.tangem.wallet.R
-import kotlinx.android.synthetic.main.dialog_create_payid.*
-
-class PayIdDialog(context: Context) : Dialog(context) {
-
- init {
- this.setContentView(R.layout.dialog_create_payid)
- }
-
- override fun show() {
- notificationsHandler?.replaceBaseLayout(this.cl_payid_dialog)
- this.setOnDismissListener {
- notificationsHandler?.returnBaseLayout()
- store.dispatch(WalletAction.CreatePayId.Cancel)
- }
- this.btn_create_payid?.setOnClickListener {
- if (this.et_payid.text.isNullOrBlank()) {
- store.dispatch(WalletAction.CreatePayId.EmptyField)
- } else {
- val payid = this.et_payid.text!!.toString() +
- this.context.getString(R.string.wallet_create_payid_domain)
- store.dispatch(WalletAction.CreatePayId.CompleteCreatingPayId(payid))
- }
- }
- super.show()
- }
-
- fun showProgress() {
- this.btn_create_payid?.visibility = View.INVISIBLE
- this.pb_create_payid?.show()
- }
-
- fun stopProgress() {
- this.btn_create_payid?.show()
- this.pb_create_payid?.hide()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt
new file mode 100644
index 0000000000..b3f63a0236
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt
@@ -0,0 +1,34 @@
+package com.tangem.tap.features.wallet.ui.dialogs
+
+import android.content.Context
+import androidx.appcompat.app.AlertDialog
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.features.feedback.ScanFailsEmail
+import com.tangem.tap.features.home.redux.HomeAction
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+class ScanFailsDialog {
+
+ companion object {
+ fun create(context: Context): AlertDialog {
+ return AlertDialog.Builder(context).apply {
+ setTitle(context.getString(R.string.common_warning))
+ setMessage(R.string.alert_troubleshooting_scan_card_title)
+ setPositiveButton(R.string.alert_button_request_support) { _, _ ->
+ store.dispatch(GlobalAction.SendFeedback(ScanFailsEmail()))
+ }
+ setNegativeButton(R.string.common_cancel) { _, _ -> }
+ setOnDismissListener {
+ store.dispatch(HomeAction.HideDialog)
+ store.dispatch(WalletAction.HideDialog)
+ }
+ }.create()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt
deleted file mode 100644
index 6f70a4f60d..0000000000
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.tangem.tap.features.wallet.ui.dialogs
-
-import android.content.Context
-import androidx.appcompat.app.AlertDialog
-import com.tangem.tap.features.wallet.redux.WalletAction
-import com.tangem.tap.features.wallet.redux.WarningType
-import com.tangem.tap.store
-import com.tangem.wallet.R
-
-class WarningDialog(context: Context) : AlertDialog(context) {
-
- private val dialog: AlertDialog = Builder(context)
- .setTitle(context.getString(R.string.common_warning))
- .setPositiveButton(context.getString(R.string.common_ok)) { _, _ ->
- dismiss()
- }.setOnDismissListener {
- store.dispatch(WalletAction.SaveCardId)
- store.dispatch(WalletAction.HideDialog)
- }
- .create()
-
- fun show(warningType: WarningType) {
- val messageRes = when (warningType) {
- WarningType.CardSignedHashesBefore -> R.string.alert_card_signed_transactions
- WarningType.DevCard -> R.string.alert_developer_card
- }
- dialog.setMessage(dialog.context.getString(messageRes))
- dialog.show()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt
new file mode 100644
index 0000000000..9e2320d30c
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt
@@ -0,0 +1,148 @@
+package com.tangem.tap.features.wallet.ui.wallet
+
+import android.app.Dialog
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.tangem.tap.common.extensions.hide
+import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.redux.global.StateDialog
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.tokens.redux.TokensAction
+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.ScanFailsDialog
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.card_balance.*
+import kotlinx.android.synthetic.main.fragment_wallet.*
+import kotlinx.android.synthetic.main.layout_balance_error.*
+import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
+
+
+class MultiWalletView : WalletView {
+
+ private var fragment: WalletFragment? = null
+ private var dialog: Dialog? = null
+
+ private lateinit var walletsAdapter: WalletAdapter
+
+
+ override fun changeWalletView(fragment: WalletFragment) {
+ setFragment(fragment)
+ onViewCreated()
+ showMultiWalletView()
+ setupButtons()
+ }
+
+
+ private fun showMultiWalletView() {
+ val fragment = fragment ?: return
+ fragment.tv_twin_card_number.hide()
+ fragment.iv_twin_card.hide()
+ fragment.rv_pending_transaction.hide()
+ fragment.l_card_balance.hide()
+ fragment.l_address.hide()
+ fragment.l_buttons_short.hide()
+ fragment.l_buttons_long.hide()
+ fragment.btn_scan_multiwallet?.show()
+ fragment.rv_multiwallet.show()
+ fragment.btn_add_token.show()
+ }
+
+ private fun setupButtons() {
+ val fragment = fragment ?: return
+ fragment.btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) }
+ }
+
+ override fun setFragment(fragment: WalletFragment) {
+ this.fragment = fragment
+ }
+
+ override fun removeFragment() {
+ this.fragment = null
+ }
+
+ override fun onViewCreated() {
+ setupWalletsRecyclerView()
+ }
+
+ private fun setupWalletsRecyclerView() {
+ val fragment = fragment ?: return
+ walletsAdapter = WalletAdapter()
+ walletsAdapter.setHasStableIds(true)
+ fragment.rv_multiwallet.layoutManager = LinearLayoutManager(fragment.requireContext())
+ fragment.rv_multiwallet.adapter = walletsAdapter
+ }
+
+ override fun onNewState(state: WalletState) {
+ val fragment = fragment ?: return
+
+ walletsAdapter.submitList(state.wallets, state.primaryBlockchain, state.primaryToken)
+
+ fragment.btn_add_token?.setOnClickListener {
+ store.dispatch(TokensAction.LoadCurrencies)
+ store.dispatch(TokensAction.SetAddedCurrencies(state.wallets))
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
+ }
+ handleErrorStates(state, fragment)
+ handleDialogs(state.walletDialog)
+ }
+
+ private fun handleErrorStates(state: WalletState, fragment: WalletFragment) {
+ when (state.primaryWallet?.currencyData?.status) {
+ BalanceStatus.EmptyCard -> {
+ showErrorState(
+ fragment,
+ fragment.getText(R.string.wallet_error_empty_card),
+ fragment.getString(R.string.wallet_error_empty_card_subtitle)
+ )
+ configureButtonsForEmptyWalletState(fragment)
+ }
+ BalanceStatus.UnknownBlockchain -> {
+ showErrorState(
+ fragment,
+ fragment.getText(R.string.wallet_error_unsupported_blockchain),
+ fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
+ )
+ }
+ }
+ }
+
+ private fun showErrorState(
+ fragment: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence,
+ ) {
+ fragment.l_card_balance.show()
+ fragment.l_balance.hide()
+ fragment.l_balance_error.show()
+ fragment.rv_multiwallet.hide()
+ fragment.btn_add_token.hide()
+ fragment.tv_error_title.text = errorTitle
+ fragment.tv_error_descriptions.text = errorDescription
+ }
+
+ private fun configureButtonsForEmptyWalletState(fragment: WalletFragment) {
+ fragment.btn_scan_multiwallet.hide()
+ fragment.l_buttons_long.show()
+ fragment.btn_scan_long.setOnClickListener { store.dispatch(WalletAction.Scan) }
+ fragment.btn_confirm_long.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
+ fragment.btn_confirm_long.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.ScanFailsDialog -> {
+ if (dialog == null) dialog = ScanFailsDialog.create(context).apply { show() }
+ }
+ else -> {
+ dialog?.dismiss()
+ dialog = null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
new file mode 100644
index 0000000000..d6762f815b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
@@ -0,0 +1,247 @@
+package com.tangem.tap.features.wallet.ui.wallet
+
+import android.app.Dialog
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.redux.global.StateDialog
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.twins.TwinCardNumber
+import com.tangem.tap.features.wallet.models.PendingTransaction
+import com.tangem.tap.features.wallet.redux.*
+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.QrDialog
+import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.card_balance.*
+import kotlinx.android.synthetic.main.fragment_wallet.*
+import kotlinx.android.synthetic.main.layout_address.*
+import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
+import kotlinx.android.synthetic.main.layout_wallet_short_buttons.*
+
+class SingleWalletView : WalletView {
+
+ private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
+ private var fragment: WalletFragment? = null
+ private var dialog: Dialog? = null
+
+ override fun setFragment(fragment: WalletFragment) {
+ this.fragment = fragment
+ }
+
+ override fun removeFragment() {
+ fragment = null
+ }
+
+ override fun changeWalletView(fragment: WalletFragment) {
+ setFragment(fragment)
+ onViewCreated()
+ showSingleWalletView()
+ }
+
+ private fun showSingleWalletView() {
+ val fragment = fragment ?: return
+ fragment.rv_multiwallet.hide()
+ fragment.btn_add_token.hide()
+ fragment.btn_scan_multiwallet?.hide()
+ fragment.rv_pending_transaction.hide()
+ fragment.l_card_balance.show()
+ fragment.l_address.show()
+ }
+
+ override fun onViewCreated() {
+ setupTransactionsRecyclerView()
+ }
+
+
+ private fun setupTransactionsRecyclerView() {
+ val fragment = fragment ?: return
+ pendingTransactionAdapter = PendingTransactionsAdapter()
+ fragment.rv_pending_transaction.layoutManager = LinearLayoutManager(fragment.requireContext())
+ fragment.rv_pending_transaction.adapter = pendingTransactionAdapter
+ }
+
+ override fun onNewState(state: WalletState) {
+ setupTwinCards(state.twinCardsState)
+ state.primaryWallet ?: return
+ setupButtons(state.primaryWallet, state.twinCardsState != null)
+ setupAddressCard(state.primaryWallet)
+ showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
+ setupBalance(state, state.primaryWallet)
+ handleDialogs(state.walletDialog)
+ }
+
+ private fun showPendingTransactionsIfPresent(pendingTransactions: List) {
+ pendingTransactionAdapter.submitList(pendingTransactions)
+ fragment?.rv_pending_transaction?.show(pendingTransactions.isNotEmpty())
+ }
+
+
+ private fun setupBalance(state: WalletState, primaryWallet: WalletData) {
+ fragment?.apply {
+ this.l_balance.show()
+ BalanceWidget(this, primaryWallet.currencyData, state.twinCardsState != null).setup()
+ }
+ }
+
+ private fun setupTwinCards(twinCardsState: TwinCardsState?) {
+ fragment?.apply {
+ twinCardsState?.cardNumber?.let { cardNumber ->
+ this.tv_twin_card_number.show()
+ this.iv_twin_card.show()
+ val number = when (cardNumber) {
+ TwinCardNumber.First -> "1"
+ TwinCardNumber.Second -> "2"
+ }
+ this.tv_twin_card_number.text =
+ this.getString(R.string.wallet_twins_chip_format, number)
+ }
+ if (twinCardsState?.cardNumber == null) {
+ this.tv_twin_card_number.hide()
+ this.iv_twin_card.hide()
+ }
+ if (twinCardsState?.showTwinOnboarding == true) {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
+ }
+ }
+
+ }
+
+ private fun setupButtons(state: WalletData, isTwinsWallet: Boolean) {
+ val fragment = fragment ?: return
+
+ setupButtonsType(state, fragment)
+
+ val btnConfirm = if (state.topUpState.allowed) {
+ fragment.btn_confirm_short
+ } else {
+ fragment.btn_confirm_long
+ }
+ val btnScan = if (state.topUpState.allowed) {
+ fragment.btn_scan_short
+ } else {
+ fragment.btn_scan_long
+ }
+
+ setupConfirmButton(state, btnConfirm, fragment, isTwinsWallet)
+
+ btnScan.setOnClickListener {
+ store.dispatch(WalletAction.Scan)
+ }
+
+ fragment.btn_copy.setOnClickListener {
+ state.walletAddresses?.selectedAddress?.address?.let { addressString ->
+ store.dispatch(WalletAction.CopyAddress(addressString, fragment.requireContext()))
+ }
+ }
+ fragment.btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
+
+ fragment.btn_top_up.setOnClickListener {
+ store.dispatch(
+ WalletAction.TopUpAction.TopUp(fragment.requireContext(), R.color.backgroundLightGray)
+ )
+ }
+ }
+
+ private fun setupButtonsType(state: WalletData, fragment: WalletFragment) {
+ if (state.topUpState.allowed) {
+ fragment.l_buttons_long.hide()
+ fragment.l_buttons_short.show()
+ } else {
+ fragment.l_buttons_long.show()
+ fragment.l_buttons_short.hide()
+ }
+ }
+
+ private fun setupConfirmButton(
+ state: WalletData, btnConfirm: Button, fragment: WalletFragment, isTwinsWallet: Boolean,
+ ) {
+ val buttonTitle = when (state.mainButton) {
+ is WalletMainButton.SendButton -> R.string.wallet_button_send
+ is WalletMainButton.CreateWalletButton -> {
+ if (!isTwinsWallet) {
+ R.string.wallet_button_create_wallet
+ } else {
+ R.string.wallet_button_create_twin_wallet
+ }
+ }
+ }
+ btnConfirm.text = fragment.getString(buttonTitle)
+ btnConfirm.isEnabled = state.mainButton.enabled
+ btnConfirm.setOnClickListener {
+ when (state.mainButton) {
+ is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send())
+ is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet)
+ }
+ }
+ }
+
+
+ private fun setupAddressCard(state: WalletData) {
+ val fragment = fragment ?: return
+ if (state.walletAddresses != null && state.blockchain != null) {
+ fragment.l_address?.show()
+ if (state.shouldShowMultipleAddress()) {
+ (fragment.l_address as? ViewGroup)?.beginDelayedTransition()
+ fragment.chip_group_address_type.show()
+ fragment.chip_group_address_type.fitChipsByGroupWidth()
+
+ val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
+ if (checkedId != View.NO_ID) fragment.chip_group_address_type.check(checkedId)
+
+ fragment.chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
+ if (checkedId == -1) return@setOnCheckedChangeListener
+ val type = MultipleAddressUiHelper.idToType(checkedId, state.blockchain)
+ type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
+ }
+ } else {
+ fragment.chip_group_address_type.hide()
+ }
+ fragment.tv_address.text = state.walletAddresses.selectedAddress.address
+ fragment.tv_explore?.setOnClickListener {
+ store.dispatch(WalletAction.ExploreAddress(
+ state.walletAddresses.selectedAddress.exploreUrl,
+ fragment.requireContext()))
+ }
+ } else {
+ fragment.l_address?.hide()
+ }
+ }
+
+ private fun handleDialogs(walletDialog: StateDialog?) {
+ val fragment = fragment ?: return
+ val context = fragment.context ?: return
+ when (walletDialog) {
+ is WalletDialog.QrDialog -> {
+ if (walletDialog.qrCode != null && walletDialog.shareUrl != null) {
+ if (dialog == null) dialog = QrDialog(context).apply {
+ this.showQr(
+ walletDialog.qrCode, walletDialog.shareUrl, walletDialog.currencyName
+ )
+ }
+ }
+ }
+ is WalletDialog.SelectAmountToSendDialog -> {
+ if (dialog == null) dialog = AmountToSendDialog(context).apply {
+ this.show(walletDialog.amounts)
+ }
+ }
+ is WalletDialog.ScanFailsDialog -> {
+ if (dialog == null) dialog = ScanFailsDialog.create(context).apply { show() }
+ }
+ null -> {
+ dialog?.dismiss()
+ dialog = null
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt
new file mode 100644
index 0000000000..ab49865c02
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt
@@ -0,0 +1,18 @@
+package com.tangem.tap.features.wallet.ui.wallet
+
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.ui.WalletFragment
+
+interface WalletView {
+
+ fun setFragment(fragment: WalletFragment)
+
+ fun removeFragment()
+
+ fun changeWalletView(fragment: WalletFragment)
+
+ fun onViewCreated()
+
+ fun onNewState(state: WalletState)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
index 08346630f3..871e3eb3d5 100644
--- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
+++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
@@ -11,16 +11,18 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.network.coinmarketcap.FiatCurrency
+import java.util.*
class PreferencesStorage(applicationContext: Application) {
- private val preferences: SharedPreferences by lazy {
- applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
- }
+ private val preferences: SharedPreferences = applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
+
+ val appRatingLaunchObserver: AppRatingLaunchObserver
init {
incrementLaunchCounter()
+ appRatingLaunchObserver = AppRatingLaunchObserver(preferences, getCountOfLaunches())
}
private val fiatCurrenciesAdapter: JsonAdapter> by lazy {
@@ -97,4 +99,63 @@ class PreferencesStorage(applicationContext: Application) {
private const val APP_LAUNCH_COUNT_KEY = "launchCount"
}
+}
+
+class AppRatingLaunchObserver(
+ private val preferences: SharedPreferences,
+ private val launchCounts: Int,
+) {
+ private val K_SHOW_RATING_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount"
+ private val K_FUNDS_FOUND_DATE = "fundsFoundDate"
+ private val K_USER_WAS_INTERACT_WITH_RATING = "userWasInteractWithRating"
+
+ private val deferShowing = 20
+ private val firstShowing = 3
+ private var fundsFoundDate: Calendar? = null
+
+ init {
+ val dateTimeMs = preferences.getLong(K_FUNDS_FOUND_DATE, -1)
+ if (dateTimeMs > 0) fundsFoundDate = Calendar.getInstance().apply { timeInMillis = dateTimeMs }
+ }
+
+ fun foundWalletWithFunds() {
+ if (fundsFoundDate != null) return
+
+ fundsFoundDate = Calendar.getInstance()
+ preferences.edit(true) {
+ putLong(K_FUNDS_FOUND_DATE, fundsFoundDate!!.timeInMillis).apply()
+ putInt(K_SHOW_RATING_AT_LAUNCH_COUNT, launchCounts + firstShowing)
+ }
+ }
+
+ fun isReadyToShow(): Boolean {
+ val fundsDate = fundsFoundDate ?: return false
+
+ if (!userWasInteractWithRating()) {
+ val diff = Calendar.getInstance().timeInMillis - fundsDate.timeInMillis
+ val diffInDays = diff / (1000 * 60 * 60 * 24)
+ return launchCounts >= getCounterOfNextShowing() && diffInDays >= firstShowing
+ }
+
+ val nextShowing = getCounterOfNextShowing()
+ return launchCounts >= nextShowing
+ }
+
+ fun applyDelayedShowing() {
+ updateNextShowing(launchCounts + deferShowing)
+ }
+
+ fun setNeverToShow() {
+ updateNextShowing(999999999)
+ }
+
+ private fun updateNextShowing(at: Int) {
+ val editor = preferences.edit()
+ editor.putInt(K_SHOW_RATING_AT_LAUNCH_COUNT, at)
+ editor.putBoolean(K_USER_WAS_INTERACT_WITH_RATING, true)
+ editor.apply()
+ }
+
+ private fun userWasInteractWithRating(): Boolean = preferences.getBoolean(K_USER_WAS_INTERACT_WITH_RATING, false)
+ private fun getCounterOfNextShowing(): Int = preferences.getInt(K_SHOW_RATING_AT_LAUNCH_COUNT, firstShowing)
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_binance.png b/app/src/main/res/drawable-hdpi/ic_binance.png
new file mode 100644
index 0000000000..edce12fa14
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_btc.png b/app/src/main/res/drawable-hdpi/ic_btc.png
new file mode 100644
index 0000000000..be16f5a51b
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_btc_cash.png b/app/src/main/res/drawable-hdpi/ic_btc_cash.png
new file mode 100644
index 0000000000..ec0864fdb8
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_cardano.png b/app/src/main/res/drawable-hdpi/ic_cardano.png
new file mode 100644
index 0000000000..8a4ac01ad4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_eth.png b/app/src/main/res/drawable-hdpi/ic_eth.png
new file mode 100644
index 0000000000..9db27f814e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_ltc.png b/app/src/main/res/drawable-hdpi/ic_ltc.png
new file mode 100644
index 0000000000..6c0fb4ad3c
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_rsk.png b/app/src/main/res/drawable-hdpi/ic_rsk.png
new file mode 100644
index 0000000000..be49cda24f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_stellar.png b/app/src/main/res/drawable-hdpi/ic_stellar.png
new file mode 100644
index 0000000000..7bb500ef1d
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_tezos.png b/app/src/main/res/drawable-hdpi/ic_tezos.png
new file mode 100644
index 0000000000..938fc9d3d4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_xlm.png b/app/src/main/res/drawable-hdpi/ic_xlm.png
new file mode 100644
index 0000000000..6155f291ea
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_xrp.png b/app/src/main/res/drawable-hdpi/ic_xrp.png
new file mode 100644
index 0000000000..058650f44b
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_binance.png b/app/src/main/res/drawable-ldpi/ic_binance.png
new file mode 100644
index 0000000000..18aa82ea4b
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_btc.png b/app/src/main/res/drawable-ldpi/ic_btc.png
new file mode 100644
index 0000000000..982bb50ad1
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_btc_cash.png b/app/src/main/res/drawable-ldpi/ic_btc_cash.png
new file mode 100644
index 0000000000..3789ae5071
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_cardano.png b/app/src/main/res/drawable-ldpi/ic_cardano.png
new file mode 100644
index 0000000000..dd8332ef62
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_eth.png b/app/src/main/res/drawable-ldpi/ic_eth.png
new file mode 100644
index 0000000000..b8cd7b26ab
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_ltc.png b/app/src/main/res/drawable-ldpi/ic_ltc.png
new file mode 100644
index 0000000000..a9dc421e25
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_rsk.png b/app/src/main/res/drawable-ldpi/ic_rsk.png
new file mode 100644
index 0000000000..416da64bb7
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_stellar.png b/app/src/main/res/drawable-ldpi/ic_stellar.png
new file mode 100644
index 0000000000..0de7200a90
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_tezos.png b/app/src/main/res/drawable-ldpi/ic_tezos.png
new file mode 100644
index 0000000000..502e81b732
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_xlm.png b/app/src/main/res/drawable-ldpi/ic_xlm.png
new file mode 100644
index 0000000000..3262fe8e13
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_xrp.png b/app/src/main/res/drawable-ldpi/ic_xrp.png
new file mode 100644
index 0000000000..70d484bc18
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_binance.png b/app/src/main/res/drawable-mdpi/ic_binance.png
new file mode 100644
index 0000000000..9254cb4a01
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_btc.png b/app/src/main/res/drawable-mdpi/ic_btc.png
new file mode 100644
index 0000000000..f02a7a13a1
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_btc_cash.png b/app/src/main/res/drawable-mdpi/ic_btc_cash.png
new file mode 100644
index 0000000000..e0dea3ea53
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_cardano.png b/app/src/main/res/drawable-mdpi/ic_cardano.png
new file mode 100644
index 0000000000..7ff6fc7ec0
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_eth.png b/app/src/main/res/drawable-mdpi/ic_eth.png
new file mode 100644
index 0000000000..e01e76371f
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_ltc.png b/app/src/main/res/drawable-mdpi/ic_ltc.png
new file mode 100644
index 0000000000..13f1de9253
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_rsk.png b/app/src/main/res/drawable-mdpi/ic_rsk.png
new file mode 100644
index 0000000000..af269e0bda
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_stellar.png b/app/src/main/res/drawable-mdpi/ic_stellar.png
new file mode 100644
index 0000000000..207f7c21a8
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_tezos.png b/app/src/main/res/drawable-mdpi/ic_tezos.png
new file mode 100644
index 0000000000..71d699414b
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_xlm.png b/app/src/main/res/drawable-mdpi/ic_xlm.png
new file mode 100644
index 0000000000..1a35164ed1
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_xrp.png b/app/src/main/res/drawable-mdpi/ic_xrp.png
new file mode 100644
index 0000000000..ec5e3bb680
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_binance.png b/app/src/main/res/drawable-xhdpi/ic_binance.png
new file mode 100644
index 0000000000..4a1619f750
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_btc.png b/app/src/main/res/drawable-xhdpi/ic_btc.png
new file mode 100644
index 0000000000..e58ee8b721
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_btc_cash.png b/app/src/main/res/drawable-xhdpi/ic_btc_cash.png
new file mode 100644
index 0000000000..4709218591
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_cardano.png b/app/src/main/res/drawable-xhdpi/ic_cardano.png
new file mode 100644
index 0000000000..5e9c1be96a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_eth.png b/app/src/main/res/drawable-xhdpi/ic_eth.png
new file mode 100644
index 0000000000..5e5807bf8b
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_ltc.png b/app/src/main/res/drawable-xhdpi/ic_ltc.png
new file mode 100644
index 0000000000..1135186fa5
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_rsk.png b/app/src/main/res/drawable-xhdpi/ic_rsk.png
new file mode 100644
index 0000000000..2016162c6d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stellar.png b/app/src/main/res/drawable-xhdpi/ic_stellar.png
new file mode 100644
index 0000000000..d6073f7fc7
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_tezos.png b/app/src/main/res/drawable-xhdpi/ic_tezos.png
new file mode 100644
index 0000000000..811d221bdb
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_xlm.png b/app/src/main/res/drawable-xhdpi/ic_xlm.png
new file mode 100644
index 0000000000..7ebe6a2fe8
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_xrp.png b/app/src/main/res/drawable-xhdpi/ic_xrp.png
new file mode 100644
index 0000000000..aa8adabc2c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_binance.png b/app/src/main/res/drawable-xxhdpi/ic_binance.png
new file mode 100644
index 0000000000..dbe9c0805f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_btc.png b/app/src/main/res/drawable-xxhdpi/ic_btc.png
new file mode 100644
index 0000000000..87f552a566
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_btc_cash.png b/app/src/main/res/drawable-xxhdpi/ic_btc_cash.png
new file mode 100644
index 0000000000..11d710501e
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_cardano.png b/app/src/main/res/drawable-xxhdpi/ic_cardano.png
new file mode 100644
index 0000000000..74faea419a
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_eth.png b/app/src/main/res/drawable-xxhdpi/ic_eth.png
new file mode 100644
index 0000000000..1e7a0e7952
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_ltc.png b/app/src/main/res/drawable-xxhdpi/ic_ltc.png
new file mode 100644
index 0000000000..0cf80c3570
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_rsk.png b/app/src/main/res/drawable-xxhdpi/ic_rsk.png
new file mode 100644
index 0000000000..3a660c50bc
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_stellar.png b/app/src/main/res/drawable-xxhdpi/ic_stellar.png
new file mode 100644
index 0000000000..8824973627
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_tezos.png b/app/src/main/res/drawable-xxhdpi/ic_tezos.png
new file mode 100644
index 0000000000..16dccf95c8
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_xlm.png b/app/src/main/res/drawable-xxhdpi/ic_xlm.png
new file mode 100644
index 0000000000..0dc1b2ba5f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_xrp.png b/app/src/main/res/drawable-xxhdpi/ic_xrp.png
new file mode 100644
index 0000000000..881d6b7b4b
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_binance.png b/app/src/main/res/drawable-xxxhdpi/ic_binance.png
new file mode 100644
index 0000000000..f18f7ff9d7
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_binance.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_btc.png b/app/src/main/res/drawable-xxxhdpi/ic_btc.png
new file mode 100644
index 0000000000..165164f069
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_btc.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_btc_cash.png b/app/src/main/res/drawable-xxxhdpi/ic_btc_cash.png
new file mode 100644
index 0000000000..babd59d8b1
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_btc_cash.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_cardano.png b/app/src/main/res/drawable-xxxhdpi/ic_cardano.png
new file mode 100644
index 0000000000..1f8b6888a5
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_cardano.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_eth.png b/app/src/main/res/drawable-xxxhdpi/ic_eth.png
new file mode 100644
index 0000000000..74ead90795
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_eth.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_ltc.png b/app/src/main/res/drawable-xxxhdpi/ic_ltc.png
new file mode 100644
index 0000000000..d159e6728f
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_ltc.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_rsk.png b/app/src/main/res/drawable-xxxhdpi/ic_rsk.png
new file mode 100644
index 0000000000..b442ac65a4
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_rsk.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stellar.png b/app/src/main/res/drawable-xxxhdpi/ic_stellar.png
new file mode 100644
index 0000000000..a19f0db146
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_stellar.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_tezos.png b/app/src/main/res/drawable-xxxhdpi/ic_tezos.png
new file mode 100644
index 0000000000..d91ceab8fc
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_tezos.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_xlm.png b/app/src/main/res/drawable-xxxhdpi/ic_xlm.png
new file mode 100644
index 0000000000..6500485826
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_xlm.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_xrp.png b/app/src/main/res/drawable-xxxhdpi/ic_xrp.png
new file mode 100644
index 0000000000..22400ff291
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_xrp.png differ
diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml
new file mode 100644
index 0000000000..9b975701fe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_copy_chip.xml b/app/src/main/res/drawable/ic_copy_chip.xml
new file mode 100644
index 0000000000..83325e0952
--- /dev/null
+++ b/app/src/main/res/drawable/ic_copy_chip.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_ducatus.png b/app/src/main/res/drawable/ic_ducatus.png
new file mode 100644
index 0000000000..cfae76f29f
Binary files /dev/null and b/app/src/main/res/drawable/ic_ducatus.png differ
diff --git a/app/src/main/res/drawable/ic_inactive.xml b/app/src/main/res/drawable/ic_inactive.xml
new file mode 100644
index 0000000000..eb19f0ce6d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_inactive.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_no_tokens.xml b/app/src/main/res/drawable/ic_no_tokens.xml
new file mode 100644
index 0000000000..f1149e0fad
--- /dev/null
+++ b/app/src/main/res/drawable/ic_no_tokens.xml
@@ -0,0 +1,468 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 0000000000..d698df7efe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_share_chip.xml b/app/src/main/res/drawable/ic_share_chip.xml
new file mode 100644
index 0000000000..e026b36c2e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_share_chip.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/shape_circle.xml b/app/src/main/res/drawable/shape_circle.xml
new file mode 100644
index 0000000000..f857819f4d
--- /dev/null
+++ b/app/src/main/res/drawable/shape_circle.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_tokens.xml b/app/src/main/res/layout/fragment_add_tokens.xml
new file mode 100644
index 0000000000..3ed5cbd260
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_tokens.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml
index 2533020f4a..00c0626cba 100644
--- a/app/src/main/res/layout/fragment_details.xml
+++ b/app/src/main/res/layout/fragment_details.xml
@@ -116,15 +116,15 @@
android:id="@+id/tv_disclaimer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:drawablePadding="15dp"
android:paddingTop="7dp"
android:paddingBottom="10dp"
- android:drawablePadding="15dp"
+ android:text="@string/disclaimer_title"
android:textColor="@color/darkGray6"
android:textSize="16sp"
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/tv_signed_hashes_title"
- android:text="@string/disclaimer_title" />
+ app:layout_constraintTop_toBottomOf="@id/tv_signed_hashes_title" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/tv_send_feedback" />
+ app:layout_constraintTop_toBottomOf="@id/tv_security_title" />
diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml
index 86ec08548b..78131a2e15 100644
--- a/app/src/main/res/layout/fragment_send.xml
+++ b/app/src/main/res/layout/fragment_send.xml
@@ -74,6 +74,13 @@
android:layout_marginBottom="24dp"
tools:visibility="gone" />
+
+
+ android:clipToPadding="false"
+ android:paddingBottom="33dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/guideline"
+ app:layout_constraintTop_toTopOf="parent">
+ app:layout_constraintTop_toBottomOf="@id/iv_card" />
+ app:layout_constraintBottom_toBottomOf="@id/iv_twin_card"
+ app:layout_constraintEnd_toEndOf="@id/iv_twin_card"
+ app:layout_constraintStart_toStartOf="@id/iv_twin_card"
+ app:layout_constraintTop_toTopOf="@id/iv_twin_card"
+ tools:text="Card 2 of 2" />
+ app:barrierDirection="bottom"
+ app:constraint_referenced_ids="iv_card,iv_twin_card" />
+
+
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" />
@@ -117,22 +132,75 @@
layout="@layout/layout_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/l_card_balance" />
+
+
+
+
+
+
+
+
+ android:visibility="gone"
+ android:layout_marginTop="30dp"
+ app:layout_constraintTop_toBottomOf="@id/l_address"
+ app:layout_constraintVertical_bias="1"
+ app:layout_constraintBottom_toBottomOf="parent" />
+ android:visibility="gone"
+ android:layout_marginTop="30dp"
+ app:layout_constraintTop_toBottomOf="@id/l_address"
+ app:layout_constraintVertical_bias="1"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+
diff --git a/app/src/main/res/layout/fragment_wallet_details.xml b/app/src/main/res/layout/fragment_wallet_details.xml
new file mode 100644
index 0000000000..757eeab445
--- /dev/null
+++ b/app/src/main/res/layout/fragment_wallet_details.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_currency_subtitle.xml b/app/src/main/res/layout/item_currency_subtitle.xml
new file mode 100644
index 0000000000..c4ae27800d
--- /dev/null
+++ b/app/src/main/res/layout/item_currency_subtitle.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_currency_wallet.xml b/app/src/main/res/layout/item_currency_wallet.xml
new file mode 100644
index 0000000000..d4a2a6dfd2
--- /dev/null
+++ b/app/src/main/res/layout/item_currency_wallet.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_popular_token.xml b/app/src/main/res/layout/item_popular_token.xml
new file mode 100644
index 0000000000..8d22ab57b4
--- /dev/null
+++ b/app/src/main/res/layout/item_popular_token.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_balance_wallet_details.xml b/app/src/main/res/layout/layout_balance_wallet_details.xml
new file mode 100644
index 0000000000..56bf9b2d69
--- /dev/null
+++ b/app/src/main/res/layout/layout_balance_wallet_details.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_search_view.xml b/app/src/main/res/layout/layout_search_view.xml
new file mode 100644
index 0000000000..65bbfa77d1
--- /dev/null
+++ b/app/src/main/res/layout/layout_search_view.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_wallet_details.xml b/app/src/main/res/layout/layout_wallet_details.xml
new file mode 100644
index 0000000000..f8ac54763e
--- /dev/null
+++ b/app/src/main/res/layout/layout_wallet_details.xml
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_wallet_long_buttons.xml b/app/src/main/res/layout/layout_wallet_long_buttons.xml
index d32b3ac917..d63cb4681a 100644
--- a/app/src/main/res/layout/layout_wallet_long_buttons.xml
+++ b/app/src/main/res/layout/layout_wallet_long_buttons.xml
@@ -1,8 +1,8 @@
+ android:layout_height="match_parent">
+
+ app:layout_constraintVertical_bias="1" />
+ app:layout_constraintVertical_bias="1" />
+ app:layout_constraintVertical_bias="1" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_warning.xml b/app/src/main/res/layout/layout_warning.xml
new file mode 100644
index 0000000000..a9a76d4637
--- /dev/null
+++ b/app/src/main/res/layout/layout_warning.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/popular_tokens.xml b/app/src/main/res/menu/popular_tokens.xml
new file mode 100644
index 0000000000..80d52565c9
--- /dev/null
+++ b/app/src/main/res/menu/popular_tokens.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/wallet_details.xml b/app/src/main/res/menu/wallet_details.xml
new file mode 100644
index 0000000000..9622311e82
--- /dev/null
+++ b/app/src/main/res/menu/wallet_details.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 4ae7d12378..0000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
old mode 100644
new mode 100755
index f26b7cd005..e307de6027
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
deleted file mode 100644
index cd4844d78b..0000000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
deleted file mode 100644
index ad4b07ff80..0000000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index bb2457e1ff..0000000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
old mode 100644
new mode 100755
index d7b8e985cc..cfab76de31
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
deleted file mode 100644
index 115308a4f9..0000000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
deleted file mode 100644
index 7fccb8a69b..0000000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index 2a7e2a1395..0000000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
old mode 100644
new mode 100755
index 4f205d64ab..bddcb01270
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
deleted file mode 100644
index a6129e9f51..0000000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 3c3b30ca7e..0000000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index dc8fb9c530..0000000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
old mode 100644
new mode 100755
index ffebc72291..ac5cb152bb
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
deleted file mode 100644
index c018380e65..0000000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 97a83eb92a..0000000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 9d876c01e3..0000000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
old mode 100644
new mode 100755
index 613405d26a..533177434c
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
index dd3b3a6d67..d32c73f660 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
index fbe488e03a..9b72259299 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 405be1cf56..0000000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index df6ab33618..97c8b2d0af 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -12,7 +12,7 @@
Sie haben keinen Zugang zur Kamera erteilt, bitte passen Sie Ihre Datenschutzeinstellungen an
Einstellungen
Willkommen bei Tangem. Haben Sie eine dieser Karten?
- Willkommen bei Tangem Tap. Scannen Sie Ihre Karten um zu starten.
+ Willkommen bei Tangem. Scannen Sie Ihre Karten um zu starten.
Ja! Bitte scannen
Einkaufen
Halten Sie die \ncard hinter dem \nphone bereit und scannen Sie sie.
@@ -21,7 +21,7 @@
Scannen
Absenden
ein Wallet erstellen
- Tangem Tap
+ Tangem
eine Walletoption wählen
Token
Tokens
@@ -95,9 +95,10 @@
Zugangscode
Sie müssen den richtigen Zugangscode eingeben, bevor Sie die Karte scannen.
Sie können NFC-Probleme mit einigen iPhone 7/7+ während der Extraktion haben
- Warnung: Diese Karte wurde früher bereits aufgeladen und Transaktionen wurden damit signiert. Ziehen Sie eine sofortige Auszahlung aller Beträge in Betracht, wenn Sie diese Karte von einer nicht vertrauenswürdigen Quelle erhalten haben.
+ Diese Karte wurde früher bereits aufgeladen und Transaktionen wurden damit signiert. Ziehen Sie eine sofortige Auszahlung aller Beträge in Betracht, wenn Sie diese Karte von einer nicht vertrauenswürdigen Quelle erhalten haben.
+ Warnung
- Diese Karte ist für die Zusammenarbeit mit Tangem Tap nicht geeignet
+ Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet
Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel
Die vor September 2019 ausgestellten Karten können mit iPhone momentan nicht extrahiert werden. Wir arbeiten eng mit Apple zusammen, damit es in den nächsten Versionen von iOS möglich wäre.
Tippen um zu signieren
@@ -111,7 +112,7 @@
RECHTLICHER HAFTUNGSAUSSCHLUSS
-\n\n1.Tangem Tap App (Software)
+\n\n1.Tangem App (Software)
\n\nDie Software ist nur für die Verwendung mit Tangem hardware Wallets (Karten) über NFC-Schnittstelle geeignet. Die Software:
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index ffbe678287..29e248d8d7 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -13,7 +13,7 @@
Vous n\'avez pas octroyé l’accès à votre caméra, veuillez modifier vos paramètres de confidentialité
Paramètres
Bienvenue à Tangem. Avez-vous une de nos cartes
- Encore une fois, bienvenue dans Tangem Tap. \nScannez votre carte pour commencer.
+ Encore une fois, bienvenue dans Tangem. \nScannez votre carte pour commencer.
Oui! Scannez-la.
Magasin
Préparez-vous à appuyer votre carte au dos de votre téléphone.
@@ -22,7 +22,7 @@
Scanner
Envoyer
Créer un portefeuille
- Tangem Tap
+ Tangem
Choisir une variante de portefeuille
Jeton d\'authentification
Jetons d\'authentification
@@ -96,8 +96,9 @@
Code d’accès
Vous devrez entrer le mot de passe correct avant de scanner la carte
Vous pouvez rencontrer des problèmes NFC avec certains iPhone 7/7 + lors de l’extraction
- Attention: cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable.
- Cette carte n\'est pas conçue pour fonctionner avec Tangem Tap
+ Cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable.
+ Attention
+ Cette carte n\'est pas conçue pour fonctionner avec Tangem
La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement
Les cartes Tangem émises avant septembre 2019 ne peuvent actuellement pas être extraites à l\'aide de l\'iPhone. Nous travaillons activement avec Apple pour rendre cela possible dans les futures versions d\'iOS.
Touchez pour signer
@@ -112,7 +113,7 @@
Avertissement
-\n\n1.Application Tangem Tap (Logiciel)
+\n\n1.Application Tangem (Logiciel)
\n\nLe logiciel est destiné à l\’utilisation exclusivement avec les e-portefeuilles Tangem (Cartes) à l\’aide de l\’interface NFC. Le logiciel NE réalise pas ce qui suit :
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 7c683849f7..a7011d4bcc 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -11,7 +11,7 @@
Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy
Impostazioni
Benvenuti in Tangem. Hai una delle nostre schede?>
- Benevenuti di nuovo su Tangem Tap. Scansiona la tua carta per iniziare.
+ Benevenuti di nuovo su Tangem. Scansiona la tua carta per iniziare.
Si! Scansiona carta.
Shop
Preparati ad avvicinare la tua carta sul retro del telefono.
@@ -20,7 +20,7 @@
Scansiona
Invia
Crea portafoglio
- Tangem Tap
+ Tangem
Scegli l\'opzione del portafoglio
Token
Token
@@ -93,8 +93,9 @@
Codice di accesso
Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto
Potresti riscontrare problemi con l\'NFC su alcuni iPhone 7/7 + durante la rimozione
- Attenzione: questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile.
- Questa carta non è progettata per funzionare con Tangem Tap
+ Questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile.
+ Attenzione
+ Questa carta non è progettata per funzionare con Tangem
La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento
Le schede emesse prima di settembre 2019 non possono attualmente essere utilizzate con un iPhone. Stiamo lavorando a stretto contatto con Apple per renderle disponibili nelle future versioni di iOS.
Avvicina per firmare
@@ -109,7 +110,7 @@
NOTA LEGALE\n
\n
-App Tangem Tap (software)\n
+App Tangem (software)\n
\n
1. Il software può essere utilizzato solo con i portafogli hardware Tangem (schede) tramite l\'interfaccia NFC. Il software NON esegue le seguenti operazioni:\n
\n
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 2b8a0270b6..fc5a1fd7b2 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -43,5 +43,8 @@
#CBE4FF
#E0E6FA
+ #1C1C1E
+ #FFB71B
+ #CA0F03
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 690be865b5..041c1aa018 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,6 +1,6 @@
- Tangem Tap
+ Tangem
No
Save changes
@@ -16,7 +16,7 @@
Settings
Welcome to Tangem.\nDo you have one of our cards?
- Welcome back to Tangem Tap. \nScan your card to start.
+ Welcome back to Tangem. \nScan your card to start.
Shop
Get ready to tap your \ncard to the back of the \nphone.
Tap in
@@ -26,7 +26,7 @@
Scan
Send
Create wallet
- Tangem Tap
+ Tangem
Choice wallet option
Token
@@ -107,8 +107,9 @@
You will have to submit the correct access code before scanning the card
You may experience NFC problems with some iPhone 7/7+ during the extraction
- Warning: This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source.
- This card it is not designed to work with Tangem Tap
+ This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source.
+ Warning
+ This card it is not designed to work with Tangem
The card you scanned is a development card. Don’t accept it as a payment
Tangem cards manufactured before September 2019 cannot currently be extracted with an iPhone. We’re working hard with Apple to make it possible in future versions of iOS.
@@ -123,7 +124,7 @@
Legal Disclaimer
\n
- \n1. Tangem Tap application (Software)
+ \n1. Tangem application (Software)
\n
\nThe Software is intended for usage only with Tangem hardware wallets (Cards) via NFC interface. The Software DOES NOT:
\n
diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml
index 8418b367b6..238bc7a845 100644
--- a/app/src/main/res/values/strings_untranslated.xml
+++ b/app/src/main/res/values/strings_untranslated.xml
@@ -31,13 +31,77 @@ this wallet.
The twin address was successfully created
+ //Common
Start
Back
+ Add
+ Remove
+ Search
+
Memo
Destination tag
Invalid destination tag. It won\'t be added to the transaction
Invalid Memo ID. It won\'t be added to the transaction
+ App
+ Send feedback
+ Sent successfully
+ Thank you for your feedback
+ Your suggestions were sent
+ Thank your for your feedback. We will response as soon as possible
+ Failed to send email
+ Reason: %s
+ Can’t send a transaction
+ Reason: %s. Do you want to send feedback?
+ Are you having difficulty scanning your card?
+ Please try to tap the card exactly as shown in the animation or request support.
+ Try again
+ Request support
+ Send feedback
+ Really cool!
+ Can be better
+ One question
+ How do you like Tangem?
+
+ All fields are required
+
+ //Manage tokens
+ No tokens yet
+ Added tokens
+ Remove %s token
+ Are you sure you want to remove this token?
+ Add Tokens
+ Add Token
+ Manage Tokens
+ Add Custom
+ Custom tokens
+ Popular tokens
+ Add Custom Token
+ Please, fill all fields
+ Entered number is not a valid decimals number
+ Name
+ Token symbol
+ Contract address
+ Decimals
+ ex. USD Coin
+ ex. USDC
+ Added
+
+ //Details
+ Blockchain
+ Manage tokens
+
+ Blockchains
+ Ethereum Tokens
+ Copy
+ Share
+ + Add tokens
+ Please wait for the transaction to complete to be able to send funds
+
+ Authenticity check failed
+ This card might be a production sample or counterfeit
+
+
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 0000000000..1a33b59980
--- /dev/null
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 4dc0d14356..3b07b1a1ac 100644
--- a/build.gradle
+++ b/build.gradle
@@ -31,14 +31,14 @@ allprojects {
username = nexusUser
password = nexusPass
}
- url "https://nexus.tangem.com/repository/maven-releases/"
+ url "https://nexus.tangem-tech.com/repository/maven-releases/"
}
maven {
credentials {
username = nexusUser
password = nexusPass
}
- url "https://nexus.tangem.com/repository/maven-snapshots/"
+ url "https://nexus.tangem-tech.com/repository/maven-snapshots/"
}
maven { url 'https://jitpack.io' }
}
diff --git a/dependencies.gradle b/dependencies.gradle
index 6aa50c71a1..f74991aa26 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,4 +1,4 @@
ext.versions = [
- kotlin : '1.4.30',
- build_gradle: '4.1.2',
+ kotlin : '1.4.31',
+ build_gradle: '4.1.3',
]