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 732ca5892e..78c6c7dbd6 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -6,7 +6,9 @@ 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.TangemSdk @@ -52,9 +54,13 @@ private fun initCoroutineExceptionHandler(): CoroutineExceptionHandler { } 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 0f609b7328..e458fd4e04 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -69,12 +69,11 @@ class TapApplication : Application() { private fun initFeedbackManager() { val infoHolder = AdditionalEmailInfo() - infoHolder.updateAppVersion(this) + infoHolder.setAppVersion(this) val logWriter = TangemLogCollector() Log.addLogger(logWriter) - val feedbackManager = FeedbackManager(infoHolder, this, logWriter) - store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager)) + store.dispatch(GlobalAction.SetFeedbackManager(FeedbackManager(infoHolder, logWriter))) } } \ 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 index e03e96133b..dc12172a82 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt @@ -7,15 +7,15 @@ import com.tangem.wallet.R @DrawableRes fun Blockchain.getIconRes(): Int { return when (this) { - Blockchain.Unknown, Blockchain.Ducatus, Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet, - Blockchain.BinanceTestnet -> 1 - Blockchain.Bitcoin -> R.drawable.ic_btc + 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 -> R.drawable.ic_eth + Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth Blockchain.RSK -> R.drawable.ic_rsk Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano - Blockchain.Binance -> R.drawable.ic_binance + 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 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/Token.kt b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt index f9499f8e70..fdfb83ef77 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt @@ -3,9 +3,14 @@ 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 ("#" + this.contractAddress.subSequence(2..7).toString()) - .toColorInt() + 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/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 45bc0c2707..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,5 +1,6 @@ package com.tangem.tap.common.redux.global +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 @@ -11,18 +12,25 @@ import org.rekotlin.Action 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 ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction() object RestoreAppCurrency : GlobalAction() { data class Success(val appCurrency: FiatCurrencyName) : GlobalAction() } + data class UpdateWalletSignedHashes(val walletSignedHashes: Int?) : 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 SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction() - data class SendFeedback(val emailData: EmailData): 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 0c8de5dc8d..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,8 +1,10 @@ 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.WarningMessage 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 @@ -13,6 +15,22 @@ 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() @@ -26,10 +44,8 @@ val globalMiddleware: Middleware = { dispatch, appState -> store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) } - store.dispatch(WalletAction.Warnings.SetWarnings( - it.getWarnings(WarningMessage.Location.MainScreen))) - store.dispatch(SendAction.SetWarnings( - it.getWarnings(WarningMessage.Location.SendScreen))) + store.dispatch(WalletAction.Warnings.Update) + store.dispatch(SendAction.Warnings.Update) } } } 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 8df35e1064..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,6 +11,12 @@ 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.ChangeAppCurrency -> { @@ -33,7 +39,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState { globalState.copy(configManager = action.configManager) } is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager) - is GlobalAction.HideWarningMessage -> globalState is GlobalAction.UpdateSecurityOptions -> { val card = when (action.securityOption) { SecurityOption.LongTap -> globalState.scanNoteResponse?.card?.copy( 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 52340df26d..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 @@ -20,9 +20,12 @@ data class GlobalState( val configManager: ConfigManager? = null, val warningManager: WarningMessagesManager? = null, val feedbackManager: FeedbackManager? = null, - val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY + val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, + val scanCardFailsCounter: Int = 0, ) : StateType typealias CryptoCurrencyName = String typealias FiatCurrencyName = String + +interface StateDialog \ No newline at end of file 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 0d6662d4f0..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,6 +1,7 @@ package com.tangem.tap.domain 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.tap.common.analytics.AnalyticsEvent @@ -13,6 +14,7 @@ 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 @@ -31,7 +33,7 @@ class TapWalletManager { private val blockchainSdkConfig by lazy { store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig() } - private val walletManagerFactory: WalletManagerFactory + val walletManagerFactory: WalletManagerFactory by lazy { WalletManagerFactory(blockchainSdkConfig) } suspend fun loadWalletData(walletManager: WalletManager) { @@ -84,9 +86,8 @@ class TapWalletManager { FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card) } TapWorkarounds.updateCard(data.card) - store.state.globalState.warningManager?.setBlockchain(data.walletManager?.wallet?.blockchain) + store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card) updateConfigManager(data) - updateFeedbackManager(data) withContext(Dispatchers.Main) { store.dispatch(WalletAction.ResetState) @@ -124,61 +125,27 @@ class TapWalletManager { } } - private fun updateFeedbackManager(data: ScanNoteResponse) { - val card = data.card - val wallet = data.walletManager?.wallet ?: return - val infoHolder = store.state.globalState.feedbackManager?.infoHolder ?: return - - infoHolder.cardId = card.cardId - infoHolder.cardFirmwareVersion = card.firmwareVersion.version - infoHolder.signedHashesCount = card.walletSignedHashes?.toString() ?: "0" - infoHolder.sourceAddress = wallet.address - infoHolder.explorerLink = wallet.getExploreUrl(wallet.address) - infoHolder.blockchain = wallet.blockchain - } - suspend fun loadData(data: ScanNoteResponse) { withContext(Dispatchers.Main) { - store.dispatch(WalletAction.Warnings.CheckIfNeeded) 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 val primaryWalletManager = data.walletManager - val primaryBlockchain = listOf(data.walletManager.wallet.blockchain) + 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[0])) + store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(primaryBlockchain)) if (primaryToken != null) { store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken)) } - if (data.card.isMultiwalletAllowed) { - val savedCurrencies = currenciesRepository.loadCardCurrencies(data.card.cardId) - - val walletManagers = listOf(primaryWalletManager) + - currenciesRepository.getBlockchains() - .filterNot { it == primaryWalletManager.wallet.blockchain } - .mapNotNull { walletManagerFactory.makeWalletManager(data.card, it) } - val otherBlockhains = savedCurrencies.blockchains - - val activeTokens = walletManagers.first { it.wallet.blockchain == Blockchain.Ethereum} - .presetTokens.toList() - val tokens = activeTokens + savedCurrencies.tokens - - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers)) - store.dispatch(WalletAction.MultiWallet.AddBlockchains(primaryBlockchain + otherBlockhains)) - store.dispatch(WalletAction.MultiWallet.AddTokens(tokens)) - store.dispatch(WalletAction.MultiWallet.FindTokensInUse) + loadMultiWalletData(data.card, primaryBlockchain, primaryWalletManager) } else { - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(listOf(primaryWalletManager))) - store.dispatch(WalletAction.MultiWallet.AddBlockchains(primaryBlockchain)) + 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)) @@ -191,6 +158,35 @@ class TapWalletManager { store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain)) store.dispatch(WalletAction.LoadArtwork(data.card, artworkId)) } + store.dispatch(WalletAction.Warnings.CheckIfNeeded) + } + } + + 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) } } @@ -217,6 +213,10 @@ 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)) { 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 index e0d0a6a52c..3471ed78ee 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/Loader.kt @@ -10,7 +10,7 @@ interface Loader { companion object { const val featuresName = "features_${BuildConfig.CONFIG_ENVIRONMENT}" - const val configValuesName = "config_${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/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index b1ccd85929..58096022fd 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -31,11 +31,12 @@ class ConfigManager( localLoader.load { config -> setupFeature(config.features) setupKey(config.configValues) - } - remoteLoader.load { config -> - setupFeature(config.features) onComplete?.invoke() } + // Uncomment to enable remote config +// remoteLoader.load { config -> +// setupFeature(config.features) +// } } fun turnOff(name: String) { @@ -79,7 +80,7 @@ class ConfigManager( blockchainSdkConfig = BlockchainSdkConfig( blockchairApiKey = values.blockchairApiKey, blockcypherTokens = values.blockcypherTokens, - infuraProjectId = values.infuraProjectId + infuraProjectId = values.infuraProjectId ) ) defaultConfig = defaultConfig.copy( @@ -89,7 +90,7 @@ class ConfigManager( blockchainSdkConfig = BlockchainSdkConfig( blockchairApiKey = values.blockchairApiKey, blockcypherTokens = values.blockcypherTokens, - infuraProjectId = values.infuraProjectId + infuraProjectId = values.infuraProjectId ) ) } diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt index 1ee151033f..81739ee7b5 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt @@ -2,6 +2,7 @@ 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 /** @@ -11,7 +12,6 @@ class WarningMessagesManager( private val warningLoader: RemoteWarningLoader, ) { - private var blockchain: Blockchain? = null private val warningsList: MutableList = mutableListOf() fun load(onComplete: VoidCallback? = null) { @@ -23,10 +23,6 @@ class WarningMessagesManager( } } - fun setBlockchain(blockchain: Blockchain?) { - this.blockchain = blockchain - } - fun addWarning(warning: WarningMessage) { if (findWarning(warning) == null) { warningsList.add(warning) @@ -34,14 +30,14 @@ class WarningMessagesManager( } } - fun getWarnings(location: WarningMessage.Location): List { + fun getWarnings(location: WarningMessage.Location, forBlockchains: List = emptyList()): List { return warningsList .filter { !it.isHidden && it.location.contains(location) } .filter { - val blockchainList = it.blockchainList + val list = it.blockchainList when { - blockchainList == null -> true - blockchainList.contains(blockchain) -> true + list == null -> true + list.containsAny(forBlockchains) -> true else -> false } } @@ -117,5 +113,17 @@ class WarningMessagesManager( 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 e2ae2aef19..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,6 @@ 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 @@ -32,10 +29,6 @@ data class ScanNoteResponse( class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { override val requiresPin2 = false - private val blockchainSdkConfig = store.state.globalState.configManager?.config - ?.blockchainSdkConfig ?: BlockchainSdkConfig() - private val walletManagerFactory = WalletManagerFactory(blockchainSdkConfig) - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { ScanTask().run(session) { result -> when (result) { @@ -59,7 +52,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable + 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(intent, "Send mail...") + 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(context, chooserIntent, null) + 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(context.filesDir, "logs.txt") + val file = File(activity.filesDir, "logs.txt") file.delete() file.createNewFile() @@ -73,10 +104,12 @@ class FeedbackManager( } class TangemLogCollector : TangemSdkLogger { + private val dateFormatter = SimpleDateFormat("HH:mm:ss.SSS") private val logs = mutableListOf() override fun log(message: () -> String, level: Log.Level) { - logs.add(message()) + val time = dateFormatter.format(Date()) + logs.add("$time: ${message()}\n") } fun getLogs(): List = logs.toList() @@ -87,26 +120,36 @@ class TangemLogCollector : TangemSdkLogger { } class AdditionalEmailInfo { - var cardId: String = "" - var cardFirmwareVersion: String = "" - var blockchain: Blockchain = Blockchain.Unknown + class EmailWalletInfo( + var blockchain: Blockchain = Blockchain.Unknown, + var address: String = "", + var explorerLink: String = "", + // var outputsCount: String = "" + // var transactionHex: String = "" + ) - var phoneModel: String = Build.MODEL - var osVersion: String = Build.VERSION.SDK_INT.toString() var appVersion: String = "" - var token: String = "" - var sourceAddress: 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 = "" - // var transactionHex: String = "" - var signedHashesCount: String = "" - var explorerLink: String = "" -// var outputsCount: String = "" - - fun updateAppVersion(context: Context) { + fun setAppVersion(context: Context) { try { val pInfo = context.packageManager.getPackageInfo(context.packageName, 0) appVersion = pInfo.versionName @@ -114,6 +157,51 @@ class AdditionalEmailInfo { 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 { @@ -133,9 +221,10 @@ class RateCanBeBetterEmail : EmailData { 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", infoHolder.blockchain.fullName) + appendKeyValue("Blockchain", walletInfo.blockchain.fullName) appendKeyValue("Phone model", infoHolder.phoneModel) appendKeyValue("OS version", infoHolder.osVersion) appendKeyValue("App version", infoHolder.appVersion) @@ -159,12 +248,13 @@ 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", infoHolder.blockchain.fullName) + appendKeyValue("Blockchain", walletInfo.blockchain.fullName) appendKeyValue("Token", infoHolder.token) - appendKeyValue("Source address", infoHolder.sourceAddress) + appendKeyValue("Source address", walletInfo.address) appendKeyValue("Destination address", infoHolder.destinationAddress) appendKeyValue("Amount", infoHolder.amount) appendKeyValue("Fee", infoHolder.fee) @@ -181,17 +271,20 @@ class FeedbackEmail : EmailData { override val subject: String = "Tangem Tap feedback" override val mainMessage: String = "Hi Tangem," override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { - return StringBuilder().apply { - appendKeyValue("Card ID", infoHolder.cardId) - appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) - appendKeyValue("Signed hashes", infoHolder.signedHashesCount) - appendKeyValue("Blockchain", infoHolder.blockchain.fullName) - appendKeyValue("Wallet address", infoHolder.sourceAddress) - appendKeyValue("Explorer link", infoHolder.explorerLink) + 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) - appendKeyValue("Phone model", infoHolder.phoneModel) - appendKeyValue("OS version", infoHolder.osVersion) - }.toString() + builder.appendKeyValue("Phone model", infoHolder.phoneModel) + builder.appendKeyValue("OS version", infoHolder.osVersion) + return builder.toString() } } 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 8a87b6108f..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,8 +3,10 @@ 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 @@ -25,6 +27,7 @@ object ReleaseSendState : Action data class PrepareSendScreen( val coinAmount: Amount?, val coinRate: BigDecimal?, + val walletManager: WalletManager?, val tokenAmount: Amount? = null, val tokenRate: BigDecimal? = null ) : SendScreenAction @@ -140,14 +143,19 @@ sealed class SendAction : SendScreenAction { data class SendError(override val error: TapError) : SendAction(), ErrorAction - sealed class Dialog : SendAction() { + 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() + + data class SendTransactionFails(val errorMessage: String) : Dialog() object Hide : Dialog() } - data class SetWarnings(val warningList: List) : SendAction() + + 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 115c1c2033..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,6 +15,7 @@ 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 @@ -46,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) } @@ -56,8 +58,8 @@ private fun verifyAndSendTransaction( 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 @@ -120,6 +122,9 @@ private fun sendTransaction( 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)) @@ -142,10 +147,11 @@ 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)) - updateFeedbackManager(walletManager, amountToSend, feeAmount, destinationAddress, card) + infoHolder?.updateOnSendError(walletManager.wallet, amountToSend, feeAmount, destinationAddress) dispatch(SendAction.Dialog.SendTransactionFails("unknown error")) } message.contains("50002") -> { @@ -160,7 +166,7 @@ private fun sendTransaction( Timber.e(throwable) FirebaseCrashlytics.getInstance().recordException(throwable) dispatch(SendAction.SendError(TapError.CustomError(message))) - updateFeedbackManager(walletManager, amountToSend, feeAmount, destinationAddress, card) + infoHolder?.updateOnSendError(walletManager.wallet, amountToSend, feeAmount, destinationAddress) dispatch(SendAction.Dialog.SendTransactionFails(message)) } } @@ -173,29 +179,6 @@ private fun sendTransaction( } } -private fun updateFeedbackManager( - walletManager: WalletManager, - amountToSend: Amount, - feeAmount: Amount, - destinationAddress: String, - card: Card, -) { - val infoHolder = store.state.globalState.feedbackManager?.infoHolder ?: return - val amountState = store.state.sendState.amountState - - infoHolder.cardId = card.cardId - infoHolder.blockchain = walletManager.wallet.blockchain - infoHolder.sourceAddress = walletManager.wallet.address - infoHolder.destinationAddress = destinationAddress - infoHolder.amount = amountToSend.value?.stripZeroPlainString() ?: "0" - infoHolder.fee = feeAmount.value?.stripZeroPlainString() ?: "0" - infoHolder.cardFirmwareVersion = card.firmwareVersion.version - if (amountState.typeOfAmount is AmountType.Token) { - infoHolder.token = amountState.amountToExtract?.currencySymbol ?: "" - } -// infoHolder.transactionHex = "" -} - fun extractErrorsForAmountField(errors: EnumSet): EnumSet { val showIntoAmountField = EnumSet.noneOf(TransactionError::class.java) errors.forEach { @@ -243,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 527d3f6545..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 @@ -45,7 +45,7 @@ private class SendReducer : SendInternalReducer { 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.SetWarnings -> sendState.copy(sendWarningsList = action.warningList) + is SendAction.Warnings.Set -> sendState.copy(sendWarningsList = action.warningList) else -> return sendState } @@ -60,14 +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 return sendState.copy( walletManager = walletManager, coinConverter = action.coinRate?.let { CurrencyConverter(it, decimals) }, - tokenConverter = action.tokenRate?. let { CurrencyConverter(it, decimals) }, + tokenConverter = action.tokenRate?.let { CurrencyConverter(it, decimals) }, amountState = sendState.amountState.copy( amountToExtract = amountToExtract, typeOfAmount = amountToExtract.type, 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 c2ec80e66a..41d2fd79c5 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,10 +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.domain.configurable.warningMessage.WarningMessage -import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.store import org.rekotlin.StateType import java.math.BigDecimal @@ -40,7 +40,7 @@ data class SendState( 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 1879696a11..5be3d75998 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 @@ -23,7 +23,6 @@ import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity import com.tangem.tap.common.snackBar.MaxAmountSnackbar import com.tangem.tap.common.text.truncateMiddleWith import com.tangem.tap.common.toggleWidget.* -import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.features.send.BaseStoreFragment import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.AddressPayIdActionUi.* @@ -235,8 +234,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { rv_warning_messages.addItemDecoration(SpacesItemDecoration(rv_warning_messages.dpToPx(16f).toInt())) rv_warning_messages.adapter = warningsAdapter - val warnings = store.state.globalState.warningManager?.getWarnings(WarningMessage.Location.SendScreen) ?: listOf() - store.dispatch(SendAction.SetWarnings(warnings)) + store.dispatch(SendAction.Warnings.Update) } override fun subscribeToStore() { 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 b85af64448..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,7 +1,6 @@ package com.tangem.tap.features.wallet.redux import android.content.Context -import com.tangem.TangemError import com.tangem.blockchain.common.* import com.tangem.blockchain.common.address.AddressType import com.tangem.commands.common.card.Card @@ -10,6 +9,7 @@ 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 @@ -33,29 +33,18 @@ sealed class WalletAction : Action { data class SetArtworkId(val artworkId: String?) : WalletAction() - // sealed class ProcessWallet : WalletAction() { -// data class LoadWallet(val artworkId: String?, val allowTopUp: Boolean -// ) : ProcessWallet() { -// data class Success(val wallet: Wallet) : ProcessWallet() -// data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : ProcessWallet() -// data class Failure(val wallet: Wallet, val errorMessage: String? = null) : ProcessWallet() -// } -// -// data class UpdateWallet(val currency: CryptoCurrencyName? = null) : ProcessWallet() { -// object ScheduleUpdatingWallet : ProcessWallet() -// data class Success(val wallet: Wallet) : ProcessWallet() -// data class Failure(val errorMessage: String? = null) : ProcessWallet() -// } -// } - sealed class MultiWallet : WalletAction() { data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet() - data class AddWalletManagers(val walletManagers: List) : 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() @@ -72,7 +61,8 @@ sealed class WalletAction : Action { } object CheckIfNeeded : Warnings() - data class SetWarnings(val warningList: List) : Warnings() + object Update : Warnings() + data class Set(val warningList: List) : Warnings() object AppRating : Warnings() { object SetNeverToShow : Warnings() @@ -99,7 +89,6 @@ sealed class WalletAction : Action { } object Scan : WalletAction() - class ScanCardFinished(val scanError: TangemError? = null) : WalletAction() data class Send(val amount: Amount? = null) : WalletAction() { data class ChooseCurrency(val amounts: List?) : WalletAction() 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 6bf6a41ffc..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 @@ -6,6 +6,7 @@ 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 @@ -22,10 +23,9 @@ data class WalletState( val error: ErrorType? = null, val cardImage: Artwork? = null, val hashesCountVerified: Boolean? = null, - val walletDialog: WalletDialog? = null, + val walletDialog: StateDialog? = null, val twinCardsState: TwinCardsState? = null, val mainWarningsList: List = mutableListOf(), - val scanCardFailsCounter: Int = 0, val wallets: List = emptyList(), val walletManagers: List = emptyList(), val isMultiwalletAllowed: Boolean = false, @@ -42,16 +42,26 @@ data class WalletState( 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 } - if (walletManager != null) return walletManager + 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 { - null + val primaryWalletManager = walletManagers.find { it.wallet.blockchain == primaryBlockchain } + if (primaryWalletManager?.presetTokens?.find { it.symbol == currencyName } != null) { + primaryWalletManager + } else { + ethereumWalletManager + } } } @@ -96,7 +106,7 @@ data class WalletState( } -sealed class WalletDialog { +sealed class WalletDialog: StateDialog { data class QrDialog( val qrCode: Bitmap?, val shareUrl: String?, val currencyName: CryptoCurrencyName? ) : WalletDialog() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 4f28ce1f3f..6daa182e35 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -1,10 +1,11 @@ package com.tangem.tap.features.wallet.redux.middlewares -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager +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 @@ -14,14 +15,19 @@ 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? + 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)) @@ -37,10 +43,19 @@ class MultiWalletMiddleware { action.tokens.map { addToken(it, walletState) } } is WalletAction.MultiWallet.AddBlockchain -> { - globalState?.scanNoteResponse?.card?.cardId?.let { - currenciesRepository.saveAddedBlockchain(it, action.blockchain) + 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 @@ -50,10 +65,39 @@ class MultiWalletMiddleware { 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 alreadyAddedTokens = walletManager.presetTokens val tokenFinder = walletManager as TokenFinder scope.launch { val result = tokenFinder.findTokens() @@ -62,9 +106,7 @@ class MultiWalletMiddleware { is Result.Success -> { if (result.data.isNotEmpty()) { store.dispatch(WalletAction.MultiWallet.AddTokens( - walletManager.presetTokens - .filterNot { presetToken -> alreadyAddedTokens.any { it.symbol == presetToken.symbol } } - .toList() + walletManager.presetTokens.toList() )) } } @@ -76,8 +118,7 @@ class MultiWalletMiddleware { } private fun addToken(token: Token, walletState: WalletState?) { - val walletManager = - (walletState?.getWalletManager(Blockchain.Ethereum.currency) as? EthereumWalletManager) + val walletManager = walletState?.getWalletManager(token.symbol) scope.launch { val result = walletManager?.addToken(token) withContext(Dispatchers.Main) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 54a51ff45e..05993a08ef 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -3,14 +3,15 @@ package com.tangem.tap.features.wallet.redux.middlewares import android.content.Intent import android.net.Uri import androidx.core.content.ContextCompat -import com.tangem.TangemSdkError 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 @@ -59,6 +60,13 @@ class WalletMiddleware { } } 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) } @@ -126,27 +134,12 @@ class WalletMiddleware { is WalletAction.Scan -> { scope.launch { val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler) - when (result) { - is CompletionResult.Success -> { - tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card) - globalState?.tapWalletManager - ?.onCardScanned(result.data, true) - if (walletState?.twinCardsState != null) { - val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown() - if (showOnboarding) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding)) - } - } - store.dispatch(WalletAction.ScanCardFinished()) - } - is CompletionResult.Failure -> { - if (result.error !is TangemSdkError.UserCancelled) { - scope.launch(Dispatchers.Main) { - store.dispatch(WalletAction.ScanCardFinished(result.error)) - if (store.state.walletState.scanCardFailsCounter >= 2) { - store.dispatch(WalletAction.ShowDialog.ScanFails) - } - } + 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) } } } @@ -199,13 +192,14 @@ class WalletMiddleware { private fun prepareSendAction(amount: Amount?, state: WalletState?): Action { val selectedWalletData = state?.getSelectedWalletData() val currency = selectedWalletData?.currencyData?.currencySymbol - val wallet = currency?.let { state.getWalletManager(currency)?.wallet } + val walletManager = state?.getWalletManager(currency) + val wallet = walletManager?.wallet return if (amount != null) { if (amount.type is AmountType.Token) { - prepareSendActionForToken(amount, state, selectedWalletData, wallet) + prepareSendActionForToken(amount, state, selectedWalletData, wallet, walletManager) } else { - PrepareSendScreen(amount, selectedWalletData?.fiatRate) + PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletManager) } } else { val amounts = wallet?.amounts?.toSendableAmounts() @@ -213,23 +207,24 @@ class WalletMiddleware { val amountToSend = amounts?.find { it.currencySymbol == currency } ?: return WalletAction.Send.ChooseCurrency(amounts) if (amountToSend.type is AmountType.Token) { - prepareSendActionForToken(amount, state, selectedWalletData, wallet) + prepareSendActionForToken(amountToSend, state, selectedWalletData, wallet, walletManager) } else { - PrepareSendScreen(amountToSend, selectedWalletData.fiatRate) + PrepareSendScreen(amountToSend, selectedWalletData.fiatRate, walletManager) } } else { if (amounts?.size ?: 0 > 1) { WalletAction.Send.ChooseCurrency(amounts) } else { val amountToSend = amounts?.first() - PrepareSendScreen(amountToSend, selectedWalletData?.fiatRate) + PrepareSendScreen(amountToSend, selectedWalletData?.fiatRate, walletManager) } } } } private fun prepareSendActionForToken( - amount: Amount?, state: WalletState?, selectedWalletData: WalletData?, wallet: Wallet? + 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) { @@ -238,7 +233,7 @@ class WalletMiddleware { selectedWalletData?.currencyData?.token?.fiatRate } return PrepareSendScreen( - wallet?.amounts?.get(AmountType.Coin), coinRate, + 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 index 347dce4c7f..d158d3d323 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -6,6 +6,7 @@ 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 @@ -28,16 +29,9 @@ import java.math.BigDecimal class WarningsMiddleware { fun handle(action: WalletAction.Warnings, globalState: GlobalState?) { when (action) { + WalletAction.Warnings.Update -> setWarningMessages() is WalletAction.Warnings.CheckIfNeeded -> { - 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()) - if (!preferencesStorage.wasCardScannedBefore(card.cardId)) { - checkIfWarningNeeded(card, validator)?.let { addWarningMessage(it) } - } - updateWarningMessages() - } + showCardWarningsIfNeeded(globalState) val readyToShow = preferencesStorage.appRatingLaunchObserver.isReadyToShow() if (readyToShow) addWarningMessage(WarningMessagesManager.appRatingWarning(), true) } @@ -68,6 +62,25 @@ class WarningsMiddleware { } } + 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? { @@ -118,11 +131,15 @@ class WarningsMiddleware { private fun addWarningMessage(warning: WarningMessage, autoUpdate: Boolean = false) { store.state.globalState.warningManager?.addWarning(warning) - if (autoUpdate) updateWarningMessages() + if (autoUpdate) setWarningMessages() } - private fun updateWarningMessages() { - val warningManager = store.state.globalState.warningManager ?: return - store.dispatch(WalletAction.Warnings.SetWarnings(warningManager.getWarnings(WarningMessage.Location.MainScreen))) + 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 index 0ce62a948d..158d7e61ff 100644 --- 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 @@ -1,7 +1,6 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.isZero import com.tangem.tap.common.extensions.toFiatString import com.tangem.tap.common.extensions.toFormattedCurrencyString @@ -19,7 +18,7 @@ class MultiWalletReducer { return when (action) { is WalletAction.MultiWallet.AddWalletManagers -> { state.copy( - walletManagers = action.walletManagers + walletManagers = state.walletManagers + action.walletManagers ) } is WalletAction.MultiWallet.AddBlockchains -> { @@ -76,7 +75,6 @@ class MultiWalletReducer { } is WalletAction.MultiWallet.AddTokens -> { if (!state.isMultiwalletAllowed) return state - val walletAddresses = createAddressList(state.getWalletManager(Blockchain.Ethereum.currency)?.wallet) val wallets = action.tokens.map { token -> WalletData( currencyData = BalanceWidgetData( @@ -84,17 +82,23 @@ class MultiWalletReducer { currency = token.name, currencySymbol = token.symbol ), - walletAddresses = walletAddresses, + walletAddresses = createAddressList( + state.getWalletManagerForToken(token.symbol)?.wallet + ), mainButton = WalletMainButton.SendButton(false), topUpState = TopUpState(allowed = false), token = token ) } - state.copy(wallets = state.wallets + wallets) + state.copy(wallets = state.replaceSomeWallets(wallets)) } is WalletAction.MultiWallet.AddToken -> { if (!state.isMultiwalletAllowed) return state - val walletAddresses = createAddressList(state.getWalletManager(Blockchain.Ethereum.currency)?.wallet) + + val walletAddresses = createAddressList( + state.getWalletManagerForToken(action.token.symbol)?.wallet + + ) val wallet = WalletData( currencyData = BalanceWidgetData( BalanceStatus.Loading, @@ -110,21 +114,23 @@ class MultiWalletReducer { state.copy(wallets = wallets) } is WalletAction.MultiWallet.TokenLoaded -> { - val pendingTransactions = state.getWalletManager(Blockchain.Ethereum.currency) + 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 balanceStatus = if (pendingTransactions.isNotEmpty()) { - BalanceStatus.TransactionInProgress - } else { - BalanceStatus.VerifiedOnline + 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 = balanceStatus, + status = tokenBalanceStatus, amount = action.amount.value?.toFormattedCurrencyString( action.amount.decimals, action.amount.currencySymbol ), @@ -156,6 +162,8 @@ class MultiWalletReducer { 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 index 59edbdd896..2b3ad57454 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet import com.tangem.common.extensions.isZero import com.tangem.tap.common.extensions.toFiatString @@ -21,112 +20,86 @@ import com.tangem.tap.store import java.math.RoundingMode class OnWalletLoadedReducer { + fun reduce(wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null): WalletState { - if (!walletState.isMultiwalletAllowed) { - return onSingleWalletLoaded(wallet, walletState, topUpAllowed) + return if (!walletState.isMultiwalletAllowed) { + onSingleWalletLoaded(wallet, walletState, topUpAllowed) } else { - val fiatCurrencySymbol = store.state.globalState.appCurrency - val amount = wallet.amounts[AmountType.Coin]?.value - if (walletState.getWalletData(wallet.blockchain.currency) == null && amount?.isZero() != false) { - return walletState - } - if (wallet.blockchain != Blockchain.Ethereum) { - 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 wallets = walletState.replaceWalletInWallets(newWalletData) - val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { - ProgressState.Loading - } else { - ProgressState.Done - } - return walletState.copy( - state = state, wallets = wallets - ) - } else { - 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 ethereumWalletData = walletState.getWalletData(wallet.blockchain.currency) - ?: WalletData() - val newEthereumWalletData = ethereumWalletData.copy( - currencyData = ethereumWalletData.currencyData.copy( - status = balanceStatus, - amount = formattedAmount, - currency = wallet.blockchain.fullName, - currencySymbol = wallet.blockchain.currency, - fiatAmount = ethereumWalletData.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) - tokenWalletData?.copy( - currencyData = tokenWalletData.currencyData.copy( - status = balanceStatus, - amount = wallet.getTokenAmount(token)?.value?.toFormattedCurrencyString( - token.decimals, token.symbol - ), - fiatAmount = tokenWalletData.fiatRate?.let { - wallet.getTokenAmount(token)?.value - ?.toFiatString(it, fiatCurrencySymbol) - } - ), - pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled) - ) - } - val wallets = walletState.replaceSomeWallets((tokens + newEthereumWalletData).filterNotNull()) - val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { - ProgressState.Loading - } else { - ProgressState.Done - } - return walletState.copy( - state = state, wallets = wallets - ) - } + onMultiWalletLoaded(wallet, walletState, topUpAllowed) } } - private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState, topUpAllowed: Boolean? = null + 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 @@ -176,7 +149,7 @@ class OnWalletLoadedReducer { ) val wallets = walletData?.let { listOf(walletData) } ?: emptyList() return walletState.copy( - state = ProgressState.Done, wallets = wallets + 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/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index f70ba93b11..957e9a32d3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -57,9 +57,16 @@ private fun internalReduce(action: Action, state: AppState): WalletState { 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 -> { @@ -100,7 +107,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) } else { val walletManager = newState.getWalletManager(action.currency) ?: return newState - val currencies = listOf(walletManager.wallet.blockchain.currency + walletManager.presetTokens.map { it.symbol }) + 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( @@ -118,14 +125,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState { 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) -// newState = newState.copy(updatingWallet = newState.pendingTransactions.isNotEmpty()) } - is WalletAction.LoadWallet.NoAccount -> { val walletData = newState.getWalletData(action.wallet.blockchain.currency)?.copy( currencyData = BalanceWidgetData( @@ -135,34 +139,46 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) ) val wallets = newState.replaceWalletInWallets(walletData) - val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { + val progressState = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { ProgressState.Loading } else { ProgressState.Done } newState = newState.copy( - state = state, + 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 = action.errorMessage + errorMessage = message ), topUpState = TopUpState(false) ) - val wallets = newState.replaceWalletInWallets(newWalletData) - val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) { + 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 = state, wallets = wallets + state = progressState, wallets = wallets ) } is WalletAction.SetArtworkId -> { @@ -174,21 +190,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState { newState = newState.copy(cardImage = cardImage) } -// 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.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 -> newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState) is WalletAction.LoadArtwork -> { @@ -246,13 +247,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) newState = newState.copy(wallets = wallets) } - is WalletAction.ScanCardFinished -> { - newState = if (action.scanError == null) { - newState.copy(scanCardFailsCounter = 0) - } else { - newState.copy(scanCardFailsCounter = newState.scanCardFailsCounter + 1) - } - } } return newState } @@ -282,16 +276,10 @@ fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null) private fun handleCheckSignedHashesActions(action: WalletAction.Warnings, state: WalletState): WalletState { return when (action) { - WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline -> state WalletAction.Warnings.CheckHashesCount.ConfirmHashesCount -> state.copy(hashesCountVerified = true) WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline -> state.copy(hashesCountVerified = false) - WalletAction.Warnings.CheckHashesCount.SaveCardId -> state - is WalletAction.Warnings.SetWarnings -> state.copy(mainWarningsList = action.warningList) - WalletAction.Warnings.CheckIfNeeded -> state - WalletAction.Warnings.AppRating -> state - WalletAction.Warnings.CheckHashesCount -> state - WalletAction.Warnings.AppRating.RemindLater -> state - WalletAction.Warnings.AppRating.SetNeverToShow -> state + is WalletAction.Warnings.Set -> state.copy(mainWarningsList = action.warningList) + else -> state } } 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 ed446c3ee5..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 @@ -13,6 +13,7 @@ import java.math.BigDecimal enum class BalanceStatus { VerifiedOnline, TransactionInProgress, + SameCurrencyTransactionInProgress, Unreachable, Loading, NoAccount, 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 index f7b06a5b5e..66425c74a7 100644 --- 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 @@ -5,7 +5,6 @@ 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.redux.global.CryptoCurrencyName import com.tangem.wallet.R class MultipleAddressUiHelper { @@ -20,13 +19,6 @@ class MultipleAddressUiHelper { } } - fun idToType(id: Int, currencyName: CryptoCurrencyName?): AddressType? { - val blockchain = currencyName?.let { currency -> - Blockchain.fromCurrency(currency) - } - return idToType(id, blockchain) - } - fun idToType(id: Int, blockchain: Blockchain?): AddressType? { return when (id) { R.id.chip_default -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 0cfff7bb46..5b59b03015 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -10,21 +10,18 @@ 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.WalletAction -import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletDialog -import com.tangem.tap.features.wallet.redux.WalletState +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.* import kotlinx.android.synthetic.main.fragment_wallet_details.* -import kotlinx.android.synthetic.main.fragment_wallet_details.rv_pending_transaction 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.* @@ -104,6 +101,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS showPendingTransactionsIfPresent(selectedWallet.pendingTransactions) setupAddressCard(selectedWallet) + setupNoInternetHandling(state) setupBalanceData(selectedWallet.currencyData) btn_confirm.isEnabled = selectedWallet.mainButton.enabled @@ -156,7 +154,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS private fun setupAddressCard(state: WalletData) { if (state.walletAddresses != null) { - if (state.shouldShowMultipleAddress()) { + if (state.shouldShowMultipleAddress() && state.blockchain != null) { (card_balance as? ViewGroup)?.beginDelayedTransition() chip_group_address_type.show() chip_group_address_type.fitChipsByGroupWidth() @@ -166,7 +164,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS chip_group_address_type.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener - val type = MultipleAddressUiHelper.idToType(checkedId, state.currencyData.currencySymbol) + val type = MultipleAddressUiHelper.idToType(checkedId, state.blockchain) type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } } else { @@ -178,7 +176,21 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS state.walletAddresses.selectedAddress.exploreUrl, requireContext())) } - iv_qr_code.setImageBitmap(state.walletAddresses.selectedAddress.address.toQrCode()) + 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() } } @@ -193,10 +205,12 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS showStatus(R.id.tv_status_loading) showBalanceWithoutToken(data, false) } - BalanceStatus.VerifiedOnline, BalanceStatus.TransactionInProgress -> { + BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress, + BalanceStatus.TransactionInProgress -> { l_balance.show() l_balance_error.hide() - val statusView = if (data.status == BalanceStatus.VerifiedOnline) { + val statusView = if (data.status == BalanceStatus.VerifiedOnline || + data.status == BalanceStatus.SameCurrencyTransactionInProgress) { R.id.tv_status_verified } else { tv_status_error.text = @@ -233,6 +247,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS ) } } + card_pending_transaction_warning.show(data.status == BalanceStatus.SameCurrencyTransactionInProgress) } private fun showStatus(@IdRes viewRes: Int) { @@ -249,7 +264,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS } } - private fun handleDialogs(walletDialog: WalletDialog?) { + private fun handleDialogs(walletDialog: StateDialog?) { when (walletDialog) { is WalletDialog.SelectAmountToSendDialog -> { if (dialog == null) dialog = AmountToSendDialog(requireContext()).apply { 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 ec1c7cdf74..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 @@ -12,9 +12,9 @@ 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.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 @@ -30,13 +30,13 @@ import com.tangem.tap.features.wallet.ui.wallet.WalletView import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_wallet.* +import kotlinx.android.synthetic.main.fragment_wallet.toolbar +import kotlinx.android.synthetic.main.fragment_wallet_details.* import org.rekotlin.StoreSubscriber class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { - private var snackbar: Snackbar? = null - private lateinit var warningsAdapter: WarningMessagesAdapter private var walletView: WalletView = SingleWalletView() @@ -133,15 +133,14 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber hideWarning() + BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress -> hideWarning() BalanceStatus.Loading -> { hideWarning() if (wallet.currencyData.amount == null) { 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 index 9a8ff9ace5..b3f63a0236 100644 --- 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 @@ -4,6 +4,7 @@ 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 @@ -22,7 +23,10 @@ class ScanFailsDialog { store.dispatch(GlobalAction.SendFeedback(ScanFailsEmail())) } setNegativeButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { store.dispatch(WalletAction.HideDialog) } + setOnDismissListener { + store.dispatch(HomeAction.HideDialog) + store.dispatch(WalletAction.HideDialog) + } }.create() } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 51f47fc82d..9e2320d30c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -4,6 +4,7 @@ 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 @@ -112,7 +113,7 @@ class MultiWalletView : WalletView { } private fun showErrorState( - fragment: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence + fragment: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence, ) { fragment.l_card_balance.show() fragment.l_balance.hide() @@ -131,14 +132,12 @@ class MultiWalletView : WalletView { fragment.btn_confirm_long.text = fragment.getText(R.string.wallet_button_create_wallet) } - private fun handleDialogs(walletDialog: WalletDialog?) { + 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 { - this.show() - } + if (dialog == null) dialog = ScanFailsDialog.create(context).apply { show() } } else -> { dialog?.dismiss() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index cc14fa932e..d6762f815b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -6,6 +6,9 @@ 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.* @@ -105,6 +108,9 @@ class SingleWalletView : WalletView { this.tv_twin_card_number.hide() this.iv_twin_card.hide() } + if (twinCardsState?.showTwinOnboarding == true) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding)) + } } } @@ -156,7 +162,7 @@ class SingleWalletView : WalletView { } private fun setupConfirmButton( - state: WalletData, btnConfirm: Button, fragment: WalletFragment, isTwinsWallet: Boolean + state: WalletData, btnConfirm: Button, fragment: WalletFragment, isTwinsWallet: Boolean, ) { val buttonTitle = when (state.mainButton) { is WalletMainButton.SendButton -> R.string.wallet_button_send @@ -181,7 +187,7 @@ class SingleWalletView : WalletView { private fun setupAddressCard(state: WalletData) { val fragment = fragment ?: return - if (state.walletAddresses != null) { + if (state.walletAddresses != null && state.blockchain != null) { fragment.l_address?.show() if (state.shouldShowMultipleAddress()) { (fragment.l_address as? ViewGroup)?.beginDelayedTransition() @@ -193,7 +199,7 @@ class SingleWalletView : WalletView { fragment.chip_group_address_type.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener - val type = MultipleAddressUiHelper.idToType(checkedId, state.currencyData.currencySymbol) + val type = MultipleAddressUiHelper.idToType(checkedId, state.blockchain) type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } } else { @@ -210,7 +216,7 @@ class SingleWalletView : WalletView { } } - private fun handleDialogs(walletDialog: WalletDialog?) { + private fun handleDialogs(walletDialog: StateDialog?) { val fragment = fragment ?: return val context = fragment.context ?: return when (walletDialog) { @@ -229,9 +235,7 @@ class SingleWalletView : WalletView { } } is WalletDialog.ScanFailsDialog -> { - if (dialog == null) dialog = ScanFailsDialog.create(context).apply { - this.show() - } + if (dialog == null) dialog = ScanFailsDialog.create(context).apply { show() } } null -> { dialog?.dismiss() 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 9d45dd8c36..871e3eb3d5 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -122,7 +122,10 @@ class AppRatingLaunchObserver( if (fundsFoundDate != null) return fundsFoundDate = Calendar.getInstance() - preferences.edit().putLong(K_FUNDS_FOUND_DATE, fundsFoundDate!!.timeInMillis).apply() + preferences.edit(true) { + putLong(K_FUNDS_FOUND_DATE, fundsFoundDate!!.timeInMillis).apply() + putInt(K_SHOW_RATING_AT_LAUNCH_COUNT, launchCounts + firstShowing) + } } fun isReadyToShow(): Boolean { @@ -130,8 +133,8 @@ class AppRatingLaunchObserver( if (!userWasInteractWithRating()) { val diff = Calendar.getInstance().timeInMillis - fundsDate.timeInMillis - val diffInDays = diff / (100 * 60 * 60 * 24) - if (diffInDays >= firstShowing) return true + val diffInDays = diff / (1000 * 60 * 60 * 24) + return launchCounts >= getCounterOfNextShowing() && diffInDays >= firstShowing } val nextShowing = getCounterOfNextShowing() 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/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index b45e3e92ce..00c0626cba 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -258,48 +258,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_security_title" /> - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index 04557df535..af281426f8 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -171,6 +171,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" + android:layout_marginTop="30dp" + app:layout_constraintTop_toBottomOf="@id/l_address" + app:layout_constraintVertical_bias="1" app:layout_constraintBottom_toBottomOf="parent" /> + + + + + + + @@ -107,12 +138,13 @@ android:id="@+id/btn_confirm" style="@style/TapButtonWithIcon" android:layout_width="0dp" - android:layout_marginStart="8dp" + android:layout_marginStart="4dp" android:layout_marginTop="30dp" android:layout_marginEnd="16dp" android:layout_marginBottom="33dp" android:text="@string/wallet_button_send" app:icon="@drawable/ic_send" + app:layout_constraintTop_toBottomOf="@id/barrier" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/guideline" diff --git a/app/src/main/res/layout/item_currency_wallet.xml b/app/src/main/res/layout/item_currency_wallet.xml index 09b7dd4cc9..d4a2a6dfd2 100644 --- a/app/src/main/res/layout/item_currency_wallet.xml +++ b/app/src/main/res/layout/item_currency_wallet.xml @@ -17,7 +17,7 @@ + android:layout_height="76dp"> + + + app:layout_constraintStart_toEndOf="@id/tv_amount" + app:layout_constraintTop_toTopOf="parent" + tools:text="BTC" /> diff --git a/app/src/main/res/layout/layout_wallet_details.xml b/app/src/main/res/layout/layout_wallet_details.xml index d276934193..f8ac54763e 100644 --- a/app/src/main/res/layout/layout_wallet_details.xml +++ b/app/src/main/res/layout/layout_wallet_details.xml @@ -83,7 +83,7 @@ app:layout_constraintEnd_toEndOf="@id/guideline" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/chip_group_address_type" - app:layout_constraintBottom_toBottomOf="@id/btn_copy" + app:layout_constraintBottom_toBottomOf="@id/v_qr_lower_end" tools:src="@drawable/ic_qr_code_show" /> + + 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