diff --git a/assets/images/2.0x/pre_seed_dark.png b/assets/images/2.0x/pre_seed_dark.png new file mode 100755 index 000000000..2bb3b8e65 Binary files /dev/null and b/assets/images/2.0x/pre_seed_dark.png differ diff --git a/assets/images/2.0x/pre_seed_light.png b/assets/images/2.0x/pre_seed_light.png new file mode 100755 index 000000000..b198effad Binary files /dev/null and b/assets/images/2.0x/pre_seed_light.png differ diff --git a/assets/images/2.0x/transfer.png b/assets/images/2.0x/transfer.png new file mode 100644 index 000000000..61be79142 Binary files /dev/null and b/assets/images/2.0x/transfer.png differ diff --git a/assets/images/2.0x/upload.png b/assets/images/2.0x/upload.png new file mode 100644 index 000000000..bb6dfbf44 Binary files /dev/null and b/assets/images/2.0x/upload.png differ diff --git a/assets/images/3.0x/pre_seed_dark.png b/assets/images/3.0x/pre_seed_dark.png new file mode 100755 index 000000000..632f2bb1e Binary files /dev/null and b/assets/images/3.0x/pre_seed_dark.png differ diff --git a/assets/images/3.0x/pre_seed_light.png b/assets/images/3.0x/pre_seed_light.png new file mode 100755 index 000000000..329fce10b Binary files /dev/null and b/assets/images/3.0x/pre_seed_light.png differ diff --git a/assets/images/3.0x/transfer.png b/assets/images/3.0x/transfer.png new file mode 100644 index 000000000..049253da5 Binary files /dev/null and b/assets/images/3.0x/transfer.png differ diff --git a/assets/images/3.0x/upload.png b/assets/images/3.0x/upload.png new file mode 100644 index 000000000..f42a50eca Binary files /dev/null and b/assets/images/3.0x/upload.png differ diff --git a/assets/images/pre_seed_dark.png b/assets/images/pre_seed_dark.png new file mode 100755 index 000000000..ed12c730a Binary files /dev/null and b/assets/images/pre_seed_dark.png differ diff --git a/assets/images/pre_seed_light.png b/assets/images/pre_seed_light.png new file mode 100755 index 000000000..bb95a6567 Binary files /dev/null and b/assets/images/pre_seed_light.png differ diff --git a/assets/images/transfer.png b/assets/images/transfer.png index 383c35fb1..d388f818f 100644 Binary files a/assets/images/transfer.png and b/assets/images/transfer.png differ diff --git a/assets/images/upload.png b/assets/images/upload.png index dba320367..fefd5f660 100644 Binary files a/assets/images/upload.png and b/assets/images/upload.png differ diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 091116f43..ab6767c8d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -354,7 +354,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -371,7 +371,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 4.0.8; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -494,7 +494,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -511,7 +511,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 4.0.8; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -528,7 +528,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -545,7 +545,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 4.0.8; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/bitcoin/address_to_output_script.dart b/lib/bitcoin/address_to_output_script.dart new file mode 100644 index 000000000..2f6698ff0 --- /dev/null +++ b/lib/bitcoin/address_to_output_script.dart @@ -0,0 +1,25 @@ +import 'dart:typed_data'; +import 'package:bs58check/bs58check.dart' as bs58check; +import 'package:bitcoin_flutter/src/utils/constants/op.dart'; +import 'package:bitcoin_flutter/src/utils/script.dart' as bscript; +import 'package:bitcoin_flutter/src/address.dart'; + + +Uint8List p2shAddressToOutputScript(String address) { + final decodeBase58 = bs58check.decode(address); + final hash = decodeBase58.sublist(1); + return bscript.compile([OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]); +} + +Uint8List addressToOutputScript(String address) { + try { + // FIXME: improve validation for p2sh addresses + if (address.startsWith('3')) { + return p2shAddressToOutputScript(address); + } + + return Address.addressToOutputScript(address); + } catch (_) { + return Uint8List(0); + } +} \ No newline at end of file diff --git a/lib/bitcoin/bitcoin_balance.dart b/lib/bitcoin/bitcoin_balance.dart index 6aefe28f4..3cc66bfe8 100644 --- a/lib/bitcoin/bitcoin_balance.dart +++ b/lib/bitcoin/bitcoin_balance.dart @@ -1,11 +1,16 @@ import 'dart:convert'; +import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:flutter/foundation.dart'; import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/entities/balance.dart'; class BitcoinBalance extends Balance { - const BitcoinBalance({@required this.confirmed, @required this.unconfirmed}) : super(); + const BitcoinBalance({@required this.confirmed, @required this.unconfirmed}) + : super(const [ + BalanceDisplayMode.availableBalance, + BalanceDisplayMode.fullBalance + ]); factory BitcoinBalance.fromJSON(String jsonSource) { if (jsonSource == null) { @@ -22,7 +27,10 @@ class BitcoinBalance extends Balance { final int confirmed; final int unconfirmed; - int get total => confirmed + unconfirmed; + int get total => + confirmed + (unconfirmed < 0 ? unconfirmed * -1 : unconfirmed); + + int get availableBalance => confirmed + (unconfirmed < 0 ? unconfirmed : 0); String get confirmedFormatted => bitcoinAmountToString(amount: confirmed); @@ -30,6 +38,21 @@ class BitcoinBalance extends Balance { String get totalFormatted => bitcoinAmountToString(amount: total); + String get availableBalanceFormatted => + bitcoinAmountToString(amount: availableBalance); + + @override + String formattedBalance(BalanceDisplayMode mode) { + switch (mode) { + case BalanceDisplayMode.fullBalance: + return totalFormatted; + case BalanceDisplayMode.availableBalance: + return availableBalanceFormatted; + default: + return null; + } + } + String toJSON() => json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed}); } diff --git a/lib/bitcoin/bitcoin_mnemonic.dart b/lib/bitcoin/bitcoin_mnemonic.dart new file mode 100644 index 000000000..7f4bcd13c --- /dev/null +++ b/lib/bitcoin/bitcoin_mnemonic.dart @@ -0,0 +1,2308 @@ +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; +import 'package:crypto/crypto.dart'; +import 'package:unorm_dart/unorm_dart.dart' as unorm; +import 'package:cryptography/cryptography.dart' as cryptography; + +const segwit = '100'; +final wordlist = englishWordlist; + +Uint8List randomBytes(int length, {bool secure = false}) { + assert(length > 0); + + final random = secure ? Random.secure() : Random(); + final ret = Uint8List(length); + + for (var i = 0; i < length; i++) { + ret[i] = random.nextInt(256); + } + + return ret; +} + +double logBase(num x, num base) => log(x) / log(base); + +String mnemonicEncode(int i) { + var _i = i; + final n = wordlist.length; + final words = []; + + while (_i > 0) { + final x = i % n; + _i = (i / n).floor(); + words.add(wordlist[x]); + } + + return words.join(' '); +} + +int mnemonicDecode(String seed) { + var i = 0; + final n = wordlist.length; + final words = seed.split(' '); + + while (words.length > 0) { + final word = words.removeLast(); + final k = wordlist.indexOf(word); + i = i * n + k; + } + + return i; +} + +bool isNewSeed(String seed, {String prefix = segwit}) { + final hmacSha512 = Hmac(sha512, utf8.encode('Seed version')); + final digest = hmacSha512.convert(utf8.encode(normalizeText(seed))); + final hx = digest.toString(); + return hx.startsWith(prefix.toLowerCase()); +} + +void maskBytes(Uint8List bytes, int bits) { + final skipCount = (bits / 8).floor(); + var lastByte = (1 << bits % 8) - 1; + + for (var i = bytes.length - 1 - skipCount; i >= 0; i--) { + bytes[i] &= lastByte; + + if (lastByte > 0) { + lastByte = 0; + } + } +} + +String bufferToBin(Uint8List data) { + final q1 = data.map((e) => e.toRadixString(2).padLeft(8, '0')); + final q2 = q1.join(''); + return q2; +} + +String encode(Uint8List data) { + final dataBitLen = data.length * 8; + final wordBitLen = logBase(wordlist.length, 2).ceil(); + final wordCount = (dataBitLen / wordBitLen).floor(); + maskBytes(data, wordCount * wordBitLen); + final bin = bufferToBin(data); + final binStr = bin.substring(bin.length - (wordCount * wordBitLen)); + final result = []; + + for (var i = 0; i < wordCount; i++) { + final wordBin = binStr.substring(i * wordBitLen, (i + 1) * wordBitLen); + result.add(wordlist[int.parse(wordBin, radix: 2)]); + } + + return result.join(' '); +} + +List prefixMatches(String source, List prefixes) { + final hmacSha512 = Hmac(sha512, utf8.encode('Seed version')); + final digest = hmacSha512.convert(utf8.encode(normalizeText(source))); + final hx = digest.toString(); + + return prefixes.map((prefix) => hx.startsWith(prefix.toLowerCase())).toList(); +} + +String generateMnemonic({int strength = 132, String prefix = segwit}) { + final wordBitlen = logBase(wordlist.length, 2).ceil(); + final wordCount = strength / wordBitlen; + final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil(); + var result = ''; + + do { + final bytes = randomBytes(byteCount); + maskBytes(bytes, strength); + result = encode(bytes); + } while (!prefixMatches(result, [prefix]).first); + + return result; +} + +Uint8List mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) { + final pbkdf2 = cryptography.Pbkdf2( + macAlgorithm: cryptography.Hmac(cryptography.sha512), + iterations: 2048, + bits: 512); + final text = normalizeText(mnemonic); + + return pbkdf2.deriveBitsSync(text.codeUnits, + nonce: cryptography.Nonce('electrum'.codeUnits)); +} + +bool matchesAnyPrefix(String mnemonic) => + prefixMatches(mnemonic, [segwit]).any((el) => el); + +bool validateMnemonic(String mnemonic, {String prefix = segwit}) { + try { + return matchesAnyPrefix(mnemonic); + } catch(e) { + return false; + } +} + +final COMBININGCODEPOINTS = combiningcodepoints(); + +List combiningcodepoints() { + final source = '300:34e|350:36f|483:487|591:5bd|5bf|5c1|5c2|5c4|5c5|5c7|610:61a|64b:65f|670|' + + '6d6:6dc|6df:6e4|6e7|6e8|6ea:6ed|711|730:74a|7eb:7f3|816:819|81b:823|825:827|' + + '829:82d|859:85b|8d4:8e1|8e3:8ff|93c|94d|951:954|9bc|9cd|a3c|a4d|abc|acd|b3c|' + + 'b4d|bcd|c4d|c55|c56|cbc|ccd|d4d|dca|e38:e3a|e48:e4b|eb8|eb9|ec8:ecb|f18|f19|' + + 'f35|f37|f39|f71|f72|f74|f7a:f7d|f80|f82:f84|f86|f87|fc6|1037|1039|103a|108d|' + + '135d:135f|1714|1734|17d2|17dd|18a9|1939:193b|1a17|1a18|1a60|1a75:1a7c|1a7f|' + + '1ab0:1abd|1b34|1b44|1b6b:1b73|1baa|1bab|1be6|1bf2|1bf3|1c37|1cd0:1cd2|' + + '1cd4:1ce0|1ce2:1ce8|1ced|1cf4|1cf8|1cf9|1dc0:1df5|1dfb:1dff|20d0:20dc|20e1|' + + '20e5:20f0|2cef:2cf1|2d7f|2de0:2dff|302a:302f|3099|309a|a66f|a674:a67d|a69e|' + + 'a69f|a6f0|a6f1|a806|a8c4|a8e0:a8f1|a92b:a92d|a953|a9b3|a9c0|aab0|aab2:aab4|' + + 'aab7|aab8|aabe|aabf|aac1|aaf6|abed|fb1e|fe20:fe2f|101fd|102e0|10376:1037a|' + + '10a0d|10a0f|10a38:10a3a|10a3f|10ae5|10ae6|11046|1107f|110b9|110ba|11100:11102|' + + '11133|11134|11173|111c0|111ca|11235|11236|112e9|112ea|1133c|1134d|11366:1136c|' + + '11370:11374|11442|11446|114c2|114c3|115bf|115c0|1163f|116b6|116b7|1172b|11c3f|' + + '16af0:16af4|16b30:16b36|1bc9e|1d165:1d169|1d16d:1d172|1d17b:1d182|1d185:1d18b|' + + '1d1aa:1d1ad|1d242:1d244|1e000:1e006|1e008:1e018|1e01b:1e021|1e023|1e024|' + + '1e026:1e02a|1e8d0:1e8d6|1e944:1e94a'; + + return source.split('|').map((e) { + if (e.contains(':')) { + return e.split(':').map((hex) => int.parse(hex, radix: 16)); + } + + return int.parse(e, radix: 16); + }).fold([], (List acc, element) { + if (element is List) { + for (var i = element[0] as int; i <= (element[1] as int); i++) {} + } else if (element is int) { + acc.add(element); + } + + return acc; + }).toList(); +} + +String removeCombiningCharacters(String source) { + return source + .split('') + .where((char) => !COMBININGCODEPOINTS.contains(char.codeUnits.first)) + .join(''); +} + +bool isCJK(String char) { + final n = char.codeUnitAt(0); + + for (var x in CJKINTERVALS) { + final imin = x[0] as num; + final imax = x[1] as num; + + if (n >= imin && n <= imax) return true; + } + + return false; +} + +String removeCJKSpaces(String source) { + final splitted = source.split(''); + final filtered = []; + + for (var i = 0; i < splitted.length; i++) { + final char = splitted[i]; + final isSpace = char.trim() == ''; + final prevIsCJK = i != 0 && isCJK(splitted[i - 1]); + final nextIsCJK = i != splitted.length - 1 && isCJK(splitted[i + 1]); + + if (!(isSpace && prevIsCJK && nextIsCJK)) { + filtered.add(char); + } + } + + return filtered.join(''); +} + +String normalizeText(String source) { + final res = removeCombiningCharacters(unorm.nfkd(source).toLowerCase()) + .trim() + .split('/\s+/') + .join(' '); + + return removeCJKSpaces(res); +} + +const CJKINTERVALS = [ + [0x4e00, 0x9fff, 'CJK Unified Ideographs'], + [0x3400, 0x4dbf, 'CJK Unified Ideographs Extension A'], + [0x20000, 0x2a6df, 'CJK Unified Ideographs Extension B'], + [0x2a700, 0x2b73f, 'CJK Unified Ideographs Extension C'], + [0x2b740, 0x2b81f, 'CJK Unified Ideographs Extension D'], + [0xf900, 0xfaff, 'CJK Compatibility Ideographs'], + [0x2f800, 0x2fa1d, 'CJK Compatibility Ideographs Supplement'], + [0x3190, 0x319f, 'Kanbun'], + [0x2e80, 0x2eff, 'CJK Radicals Supplement'], + [0x2f00, 0x2fdf, 'CJK Radicals'], + [0x31c0, 0x31ef, 'CJK Strokes'], + [0x2ff0, 0x2fff, 'Ideographic Description Characters'], + [0xe0100, 0xe01ef, 'Variation Selectors Supplement'], + [0x3100, 0x312f, 'Bopomofo'], + [0x31a0, 0x31bf, 'Bopomofo Extended'], + [0xff00, 0xffef, 'Halfwidth and Fullwidth Forms'], + [0x3040, 0x309f, 'Hiragana'], + [0x30a0, 0x30ff, 'Katakana'], + [0x31f0, 0x31ff, 'Katakana Phonetic Extensions'], + [0x1b000, 0x1b0ff, 'Kana Supplement'], + [0xac00, 0xd7af, 'Hangul Syllables'], + [0x1100, 0x11ff, 'Hangul Jamo'], + [0xa960, 0xa97f, 'Hangul Jamo Extended A'], + [0xd7b0, 0xd7ff, 'Hangul Jamo Extended B'], + [0x3130, 0x318f, 'Hangul Compatibility Jamo'], + [0xa4d0, 0xa4ff, 'Lisu'], + [0x16f00, 0x16f9f, 'Miao'], + [0xa000, 0xa48f, 'Yi Syllables'], + [0xa490, 0xa4cf, 'Yi Radicals'], +]; + +final englishWordlist = [ + 'abandon', + 'ability', + 'able', + 'about', + 'above', + 'absent', + 'absorb', + 'abstract', + 'absurd', + 'abuse', + 'access', + 'accident', + 'account', + 'accuse', + 'achieve', + 'acid', + 'acoustic', + 'acquire', + 'across', + 'act', + 'action', + 'actor', + 'actress', + 'actual', + 'adapt', + 'add', + 'addict', + 'address', + 'adjust', + 'admit', + 'adult', + 'advance', + 'advice', + 'aerobic', + 'affair', + 'afford', + 'afraid', + 'again', + 'age', + 'agent', + 'agree', + 'ahead', + 'aim', + 'air', + 'airport', + 'aisle', + 'alarm', + 'album', + 'alcohol', + 'alert', + 'alien', + 'all', + 'alley', + 'allow', + 'almost', + 'alone', + 'alpha', + 'already', + 'also', + 'alter', + 'always', + 'amateur', + 'amazing', + 'among', + 'amount', + 'amused', + 'analyst', + 'anchor', + 'ancient', + 'anger', + 'angle', + 'angry', + 'animal', + 'ankle', + 'announce', + 'annual', + 'another', + 'answer', + 'antenna', + 'antique', + 'anxiety', + 'any', + 'apart', + 'apology', + 'appear', + 'apple', + 'approve', + 'april', + 'arch', + 'arctic', + 'area', + 'arena', + 'argue', + 'arm', + 'armed', + 'armor', + 'army', + 'around', + 'arrange', + 'arrest', + 'arrive', + 'arrow', + 'art', + 'artefact', + 'artist', + 'artwork', + 'ask', + 'aspect', + 'assault', + 'asset', + 'assist', + 'assume', + 'asthma', + 'athlete', + 'atom', + 'attack', + 'attend', + 'attitude', + 'attract', + 'auction', + 'audit', + 'august', + 'aunt', + 'author', + 'auto', + 'autumn', + 'average', + 'avocado', + 'avoid', + 'awake', + 'aware', + 'away', + 'awesome', + 'awful', + 'awkward', + 'axis', + 'baby', + 'bachelor', + 'bacon', + 'badge', + 'bag', + 'balance', + 'balcony', + 'ball', + 'bamboo', + 'banana', + 'banner', + 'bar', + 'barely', + 'bargain', + 'barrel', + 'base', + 'basic', + 'basket', + 'battle', + 'beach', + 'bean', + 'beauty', + 'because', + 'become', + 'beef', + 'before', + 'begin', + 'behave', + 'behind', + 'believe', + 'below', + 'belt', + 'bench', + 'benefit', + 'best', + 'betray', + 'better', + 'between', + 'beyond', + 'bicycle', + 'bid', + 'bike', + 'bind', + 'biology', + 'bird', + 'birth', + 'bitter', + 'black', + 'blade', + 'blame', + 'blanket', + 'blast', + 'bleak', + 'bless', + 'blind', + 'blood', + 'blossom', + 'blouse', + 'blue', + 'blur', + 'blush', + 'board', + 'boat', + 'body', + 'boil', + 'bomb', + 'bone', + 'bonus', + 'book', + 'boost', + 'border', + 'boring', + 'borrow', + 'boss', + 'bottom', + 'bounce', + 'box', + 'boy', + 'bracket', + 'brain', + 'brand', + 'brass', + 'brave', + 'bread', + 'breeze', + 'brick', + 'bridge', + 'brief', + 'bright', + 'bring', + 'brisk', + 'broccoli', + 'broken', + 'bronze', + 'broom', + 'brother', + 'brown', + 'brush', + 'bubble', + 'buddy', + 'budget', + 'buffalo', + 'build', + 'bulb', + 'bulk', + 'bullet', + 'bundle', + 'bunker', + 'burden', + 'burger', + 'burst', + 'bus', + 'business', + 'busy', + 'butter', + 'buyer', + 'buzz', + 'cabbage', + 'cabin', + 'cable', + 'cactus', + 'cage', + 'cake', + 'call', + 'calm', + 'camera', + 'camp', + 'can', + 'canal', + 'cancel', + 'candy', + 'cannon', + 'canoe', + 'canvas', + 'canyon', + 'capable', + 'capital', + 'captain', + 'car', + 'carbon', + 'card', + 'cargo', + 'carpet', + 'carry', + 'cart', + 'case', + 'cash', + 'casino', + 'castle', + 'casual', + 'cat', + 'catalog', + 'catch', + 'category', + 'cattle', + 'caught', + 'cause', + 'caution', + 'cave', + 'ceiling', + 'celery', + 'cement', + 'census', + 'century', + 'cereal', + 'certain', + 'chair', + 'chalk', + 'champion', + 'change', + 'chaos', + 'chapter', + 'charge', + 'chase', + 'chat', + 'cheap', + 'check', + 'cheese', + 'chef', + 'cherry', + 'chest', + 'chicken', + 'chief', + 'child', + 'chimney', + 'choice', + 'choose', + 'chronic', + 'chuckle', + 'chunk', + 'churn', + 'cigar', + 'cinnamon', + 'circle', + 'citizen', + 'city', + 'civil', + 'claim', + 'clap', + 'clarify', + 'claw', + 'clay', + 'clean', + 'clerk', + 'clever', + 'click', + 'client', + 'cliff', + 'climb', + 'clinic', + 'clip', + 'clock', + 'clog', + 'close', + 'cloth', + 'cloud', + 'clown', + 'club', + 'clump', + 'cluster', + 'clutch', + 'coach', + 'coast', + 'coconut', + 'code', + 'coffee', + 'coil', + 'coin', + 'collect', + 'color', + 'column', + 'combine', + 'come', + 'comfort', + 'comic', + 'common', + 'company', + 'concert', + 'conduct', + 'confirm', + 'congress', + 'connect', + 'consider', + 'control', + 'convince', + 'cook', + 'cool', + 'copper', + 'copy', + 'coral', + 'core', + 'corn', + 'correct', + 'cost', + 'cotton', + 'couch', + 'country', + 'couple', + 'course', + 'cousin', + 'cover', + 'coyote', + 'crack', + 'cradle', + 'craft', + 'cram', + 'crane', + 'crash', + 'crater', + 'crawl', + 'crazy', + 'cream', + 'credit', + 'creek', + 'crew', + 'cricket', + 'crime', + 'crisp', + 'critic', + 'crop', + 'cross', + 'crouch', + 'crowd', + 'crucial', + 'cruel', + 'cruise', + 'crumble', + 'crunch', + 'crush', + 'cry', + 'crystal', + 'cube', + 'culture', + 'cup', + 'cupboard', + 'curious', + 'current', + 'curtain', + 'curve', + 'cushion', + 'custom', + 'cute', + 'cycle', + 'dad', + 'damage', + 'damp', + 'dance', + 'danger', + 'daring', + 'dash', + 'daughter', + 'dawn', + 'day', + 'deal', + 'debate', + 'debris', + 'decade', + 'december', + 'decide', + 'decline', + 'decorate', + 'decrease', + 'deer', + 'defense', + 'define', + 'defy', + 'degree', + 'delay', + 'deliver', + 'demand', + 'demise', + 'denial', + 'dentist', + 'deny', + 'depart', + 'depend', + 'deposit', + 'depth', + 'deputy', + 'derive', + 'describe', + 'desert', + 'design', + 'desk', + 'despair', + 'destroy', + 'detail', + 'detect', + 'develop', + 'device', + 'devote', + 'diagram', + 'dial', + 'diamond', + 'diary', + 'dice', + 'diesel', + 'diet', + 'differ', + 'digital', + 'dignity', + 'dilemma', + 'dinner', + 'dinosaur', + 'direct', + 'dirt', + 'disagree', + 'discover', + 'disease', + 'dish', + 'dismiss', + 'disorder', + 'display', + 'distance', + 'divert', + 'divide', + 'divorce', + 'dizzy', + 'doctor', + 'document', + 'dog', + 'doll', + 'dolphin', + 'domain', + 'donate', + 'donkey', + 'donor', + 'door', + 'dose', + 'double', + 'dove', + 'draft', + 'dragon', + 'drama', + 'drastic', + 'draw', + 'dream', + 'dress', + 'drift', + 'drill', + 'drink', + 'drip', + 'drive', + 'drop', + 'drum', + 'dry', + 'duck', + 'dumb', + 'dune', + 'during', + 'dust', + 'dutch', + 'duty', + 'dwarf', + 'dynamic', + 'eager', + 'eagle', + 'early', + 'earn', + 'earth', + 'easily', + 'east', + 'easy', + 'echo', + 'ecology', + 'economy', + 'edge', + 'edit', + 'educate', + 'effort', + 'egg', + 'eight', + 'either', + 'elbow', + 'elder', + 'electric', + 'elegant', + 'element', + 'elephant', + 'elevator', + 'elite', + 'else', + 'embark', + 'embody', + 'embrace', + 'emerge', + 'emotion', + 'employ', + 'empower', + 'empty', + 'enable', + 'enact', + 'end', + 'endless', + 'endorse', + 'enemy', + 'energy', + 'enforce', + 'engage', + 'engine', + 'enhance', + 'enjoy', + 'enlist', + 'enough', + 'enrich', + 'enroll', + 'ensure', + 'enter', + 'entire', + 'entry', + 'envelope', + 'episode', + 'equal', + 'equip', + 'era', + 'erase', + 'erode', + 'erosion', + 'error', + 'erupt', + 'escape', + 'essay', + 'essence', + 'estate', + 'eternal', + 'ethics', + 'evidence', + 'evil', + 'evoke', + 'evolve', + 'exact', + 'example', + 'excess', + 'exchange', + 'excite', + 'exclude', + 'excuse', + 'execute', + 'exercise', + 'exhaust', + 'exhibit', + 'exile', + 'exist', + 'exit', + 'exotic', + 'expand', + 'expect', + 'expire', + 'explain', + 'expose', + 'express', + 'extend', + 'extra', + 'eye', + 'eyebrow', + 'fabric', + 'face', + 'faculty', + 'fade', + 'faint', + 'faith', + 'fall', + 'false', + 'fame', + 'family', + 'famous', + 'fan', + 'fancy', + 'fantasy', + 'farm', + 'fashion', + 'fat', + 'fatal', + 'father', + 'fatigue', + 'fault', + 'favorite', + 'feature', + 'february', + 'federal', + 'fee', + 'feed', + 'feel', + 'female', + 'fence', + 'festival', + 'fetch', + 'fever', + 'few', + 'fiber', + 'fiction', + 'field', + 'figure', + 'file', + 'film', + 'filter', + 'final', + 'find', + 'fine', + 'finger', + 'finish', + 'fire', + 'firm', + 'first', + 'fiscal', + 'fish', + 'fit', + 'fitness', + 'fix', + 'flag', + 'flame', + 'flash', + 'flat', + 'flavor', + 'flee', + 'flight', + 'flip', + 'float', + 'flock', + 'floor', + 'flower', + 'fluid', + 'flush', + 'fly', + 'foam', + 'focus', + 'fog', + 'foil', + 'fold', + 'follow', + 'food', + 'foot', + 'force', + 'forest', + 'forget', + 'fork', + 'fortune', + 'forum', + 'forward', + 'fossil', + 'foster', + 'found', + 'fox', + 'fragile', + 'frame', + 'frequent', + 'fresh', + 'friend', + 'fringe', + 'frog', + 'front', + 'frost', + 'frown', + 'frozen', + 'fruit', + 'fuel', + 'fun', + 'funny', + 'furnace', + 'fury', + 'future', + 'gadget', + 'gain', + 'galaxy', + 'gallery', + 'game', + 'gap', + 'garage', + 'garbage', + 'garden', + 'garlic', + 'garment', + 'gas', + 'gasp', + 'gate', + 'gather', + 'gauge', + 'gaze', + 'general', + 'genius', + 'genre', + 'gentle', + 'genuine', + 'gesture', + 'ghost', + 'giant', + 'gift', + 'giggle', + 'ginger', + 'giraffe', + 'girl', + 'give', + 'glad', + 'glance', + 'glare', + 'glass', + 'glide', + 'glimpse', + 'globe', + 'gloom', + 'glory', + 'glove', + 'glow', + 'glue', + 'goat', + 'goddess', + 'gold', + 'good', + 'goose', + 'gorilla', + 'gospel', + 'gossip', + 'govern', + 'gown', + 'grab', + 'grace', + 'grain', + 'grant', + 'grape', + 'grass', + 'gravity', + 'great', + 'green', + 'grid', + 'grief', + 'grit', + 'grocery', + 'group', + 'grow', + 'grunt', + 'guard', + 'guess', + 'guide', + 'guilt', + 'guitar', + 'gun', + 'gym', + 'habit', + 'hair', + 'half', + 'hammer', + 'hamster', + 'hand', + 'happy', + 'harbor', + 'hard', + 'harsh', + 'harvest', + 'hat', + 'have', + 'hawk', + 'hazard', + 'head', + 'health', + 'heart', + 'heavy', + 'hedgehog', + 'height', + 'hello', + 'helmet', + 'help', + 'hen', + 'hero', + 'hidden', + 'high', + 'hill', + 'hint', + 'hip', + 'hire', + 'history', + 'hobby', + 'hockey', + 'hold', + 'hole', + 'holiday', + 'hollow', + 'home', + 'honey', + 'hood', + 'hope', + 'horn', + 'horror', + 'horse', + 'hospital', + 'host', + 'hotel', + 'hour', + 'hover', + 'hub', + 'huge', + 'human', + 'humble', + 'humor', + 'hundred', + 'hungry', + 'hunt', + 'hurdle', + 'hurry', + 'hurt', + 'husband', + 'hybrid', + 'ice', + 'icon', + 'idea', + 'identify', + 'idle', + 'ignore', + 'ill', + 'illegal', + 'illness', + 'image', + 'imitate', + 'immense', + 'immune', + 'impact', + 'impose', + 'improve', + 'impulse', + 'inch', + 'include', + 'income', + 'increase', + 'index', + 'indicate', + 'indoor', + 'industry', + 'infant', + 'inflict', + 'inform', + 'inhale', + 'inherit', + 'initial', + 'inject', + 'injury', + 'inmate', + 'inner', + 'innocent', + 'input', + 'inquiry', + 'insane', + 'insect', + 'inside', + 'inspire', + 'install', + 'intact', + 'interest', + 'into', + 'invest', + 'invite', + 'involve', + 'iron', + 'island', + 'isolate', + 'issue', + 'item', + 'ivory', + 'jacket', + 'jaguar', + 'jar', + 'jazz', + 'jealous', + 'jeans', + 'jelly', + 'jewel', + 'job', + 'join', + 'joke', + 'journey', + 'joy', + 'judge', + 'juice', + 'jump', + 'jungle', + 'junior', + 'junk', + 'just', + 'kangaroo', + 'keen', + 'keep', + 'ketchup', + 'key', + 'kick', + 'kid', + 'kidney', + 'kind', + 'kingdom', + 'kiss', + 'kit', + 'kitchen', + 'kite', + 'kitten', + 'kiwi', + 'knee', + 'knife', + 'knock', + 'know', + 'lab', + 'label', + 'labor', + 'ladder', + 'lady', + 'lake', + 'lamp', + 'language', + 'laptop', + 'large', + 'later', + 'latin', + 'laugh', + 'laundry', + 'lava', + 'law', + 'lawn', + 'lawsuit', + 'layer', + 'lazy', + 'leader', + 'leaf', + 'learn', + 'leave', + 'lecture', + 'left', + 'leg', + 'legal', + 'legend', + 'leisure', + 'lemon', + 'lend', + 'length', + 'lens', + 'leopard', + 'lesson', + 'letter', + 'level', + 'liar', + 'liberty', + 'library', + 'license', + 'life', + 'lift', + 'light', + 'like', + 'limb', + 'limit', + 'link', + 'lion', + 'liquid', + 'list', + 'little', + 'live', + 'lizard', + 'load', + 'loan', + 'lobster', + 'local', + 'lock', + 'logic', + 'lonely', + 'long', + 'loop', + 'lottery', + 'loud', + 'lounge', + 'love', + 'loyal', + 'lucky', + 'luggage', + 'lumber', + 'lunar', + 'lunch', + 'luxury', + 'lyrics', + 'machine', + 'mad', + 'magic', + 'magnet', + 'maid', + 'mail', + 'main', + 'major', + 'make', + 'mammal', + 'man', + 'manage', + 'mandate', + 'mango', + 'mansion', + 'manual', + 'maple', + 'marble', + 'march', + 'margin', + 'marine', + 'market', + 'marriage', + 'mask', + 'mass', + 'master', + 'match', + 'material', + 'math', + 'matrix', + 'matter', + 'maximum', + 'maze', + 'meadow', + 'mean', + 'measure', + 'meat', + 'mechanic', + 'medal', + 'media', + 'melody', + 'melt', + 'member', + 'memory', + 'mention', + 'menu', + 'mercy', + 'merge', + 'merit', + 'merry', + 'mesh', + 'message', + 'metal', + 'method', + 'middle', + 'midnight', + 'milk', + 'million', + 'mimic', + 'mind', + 'minimum', + 'minor', + 'minute', + 'miracle', + 'mirror', + 'misery', + 'miss', + 'mistake', + 'mix', + 'mixed', + 'mixture', + 'mobile', + 'model', + 'modify', + 'mom', + 'moment', + 'monitor', + 'monkey', + 'monster', + 'month', + 'moon', + 'moral', + 'more', + 'morning', + 'mosquito', + 'mother', + 'motion', + 'motor', + 'mountain', + 'mouse', + 'move', + 'movie', + 'much', + 'muffin', + 'mule', + 'multiply', + 'muscle', + 'museum', + 'mushroom', + 'music', + 'must', + 'mutual', + 'myself', + 'mystery', + 'myth', + 'naive', + 'name', + 'napkin', + 'narrow', + 'nasty', + 'nation', + 'nature', + 'near', + 'neck', + 'need', + 'negative', + 'neglect', + 'neither', + 'nephew', + 'nerve', + 'nest', + 'net', + 'network', + 'neutral', + 'never', + 'news', + 'next', + 'nice', + 'night', + 'noble', + 'noise', + 'nominee', + 'noodle', + 'normal', + 'north', + 'nose', + 'notable', + 'note', + 'nothing', + 'notice', + 'novel', + 'now', + 'nuclear', + 'number', + 'nurse', + 'nut', + 'oak', + 'obey', + 'object', + 'oblige', + 'obscure', + 'observe', + 'obtain', + 'obvious', + 'occur', + 'ocean', + 'october', + 'odor', + 'off', + 'offer', + 'office', + 'often', + 'oil', + 'okay', + 'old', + 'olive', + 'olympic', + 'omit', + 'once', + 'one', + 'onion', + 'online', + 'only', + 'open', + 'opera', + 'opinion', + 'oppose', + 'option', + 'orange', + 'orbit', + 'orchard', + 'order', + 'ordinary', + 'organ', + 'orient', + 'original', + 'orphan', + 'ostrich', + 'other', + 'outdoor', + 'outer', + 'output', + 'outside', + 'oval', + 'oven', + 'over', + 'own', + 'owner', + 'oxygen', + 'oyster', + 'ozone', + 'pact', + 'paddle', + 'page', + 'pair', + 'palace', + 'palm', + 'panda', + 'panel', + 'panic', + 'panther', + 'paper', + 'parade', + 'parent', + 'park', + 'parrot', + 'party', + 'pass', + 'patch', + 'path', + 'patient', + 'patrol', + 'pattern', + 'pause', + 'pave', + 'payment', + 'peace', + 'peanut', + 'pear', + 'peasant', + 'pelican', + 'pen', + 'penalty', + 'pencil', + 'people', + 'pepper', + 'perfect', + 'permit', + 'person', + 'pet', + 'phone', + 'photo', + 'phrase', + 'physical', + 'piano', + 'picnic', + 'picture', + 'piece', + 'pig', + 'pigeon', + 'pill', + 'pilot', + 'pink', + 'pioneer', + 'pipe', + 'pistol', + 'pitch', + 'pizza', + 'place', + 'planet', + 'plastic', + 'plate', + 'play', + 'please', + 'pledge', + 'pluck', + 'plug', + 'plunge', + 'poem', + 'poet', + 'point', + 'polar', + 'pole', + 'police', + 'pond', + 'pony', + 'pool', + 'popular', + 'portion', + 'position', + 'possible', + 'post', + 'potato', + 'pottery', + 'poverty', + 'powder', + 'power', + 'practice', + 'praise', + 'predict', + 'prefer', + 'prepare', + 'present', + 'pretty', + 'prevent', + 'price', + 'pride', + 'primary', + 'print', + 'priority', + 'prison', + 'private', + 'prize', + 'problem', + 'process', + 'produce', + 'profit', + 'program', + 'project', + 'promote', + 'proof', + 'property', + 'prosper', + 'protect', + 'proud', + 'provide', + 'public', + 'pudding', + 'pull', + 'pulp', + 'pulse', + 'pumpkin', + 'punch', + 'pupil', + 'puppy', + 'purchase', + 'purity', + 'purpose', + 'purse', + 'push', + 'put', + 'puzzle', + 'pyramid', + 'quality', + 'quantum', + 'quarter', + 'question', + 'quick', + 'quit', + 'quiz', + 'quote', + 'rabbit', + 'raccoon', + 'race', + 'rack', + 'radar', + 'radio', + 'rail', + 'rain', + 'raise', + 'rally', + 'ramp', + 'ranch', + 'random', + 'range', + 'rapid', + 'rare', + 'rate', + 'rather', + 'raven', + 'raw', + 'razor', + 'ready', + 'real', + 'reason', + 'rebel', + 'rebuild', + 'recall', + 'receive', + 'recipe', + 'record', + 'recycle', + 'reduce', + 'reflect', + 'reform', + 'refuse', + 'region', + 'regret', + 'regular', + 'reject', + 'relax', + 'release', + 'relief', + 'rely', + 'remain', + 'remember', + 'remind', + 'remove', + 'render', + 'renew', + 'rent', + 'reopen', + 'repair', + 'repeat', + 'replace', + 'report', + 'require', + 'rescue', + 'resemble', + 'resist', + 'resource', + 'response', + 'result', + 'retire', + 'retreat', + 'return', + 'reunion', + 'reveal', + 'review', + 'reward', + 'rhythm', + 'rib', + 'ribbon', + 'rice', + 'rich', + 'ride', + 'ridge', + 'rifle', + 'right', + 'rigid', + 'ring', + 'riot', + 'ripple', + 'risk', + 'ritual', + 'rival', + 'river', + 'road', + 'roast', + 'robot', + 'robust', + 'rocket', + 'romance', + 'roof', + 'rookie', + 'room', + 'rose', + 'rotate', + 'rough', + 'round', + 'route', + 'royal', + 'rubber', + 'rude', + 'rug', + 'rule', + 'run', + 'runway', + 'rural', + 'sad', + 'saddle', + 'sadness', + 'safe', + 'sail', + 'salad', + 'salmon', + 'salon', + 'salt', + 'salute', + 'same', + 'sample', + 'sand', + 'satisfy', + 'satoshi', + 'sauce', + 'sausage', + 'save', + 'say', + 'scale', + 'scan', + 'scare', + 'scatter', + 'scene', + 'scheme', + 'school', + 'science', + 'scissors', + 'scorpion', + 'scout', + 'scrap', + 'screen', + 'script', + 'scrub', + 'sea', + 'search', + 'season', + 'seat', + 'second', + 'secret', + 'section', + 'security', + 'seed', + 'seek', + 'segment', + 'select', + 'sell', + 'seminar', + 'senior', + 'sense', + 'sentence', + 'series', + 'service', + 'session', + 'settle', + 'setup', + 'seven', + 'shadow', + 'shaft', + 'shallow', + 'share', + 'shed', + 'shell', + 'sheriff', + 'shield', + 'shift', + 'shine', + 'ship', + 'shiver', + 'shock', + 'shoe', + 'shoot', + 'shop', + 'short', + 'shoulder', + 'shove', + 'shrimp', + 'shrug', + 'shuffle', + 'shy', + 'sibling', + 'sick', + 'side', + 'siege', + 'sight', + 'sign', + 'silent', + 'silk', + 'silly', + 'silver', + 'similar', + 'simple', + 'since', + 'sing', + 'siren', + 'sister', + 'situate', + 'six', + 'size', + 'skate', + 'sketch', + 'ski', + 'skill', + 'skin', + 'skirt', + 'skull', + 'slab', + 'slam', + 'sleep', + 'slender', + 'slice', + 'slide', + 'slight', + 'slim', + 'slogan', + 'slot', + 'slow', + 'slush', + 'small', + 'smart', + 'smile', + 'smoke', + 'smooth', + 'snack', + 'snake', + 'snap', + 'sniff', + 'snow', + 'soap', + 'soccer', + 'social', + 'sock', + 'soda', + 'soft', + 'solar', + 'soldier', + 'solid', + 'solution', + 'solve', + 'someone', + 'song', + 'soon', + 'sorry', + 'sort', + 'soul', + 'sound', + 'soup', + 'source', + 'south', + 'space', + 'spare', + 'spatial', + 'spawn', + 'speak', + 'special', + 'speed', + 'spell', + 'spend', + 'sphere', + 'spice', + 'spider', + 'spike', + 'spin', + 'spirit', + 'split', + 'spoil', + 'sponsor', + 'spoon', + 'sport', + 'spot', + 'spray', + 'spread', + 'spring', + 'spy', + 'square', + 'squeeze', + 'squirrel', + 'stable', + 'stadium', + 'staff', + 'stage', + 'stairs', + 'stamp', + 'stand', + 'start', + 'state', + 'stay', + 'steak', + 'steel', + 'stem', + 'step', + 'stereo', + 'stick', + 'still', + 'sting', + 'stock', + 'stomach', + 'stone', + 'stool', + 'story', + 'stove', + 'strategy', + 'street', + 'strike', + 'strong', + 'struggle', + 'student', + 'stuff', + 'stumble', + 'style', + 'subject', + 'submit', + 'subway', + 'success', + 'such', + 'sudden', + 'suffer', + 'sugar', + 'suggest', + 'suit', + 'summer', + 'sun', + 'sunny', + 'sunset', + 'super', + 'supply', + 'supreme', + 'sure', + 'surface', + 'surge', + 'surprise', + 'surround', + 'survey', + 'suspect', + 'sustain', + 'swallow', + 'swamp', + 'swap', + 'swarm', + 'swear', + 'sweet', + 'swift', + 'swim', + 'swing', + 'switch', + 'sword', + 'symbol', + 'symptom', + 'syrup', + 'system', + 'table', + 'tackle', + 'tag', + 'tail', + 'talent', + 'talk', + 'tank', + 'tape', + 'target', + 'task', + 'taste', + 'tattoo', + 'taxi', + 'teach', + 'team', + 'tell', + 'ten', + 'tenant', + 'tennis', + 'tent', + 'term', + 'test', + 'text', + 'thank', + 'that', + 'theme', + 'then', + 'theory', + 'there', + 'they', + 'thing', + 'this', + 'thought', + 'three', + 'thrive', + 'throw', + 'thumb', + 'thunder', + 'ticket', + 'tide', + 'tiger', + 'tilt', + 'timber', + 'time', + 'tiny', + 'tip', + 'tired', + 'tissue', + 'title', + 'toast', + 'tobacco', + 'today', + 'toddler', + 'toe', + 'together', + 'toilet', + 'token', + 'tomato', + 'tomorrow', + 'tone', + 'tongue', + 'tonight', + 'tool', + 'tooth', + 'top', + 'topic', + 'topple', + 'torch', + 'tornado', + 'tortoise', + 'toss', + 'total', + 'tourist', + 'toward', + 'tower', + 'town', + 'toy', + 'track', + 'trade', + 'traffic', + 'tragic', + 'train', + 'transfer', + 'trap', + 'trash', + 'travel', + 'tray', + 'treat', + 'tree', + 'trend', + 'trial', + 'tribe', + 'trick', + 'trigger', + 'trim', + 'trip', + 'trophy', + 'trouble', + 'truck', + 'true', + 'truly', + 'trumpet', + 'trust', + 'truth', + 'try', + 'tube', + 'tuition', + 'tumble', + 'tuna', + 'tunnel', + 'turkey', + 'turn', + 'turtle', + 'twelve', + 'twenty', + 'twice', + 'twin', + 'twist', + 'two', + 'type', + 'typical', + 'ugly', + 'umbrella', + 'unable', + 'unaware', + 'uncle', + 'uncover', + 'under', + 'undo', + 'unfair', + 'unfold', + 'unhappy', + 'uniform', + 'unique', + 'unit', + 'universe', + 'unknown', + 'unlock', + 'until', + 'unusual', + 'unveil', + 'update', + 'upgrade', + 'uphold', + 'upon', + 'upper', + 'upset', + 'urban', + 'urge', + 'usage', + 'use', + 'used', + 'useful', + 'useless', + 'usual', + 'utility', + 'vacant', + 'vacuum', + 'vague', + 'valid', + 'valley', + 'valve', + 'van', + 'vanish', + 'vapor', + 'various', + 'vast', + 'vault', + 'vehicle', + 'velvet', + 'vendor', + 'venture', + 'venue', + 'verb', + 'verify', + 'version', + 'very', + 'vessel', + 'veteran', + 'viable', + 'vibrant', + 'vicious', + 'victory', + 'video', + 'view', + 'village', + 'vintage', + 'violin', + 'virtual', + 'virus', + 'visa', + 'visit', + 'visual', + 'vital', + 'vivid', + 'vocal', + 'voice', + 'void', + 'volcano', + 'volume', + 'vote', + 'voyage', + 'wage', + 'wagon', + 'wait', + 'walk', + 'wall', + 'walnut', + 'want', + 'warfare', + 'warm', + 'warrior', + 'wash', + 'wasp', + 'waste', + 'water', + 'wave', + 'way', + 'wealth', + 'weapon', + 'wear', + 'weasel', + 'weather', + 'web', + 'wedding', + 'weekend', + 'weird', + 'welcome', + 'west', + 'wet', + 'whale', + 'what', + 'wheat', + 'wheel', + 'when', + 'where', + 'whip', + 'whisper', + 'wide', + 'width', + 'wife', + 'wild', + 'will', + 'win', + 'window', + 'wine', + 'wing', + 'wink', + 'winner', + 'winter', + 'wire', + 'wisdom', + 'wise', + 'wish', + 'witness', + 'wolf', + 'woman', + 'wonder', + 'wood', + 'wool', + 'word', + 'work', + 'world', + 'worry', + 'worth', + 'wrap', + 'wreck', + 'wrestle', + 'wrist', + 'write', + 'wrong', + 'yard', + 'year', + 'yellow', + 'you', + 'young', + 'youth', + 'zebra', + 'zero', + 'zone', + 'zoo' +]; diff --git a/lib/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart b/lib/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart new file mode 100644 index 000000000..761b02601 --- /dev/null +++ b/lib/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart @@ -0,0 +1,5 @@ +class BitcoinMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Bitcoin mnemonic has incorrect format. Mnemonic should contain 12 words separated by space.'; +} diff --git a/lib/bitcoin/bitcoin_transaction_history.dart b/lib/bitcoin/bitcoin_transaction_history.dart index c687c0ba4..09cefe5d4 100644 --- a/lib/bitcoin/bitcoin_transaction_history.dart +++ b/lib/bitcoin/bitcoin_transaction_history.dart @@ -65,7 +65,7 @@ abstract class BitcoinTransactionHistoryBase return historiesWithDetails.fold>( {}, (acc, tx) { - acc[tx.id] = tx; + acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx; return acc; }); } @@ -103,10 +103,6 @@ abstract class BitcoinTransactionHistoryBase Future save() async { final data = json.encode({'height': _height, 'transactions': transactions}); - - print('data'); - print(data); - await writeData(path: path, password: _password, data: data); } @@ -168,7 +164,9 @@ abstract class BitcoinTransactionHistoryBase }); _height = content['height'] as int; - } catch (_) {} + } catch (e) { + print(e); + } } void _updateOrInsert(BitcoinTransactionInfo transaction) { diff --git a/lib/bitcoin/bitcoin_transaction_info.dart b/lib/bitcoin/bitcoin_transaction_info.dart index 29cb8521e..f9051a5e0 100644 --- a/lib/bitcoin/bitcoin_transaction_info.dart +++ b/lib/bitcoin/bitcoin_transaction_info.dart @@ -12,6 +12,7 @@ class BitcoinTransactionInfo extends TransactionInfo { {@required String id, @required int height, @required int amount, + @required int fee, @required TransactionDirection direction, @required bool isPending, @required DateTime date, @@ -19,6 +20,7 @@ class BitcoinTransactionInfo extends TransactionInfo { this.id = id; this.height = height; this.amount = amount; + this.fee = fee; this.direction = direction; this.date = date; this.isPending = isPending; @@ -36,37 +38,42 @@ class BitcoinTransactionInfo extends TransactionInfo { : DateTime.now(); final confirmations = obj['confirmations'] as int ?? 0; var direction = TransactionDirection.incoming; + var inputsAmount = 0; + var amount = 0; + var totalOutAmount = 0; for (dynamic vin in vins) { final vout = vin['vout'] as int; final out = vin['tx']['vout'][vout] as Map; final outAddresses = (out['scriptPubKey']['addresses'] as List)?.toSet(); + inputsAmount += doubleToBitcoinAmount(out['value'] as double ?? 0); if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) { direction = TransactionDirection.outgoing; - break; } } - final amount = vout.fold(0, (int acc, dynamic out) { + for (dynamic out in vout) { final outAddresses = out['scriptPubKey']['addresses'] as List ?? []; final ntrs = outAddresses.toSet().intersection(addressesSet); - var amount = acc; + final value = doubleToBitcoinAmount(out['value'] as double ?? 0.0); + totalOutAmount += value; if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) || (direction == TransactionDirection.outgoing && ntrs.isEmpty)) { - amount += doubleToBitcoinAmount(out['value'] as double ?? 0.0); + amount += value; } + } - return amount; - }); + final fee = inputsAmount - totalOutAmount; return BitcoinTransactionInfo( id: id, height: height, isPending: false, + fee: fee, direction: direction, amount: amount, date: date, @@ -101,6 +108,7 @@ class BitcoinTransactionInfo extends TransactionInfo { id: tx.getId(), height: height, isPending: false, + fee: null, direction: TransactionDirection.incoming, amount: amount, date: date, @@ -112,6 +120,7 @@ class BitcoinTransactionInfo extends TransactionInfo { id: data['id'] as String, height: data['height'] as int, amount: data['amount'] as int, + fee: data['fee'] as int, direction: parseTransactionDirectionFromInt(data['direction'] as int), date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), isPending: data['isPending'] as bool, @@ -124,12 +133,29 @@ class BitcoinTransactionInfo extends TransactionInfo { String amountFormatted() => '${formatAmount(bitcoinAmountToString(amount: amount))} BTC'; + @override + String feeFormatted() => fee != null + ? '${formatAmount(bitcoinAmountToString(amount: fee))} BTC' + : ''; + @override String fiatAmount() => _fiatAmount ?? ''; @override void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); + BitcoinTransactionInfo updated(BitcoinTransactionInfo info) { + return BitcoinTransactionInfo( + id: id, + height: info.height, + amount: info.amount, + fee: info.fee, + direction: direction ?? info.direction, + date: date ?? info.date, + isPending: isPending ?? info.isPending, + confirmations: info.confirmations); + } + Map toJson() { final m = {}; m['id'] = id; @@ -139,6 +165,7 @@ class BitcoinTransactionInfo extends TransactionInfo { m['date'] = date.millisecondsSinceEpoch; m['isPending'] = isPending; m['confirmations'] = confirmations; + m['fee'] = fee; return m; } } diff --git a/lib/bitcoin/bitcoin_wallet.dart b/lib/bitcoin/bitcoin_wallet.dart index e43c0aeaa..052440f1e 100644 --- a/lib/bitcoin/bitcoin_wallet.dart +++ b/lib/bitcoin/bitcoin_wallet.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'dart:convert'; +import 'package:cake_wallet/bitcoin/address_to_output_script.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; import 'package:mobx/mobx.dart'; -import 'package:bip39/bip39.dart' as bip39; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; @@ -42,8 +43,9 @@ abstract class BitcoinWalletBase extends WalletBase with Store { BitcoinBalance initialBalance}) : balance = initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0), - hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic), - network: bitcoin.bitcoin), + hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic), + network: bitcoin.bitcoin) + .derivePath("m/0'/0"), addresses = initialAddresses != null ? ObservableList.of(initialAddresses) : ObservableList(), @@ -58,6 +60,7 @@ abstract class BitcoinWalletBase extends WalletBase with Store { {@required String password, @required String name, @required String dirPath, + @required WalletInfo walletInfo, String jsonSource}) { final data = json.decode(jsonSource) as Map; final mnemonic = data['mnemonic'] as String; @@ -83,7 +86,8 @@ abstract class BitcoinWalletBase extends WalletBase with Store { name: name, accountIndex: accountIndex, initialAddresses: addresses, - initialBalance: balance); + initialBalance: balance, + walletInfo: walletInfo); } static BitcoinWallet build( @@ -91,6 +95,7 @@ abstract class BitcoinWalletBase extends WalletBase with Store { @required String password, @required String name, @required String dirPath, + @required WalletInfo walletInfo, List initialAddresses, BitcoinBalance initialBalance, int accountIndex = 0}) { @@ -107,7 +112,8 @@ abstract class BitcoinWalletBase extends WalletBase with Store { accountIndex: accountIndex, initialAddresses: initialAddresses, initialBalance: initialBalance, - transactionHistory: history); + transactionHistory: history, + walletInfo: walletInfo); } @override @@ -170,6 +176,22 @@ abstract class BitcoinWalletBase extends WalletBase with Store { return address; } + Future> generateNewAddresses(int count) async { + final list = []; + + for (var i = 0; i < count; i++) { + _accountIndex += 1; + final address = BitcoinAddressRecord(_getAddress(index: _accountIndex), + index: _accountIndex, label: null); + list.add(address); + } + + addresses.addAll(list); + await save(); + + return list; + } + Future updateAddress(String address, {String label}) async { for (final addr in addresses) { if (addr.address == address) { @@ -185,8 +207,10 @@ abstract class BitcoinWalletBase extends WalletBase with Store { Future startSync() async { try { syncStatus = StartingSyncStatus(); - transactionHistory.updateAsync( - onFinished: () => print('transactionHistory update finished!')); + transactionHistory.updateAsync(onFinished: () { + print('transactionHistory update finished!'); + transactionHistory.save(); + }); _subscribeForUpdates(); await _updateBalance(); syncStatus = SyncedSyncStatus(); @@ -238,9 +262,8 @@ abstract class BitcoinWalletBase extends WalletBase with Store { final utxs = await unptsFutures; for (final utx in utxs) { - final inAmount = utx.value > totalAmount ? totalAmount : utx.value; - leftAmount = leftAmount - inAmount; - totalInputAmount += inAmount; + leftAmount = leftAmount - utx.value; + totalInputAmount += utx.value; inputs.add(utx); if (leftAmount <= 0) { @@ -279,7 +302,8 @@ abstract class BitcoinWalletBase extends WalletBase with Store { } }); - txb.addOutput(transactionCredentials.address, amount); + txb.addOutput( + addressToOutputScript(transactionCredentials.address), amount); if (changeValue > 0) { txb.addOutput(changeAddress, changeValue); @@ -295,7 +319,10 @@ abstract class BitcoinWalletBase extends WalletBase with Store { return PendingBitcoinTransaction(txb.build(), eclient: eclient, amount: amount, fee: fee) - ..addListener((transaction) => transactionHistory.addOne(transaction)); + ..addListener((transaction) async { + transactionHistory.addOne(transaction); + await _updateBalance(); + }); } String toJSON() => json.encode({ @@ -310,8 +337,10 @@ abstract class BitcoinWalletBase extends WalletBase with Store { bitcoinAmountToDouble(amount: _feeMultiplier(priority)); @override - Future save() async => - await write(path: path, password: _password, data: toJSON()); + Future save() async { + await write(path: path, password: _password, data: toJSON()); + await transactionHistory.save(); + } bitcoin.ECPair keyPairFor({@required int index}) => generateKeyPair(hd: hd, index: index); @@ -321,13 +350,18 @@ abstract class BitcoinWalletBase extends WalletBase with Store { // FIXME: Unimplemented } + @override + void close() async{ + await eclient.close(); + } + void _subscribeForUpdates() { scriptHashes.forEach((sh) async { await _scripthashesUpdateSubject[sh]?.close(); _scripthashesUpdateSubject[sh] = eclient.scripthashUpdate(sh); _scripthashesUpdateSubject[sh].listen((event) async { - transactionHistory.updateAsync(); await _updateBalance(); + transactionHistory.updateAsync(); }); }); } @@ -358,9 +392,9 @@ abstract class BitcoinWalletBase extends WalletBase with Store { case TransactionPriority.slow: return 6000; case TransactionPriority.regular: - return 9000; + return 22080; case TransactionPriority.fast: - return 15000; + return 24000; default: return 0; } diff --git a/lib/bitcoin/bitcoin_wallet_creation_credentials.dart b/lib/bitcoin/bitcoin_wallet_creation_credentials.dart index 051a5700e..f8df42df0 100644 --- a/lib/bitcoin/bitcoin_wallet_creation_credentials.dart +++ b/lib/bitcoin/bitcoin_wallet_creation_credentials.dart @@ -1,21 +1,23 @@ import 'package:cake_wallet/core/wallet_credentials.dart'; +import 'package:cake_wallet/entities/wallet_info.dart'; class BitcoinNewWalletCredentials extends WalletCredentials { - BitcoinNewWalletCredentials({String name}) : super(name: name); + BitcoinNewWalletCredentials({String name, WalletInfo walletInfo}) + : super(name: name, walletInfo: walletInfo); } class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials { BitcoinRestoreWalletFromSeedCredentials( - {String name, String password, this.mnemonic}) - : super(name: name, password: password); + {String name, String password, this.mnemonic, WalletInfo walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); final String mnemonic; } class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials { BitcoinRestoreWalletFromWIFCredentials( - {String name, String password, this.wif}) - : super(name: name, password: password); + {String name, String password, this.wif, WalletInfo walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); final String wif; } diff --git a/lib/bitcoin/bitcoin_wallet_service.dart b/lib/bitcoin/bitcoin_wallet_service.dart index 2e9a1c305..205abdcca 100644 --- a/lib/bitcoin/bitcoin_wallet_service.dart +++ b/lib/bitcoin/bitcoin_wallet_service.dart @@ -1,25 +1,34 @@ import 'dart:io'; -import 'package:bip39/bip39.dart' as bip39; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart'; import 'package:cake_wallet/bitcoin/file.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart'; +import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_service.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; import 'package:cake_wallet/entities/pathForWallet.dart'; +import 'package:cake_wallet/entities/wallet_info.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:hive/hive.dart'; class BitcoinWalletService extends WalletService< BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> { + BitcoinWalletService(this.walletInfoSource); + + final Box walletInfoSource; + @override Future create(BitcoinNewWalletCredentials credentials) async { final dirPath = await pathForWalletDir( type: WalletType.bitcoin, name: credentials.name); final wallet = BitcoinWalletBase.build( dirPath: dirPath, - mnemonic: bip39.generateMnemonic(), + mnemonic: generateMnemonic(), password: credentials.password, - name: credentials.name); + name: credentials.name, + walletInfo: credentials.walletInfo); await wallet.save(); await wallet.init(); @@ -37,11 +46,15 @@ class BitcoinWalletService extends WalletService< await pathForWalletDir(name: name, type: WalletType.bitcoin); final walletPath = '$walletDirPath/$name'; final walletJSONRaw = await read(path: walletPath, password: password); + final walletInfo = walletInfoSource.values.firstWhere( + (info) => info.id == WalletBase.idFor(name, WalletType.bitcoin), + orElse: () => null); final wallet = BitcoinWalletBase.fromJSON( password: password, name: name, dirPath: walletDirPath, - jsonSource: walletJSONRaw); + jsonSource: walletJSONRaw, + walletInfo: walletInfo); await wallet.init(); return wallet; @@ -62,15 +75,21 @@ class BitcoinWalletService extends WalletService< @override Future restoreFromSeed( BitcoinRestoreWalletFromSeedCredentials credentials) async { + if (!validateMnemonic(credentials.mnemonic)) { + throw BitcoinMnemonicIsIncorrectException(); + } + final dirPath = await pathForWalletDir( type: WalletType.bitcoin, name: credentials.name); final wallet = BitcoinWalletBase.build( dirPath: dirPath, name: credentials.name, password: credentials.password, - mnemonic: credentials.mnemonic); + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo); await wallet.save(); await wallet.init(); + await wallet.generateNewAddresses(32); return wallet; } diff --git a/lib/bitcoin/electrum.dart b/lib/bitcoin/electrum.dart index 5348959e7..20dc29688 100644 --- a/lib/bitcoin/electrum.dart +++ b/lib/bitcoin/electrum.dart @@ -22,8 +22,9 @@ String jsonrpcparams(List params) { } String jsonrpc( - {String method, List params, int id, double version = 2.0}) => - '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n'; + {String method, List params, int id, double version = 2.0}) => + '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json + .encode(params)}}\n'; class SocketTask { SocketTask({this.completer, this.isSubscription, this.subject}); @@ -49,6 +50,7 @@ class ElectrumClient { final Map _tasks; bool _isConnected; Timer _aliveTimer; + String unterminatedString; Future connectToUri(String uri) async { final splittedUri = uri.split(':'); @@ -73,26 +75,31 @@ class ElectrumClient { socket.listen((Uint8List event) { try { - final jsoned = - json.decode(utf8.decode(event.toList())) as Map; - print(jsoned); - final method = jsoned['method']; - final id = jsoned['id'] as String; - final params = jsoned['result']; + _handleResponse(utf8.decode(event.toList())); + } on FormatException catch (e) { + final msg = e.message.toLowerCase(); - if (method is String) { - _methodHandler(method: method, request: jsoned); - return; + if (msg == 'Unterminated string'.toLowerCase()) { + unterminatedString = e.source as String; } - _finish(id, params); + if (msg == 'Unexpected character'.toLowerCase()) { + unterminatedString += e.source as String; + } + + if (isJSONStringCorrect(unterminatedString)) { + _handleResponse(unterminatedString); + unterminatedString = null; + } } catch (e) { print(e); } }, onError: (Object error) { print(error.toString()); _setIsConnected(false); - }, onDone: () => _setIsConnected(false)); + }, onDone: () { + _setIsConnected(false); + }); keepAlive(); } @@ -103,7 +110,7 @@ class ElectrumClient { Future ping() async { try { - // await callWithTimeout(method: 'server.ping'); + await callWithTimeout(method: 'server.ping'); _setIsConnected(true); } on RequestFailedTimeoutException catch (_) { _setIsConnected(false); @@ -146,7 +153,7 @@ class ElectrumClient { }); Future>> getListUnspentWithAddress( - String address) => + String address) => call( method: 'blockchain.scripthash.listunspent', params: [scriptHash(address)]).then((dynamic result) { @@ -197,7 +204,7 @@ class ElectrumClient { }); Future> getTransactionRaw( - {@required String hash}) async => + {@required String hash}) async => call(method: 'blockchain.transaction.get', params: [hash, true]) .then((dynamic result) { if (result is Map) { @@ -209,20 +216,24 @@ class ElectrumClient { Future> getTransactionExpanded( {@required String hash}) async { - final originalTx = await getTransactionRaw(hash: hash); - final vins = originalTx['vin'] as List; + try { + final originalTx = await getTransactionRaw(hash: hash); + final vins = originalTx['vin'] as List; - for (dynamic vin in vins) { - if (vin is Map) { - vin['tx'] = await getTransactionRaw(hash: vin['txid'] as String); + for (dynamic vin in vins) { + if (vin is Map) { + vin['tx'] = await getTransactionRaw(hash: vin['txid'] as String); + } } - } - return originalTx; + return originalTx; + } catch (_) { + return {}; + } } Future broadcastTransaction( - {@required String transactionRaw}) async => + {@required String transactionRaw}) async => call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) .then((dynamic result) { if (result is String) { @@ -233,14 +244,14 @@ class ElectrumClient { }); Future> getMerkle( - {@required String hash, @required int height}) async => + {@required String hash, @required int height}) async => await call( method: 'blockchain.transaction.get_merkle', params: [hash, height]) as Map; Future> getHeader({@required int height}) async => await call(method: 'blockchain.block.get_header', params: [height]) - as Map; + as Map; Future estimatefee({@required int p}) => call(method: 'blockchain.estimatefee', params: [p]) @@ -256,16 +267,17 @@ class ElectrumClient { return 0; }); - BehaviorSubject scripthashUpdate(String scripthash) => - subscribe( - id: 'blockchain.scripthash.subscribe:$scripthash', - method: 'blockchain.scripthash.subscribe', - params: [scripthash]); + BehaviorSubject scripthashUpdate(String scripthash) { + _id += 1; + return subscribe( + id: 'blockchain.scripthash.subscribe:$scripthash', + method: 'blockchain.scripthash.subscribe', + params: [scripthash]); + } - BehaviorSubject subscribe( - {@required String id, - @required String method, - List params = const []}) { + BehaviorSubject subscribe({@required String id, + @required String method, + List params = const []}) { final subscription = BehaviorSubject(); _regisrySubscription(id, subscription); socket.write(jsonrpc(method: method, id: _id, params: params)); @@ -273,7 +285,8 @@ class ElectrumClient { return subscription; } - Future call({String method, List params = const []}) { + Future call({String method, List params = const []}) async { + await Future.delayed(Duration(milliseconds: 100)); final completer = Completer(); _id += 1; final id = _id; @@ -283,10 +296,9 @@ class ElectrumClient { return completer.future; } - Future callWithTimeout( - {String method, - List params = const [], - int timeout = 2000}) async { + Future callWithTimeout({String method, + List params = const [], + int timeout = 2000}) async { final completer = Completer(); _id += 1; final id = _id; @@ -307,8 +319,15 @@ class ElectrumClient { socket.write(jsonrpc(method: method, id: _id, params: params)); } - void _regisryTask(int id, Completer completer) => _tasks[id.toString()] = - SocketTask(completer: completer, isSubscription: false); + Future close() async { + _aliveTimer.cancel(); + await socket.close(); + onConnectionStatusChange = null; + } + + void _regisryTask(int id, Completer completer) => + _tasks[id.toString()] = + SocketTask(completer: completer, isSubscription: false); void _regisrySubscription(String id, BehaviorSubject subject) => _tasks[id] = SocketTask(subject: subject, isSubscription: true); @@ -351,6 +370,31 @@ class ElectrumClient { _isConnected = isConnected; } + + void _handleResponse(String response) { + print('Response: $response'); + final jsoned = json.decode(response) as Map; + // print(jsoned); + final method = jsoned['method']; + final id = jsoned['id'] as String; + final result = jsoned['result']; + + if (method is String) { + _methodHandler(method: method, request: jsoned); + return; + } + + _finish(id, result); + } +} +// FIXME: move me +bool isJSONStringCorrect(String source) { + try { + json.decode(source); + return true; + } catch (_) { + return false; + } } class RequestFailedTimeoutException implements Exception { diff --git a/lib/bitcoin/pending_bitcoin_transaction.dart b/lib/bitcoin/pending_bitcoin_transaction.dart index d2c018a35..edd5a0450 100644 --- a/lib/bitcoin/pending_bitcoin_transaction.dart +++ b/lib/bitcoin/pending_bitcoin_transaction.dart @@ -16,6 +16,7 @@ class PendingBitcoinTransaction with PendingTransaction { final int amount; final int fee; + @override String get id => _tx.getId(); @override diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index ccc8b83ab..c0eae3189 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -60,7 +60,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.bnb: return [42]; case CryptoCurrency.btc: - return [33, 34, 42]; + return [33, 34, 42, 62]; case CryptoCurrency.dai: return [42]; case CryptoCurrency.dash: diff --git a/lib/core/execution_state.dart b/lib/core/execution_state.dart index d4e3faeec..18dc81030 100644 --- a/lib/core/execution_state.dart +++ b/lib/core/execution_state.dart @@ -4,7 +4,11 @@ class InitialExecutionState extends ExecutionState {} class IsExecutingState extends ExecutionState {} -class ExecutedSuccessfullyState extends ExecutionState {} +class ExecutedSuccessfullyState extends ExecutionState { + ExecutedSuccessfullyState({this.payload}); + + final dynamic payload; +} class FailureState extends ExecutionState { FailureState(this.error); diff --git a/lib/core/monero_account_label_validator.dart b/lib/core/monero_account_label_validator.dart index dcbac668f..2bc0fcccc 100644 --- a/lib/core/monero_account_label_validator.dart +++ b/lib/core/monero_account_label_validator.dart @@ -7,7 +7,7 @@ class MoneroLabelValidator extends TextValidator { MoneroLabelValidator({@required CryptoCurrency type}) : super( errorMessage: S.current.error_text_account_name, - pattern: '^[a-zA-Z0-9_]{1,15}\$', + pattern: '^[a-zA-Z0-9_ ]{1,15}\$', minLength: 1, maxLength: 15); } diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 36a06cb03..94ada7481 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -1,4 +1,4 @@ -import 'package:bip39/src/wordlists/english.dart' as bitcoin_english; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart' as bitcoin_electrum; import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/entities/mnemonic_item.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; @@ -64,7 +64,7 @@ class SeedValidator extends Validator { static List getBitcoinWordList(String language) { assert(language.toLowerCase() == LanguageList.english.toLowerCase()); - return bitcoin_english.WORDLIST; + return bitcoin_electrum.englishWordlist; } @override diff --git a/lib/core/wallet_base.dart b/lib/core/wallet_base.dart index 8eea39dca..d08eff0ef 100644 --- a/lib/core/wallet_base.dart +++ b/lib/core/wallet_base.dart @@ -52,4 +52,6 @@ abstract class WalletBase { Future save(); Future rescan({int height}); + + void close(); } diff --git a/lib/core/wallet_credentials.dart b/lib/core/wallet_credentials.dart index 3998bb005..ddfafab1a 100644 --- a/lib/core/wallet_credentials.dart +++ b/lib/core/wallet_credentials.dart @@ -1,7 +1,7 @@ import 'package:cake_wallet/entities/wallet_info.dart'; abstract class WalletCredentials { - WalletCredentials({this.name, this.password, this.height}); + WalletCredentials({this.name, this.password, this.height, this.walletInfo}); final String name; final int height; diff --git a/lib/di.dart b/lib/di.dart index 1571c925b..0a6646e5d 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -16,11 +16,13 @@ import 'package:cake_wallet/src/screens/contact/contact_page.dart'; import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart'; import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart'; import 'package:cake_wallet/src/screens/faq/faq_page.dart'; +import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart'; import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart'; import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/rescan/rescan_page.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; +import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart'; import 'package:cake_wallet/src/screens/send/send_template_page.dart'; import 'package:cake_wallet/src/screens/settings/change_language.dart'; @@ -48,6 +50,7 @@ import 'package:cake_wallet/store/wallet_list_store.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart'; +import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/rescan_view_model.dart'; @@ -273,7 +276,7 @@ Future setup( getIt.registerFactory(() => MoneroAccountListPage( accountListViewModel: getIt.get())); - getIt.registerFactory(() { + /*getIt.registerFactory(() { final wallet = getIt.get().wallet; if (wallet is MoneroWallet) { @@ -286,7 +289,19 @@ Future setup( getIt.registerFactory(() => MoneroAccountEditOrCreatePage( moneroAccountCreationViewModel: - getIt.get())); + getIt.get()));*/ + + getIt.registerFactoryParam( + (AccountListItem account, _) => MoneroAccountEditOrCreateViewModel( + (getIt.get().wallet as MoneroWallet).accountList, + accountListItem: account)); + + getIt.registerFactoryParam( + (AccountListItem account, _) => MoneroAccountEditOrCreatePage( + moneroAccountCreationViewModel: + getIt.get(param1: account))); getIt.registerFactory(() { final appStore = getIt.get(); @@ -361,7 +376,7 @@ Future setup( getIt.registerFactory(() => MoneroWalletService(walletInfoSource)); - getIt.registerFactory(() => BitcoinWalletService()); + getIt.registerFactory(() => BitcoinWalletService(walletInfoSource)); getIt.registerFactoryParam( (WalletType param1, __) { @@ -405,4 +420,12 @@ Future setup( transactionInfo, getIt.get().shouldSaveRecipientAddress, transactionDescriptionBox)); + + getIt.registerFactoryParam( + (para1, param2) => NewWalletTypePage(getIt.get(), + onTypeSelected: para1, isNewWallet: param2)); + + getIt.registerFactoryParam( + (WalletType type, _) => PreSeedPage(type)); } diff --git a/lib/entities/balance.dart b/lib/entities/balance.dart index e8564c5a2..89f3cd725 100644 --- a/lib/entities/balance.dart +++ b/lib/entities/balance.dart @@ -1,3 +1,9 @@ +import 'package:cake_wallet/entities/balance_display_mode.dart'; + abstract class Balance { - const Balance(); + const Balance(this.availableModes); + + final List availableModes; + + String formattedBalance(BalanceDisplayMode mode); } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 28fc3da90..11e9fa863 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -45,7 +45,7 @@ Future defaultSettingsMigration( FiatCurrency.usd.toString()); await sharedPreferences.setInt( PreferencesKey.currentTransactionPriorityKey, - TransactionPriority.standart.raw); + TransactionPriority.standard.raw); await sharedPreferences.setInt( PreferencesKey.currentBalanceDisplayModeKey, BalanceDisplayMode.availableBalance.raw); diff --git a/lib/entities/load_current_wallet.dart b/lib/entities/load_current_wallet.dart index ef690521f..88c236088 100644 --- a/lib/entities/load_current_wallet.dart +++ b/lib/entities/load_current_wallet.dart @@ -19,5 +19,5 @@ Future loadCurrentWallet() async { await getIt.get().getWalletPassword(walletName: name); final _service = getIt.get(param1: type); final wallet = await _service.openWallet(name, password); - appStore.wallet = wallet; + appStore.changeCurrentWallet(wallet); } diff --git a/lib/entities/node.dart b/lib/entities/node.dart index f307111d2..48f3c9d8e 100644 --- a/lib/entities/node.dart +++ b/lib/entities/node.dart @@ -14,7 +14,8 @@ class Node extends HiveObject with Keyable { {@required this.uri, @required WalletType type, this.login, - this.password}) { + this.password, + this.useSSL}) { this.type = type; } @@ -22,7 +23,8 @@ class Node extends HiveObject with Keyable { : uri = map['uri'] as String ?? '', login = map['login'] as String, password = map['password'] as String, - typeRaw = map['typeRaw'] as int; + typeRaw = map['typeRaw'] as int, + useSSL = map['useSSL'] as bool; static const boxName = 'Nodes'; @@ -38,6 +40,11 @@ class Node extends HiveObject with Keyable { @HiveField(3) int typeRaw; + @HiveField(4) + bool useSSL; + + bool get isSSL => useSSL ?? false; + @override dynamic get keyIndex { _keyIndex ??= key; diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index 470b9bc99..118394e6f 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -39,9 +39,9 @@ Future> loadElectrumServerList() async { Future resetToDefault(Box nodeSource) async { final moneroNodes = await loadDefaultNodes(); - // final bitcoinElectrumServerList = await loadElectrumServerList(); - // final nodes = moneroNodes + bitcoinElectrumServerList; + final bitcoinElectrumServerList = await loadElectrumServerList(); + final nodes = moneroNodes + bitcoinElectrumServerList; await nodeSource.clear(); - await nodeSource.addAll(moneroNodes); + await nodeSource.addAll(nodes); } diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 53d59460a..c2d6427c9 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -9,7 +9,8 @@ class PreferencesKey { static const shouldSaveRecipientAddressKey = 'save_recipient_address'; static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication'; - static const currentDarkTheme = 'dark_theme'; + static const currentTheme = 'current_theme'; + static const isDarkThemeLegacy = 'dark_theme'; static const displayActionListModeKey = 'display_list_mode'; static const currentPinLength = 'current_pin_length'; static const currentLanguageCode = 'language_code'; diff --git a/lib/entities/transaction_info.dart b/lib/entities/transaction_info.dart index c9ad89c71..986ab384d 100644 --- a/lib/entities/transaction_info.dart +++ b/lib/entities/transaction_info.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/utils/mobx.dart'; abstract class TransactionInfo extends Object with Keyable { String id; int amount; + int fee; TransactionDirection direction; bool isPending; DateTime date; @@ -11,6 +12,7 @@ abstract class TransactionInfo extends Object with Keyable { int confirmations; String amountFormatted(); String fiatAmount(); + String feeFormatted(); void changeFiatAmount(String amount); @override diff --git a/lib/entities/transaction_priority.dart b/lib/entities/transaction_priority.dart index 9bad84c9f..b7661dbdc 100644 --- a/lib/entities/transaction_priority.dart +++ b/lib/entities/transaction_priority.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/entities/enumerable_item.dart'; @@ -17,7 +18,23 @@ class TransactionPriority extends EnumerableItem with Serializable { static const medium = TransactionPriority(title: 'Medium', raw: 2); static const fast = TransactionPriority(title: 'Fast', raw: 3); static const fastest = TransactionPriority(title: 'Fastest', raw: 4); - static const standart = slow; + static const standard = slow; + + + static List forWalletType(WalletType type) { + switch (type) { + case WalletType.monero: + return TransactionPriority.all; + case WalletType.bitcoin: + return [ + TransactionPriority.slow, + TransactionPriority.regular, + TransactionPriority.fast + ]; + default: + return []; + } + } static TransactionPriority deserialize({int raw}) { switch (raw) { diff --git a/lib/entities/wallet_type.dart b/lib/entities/wallet_type.dart index f1a48ce52..ef4027a54 100644 --- a/lib/entities/wallet_type.dart +++ b/lib/entities/wallet_type.dart @@ -48,3 +48,14 @@ String walletTypeToString(WalletType type) { return ''; } } + +String walletTypeToDisplayName(WalletType type) { + switch (type) { + case WalletType.monero: + return 'Monero'; + case WalletType.bitcoin: + return 'Bitcoin (Electrum)'; + default: + return ''; + } +} diff --git a/lib/exchange/exchange_trade_state.dart b/lib/exchange/exchange_trade_state.dart index 324ae9338..109293bec 100644 --- a/lib/exchange/exchange_trade_state.dart +++ b/lib/exchange/exchange_trade_state.dart @@ -14,7 +14,8 @@ class TradeIsCreatedSuccessfully extends ExchangeTradeState { } class TradeIsCreatedFailure extends ExchangeTradeState { - TradeIsCreatedFailure({@required this.error}); + TradeIsCreatedFailure({@required this.title, @required this.error}); + final String title; final String error; } \ No newline at end of file diff --git a/lib/exchange/xmrto/xmrto_exchange_provider.dart b/lib/exchange/xmrto/xmrto_exchange_provider.dart index c57529823..cc63ff1a3 100644 --- a/lib/exchange/xmrto/xmrto_exchange_provider.dart +++ b/lib/exchange/xmrto/xmrto_exchange_provider.dart @@ -12,6 +12,7 @@ import 'package:cake_wallet/exchange/xmrto/xmrto_trade_request.dart'; import 'package:cake_wallet/exchange/trade_not_created_exeption.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/trade_not_found_exeption.dart'; +import 'package:cake_wallet/generated/i18n.dart'; class XMRTOExchangeProvider extends ExchangeProvider { XMRTOExchangeProvider() @@ -26,11 +27,14 @@ class XMRTOExchangeProvider extends ExchangeProvider { static const _orderParameterUriSuffix = '/order_parameter_query'; static const _orderStatusUriSuffix = '/order_status_query/'; static const _orderCreateUriSuffix = '/order_create/'; + static const _headers = { + 'Content-Type': 'application/json', + 'User-Agent': userAgent + }; static Future _checkIsAvailable() async { const url = originalApiUri + _orderParameterUriSuffix; - final response = - await get(url, headers: {'Content-Type': 'application/json'}); + final response = await get(url, headers: _headers); return !(response.statusCode == 403); } @@ -90,16 +94,28 @@ class XMRTOExchangeProvider extends ExchangeProvider { Future createTrade({TradeRequest request}) async { final _request = request as XMRTOTradeRequest; final url = originalApiUri + _orderCreateUriSuffix; + final _amount = + _request.isBTCRequest ? _request.receiveAmount : _request.amount; + + final _amountCurrency = _request.isBTCRequest + ? _request.to.toString() + : _request.from.toString(); + + final pattern = '^([0-9]+([.\,][0-9]{0,8})?|[.\,][0-9]{1,8})\$'; + final isValid = RegExp(pattern).hasMatch(_amount); + + if (!isValid) { + throw TradeNotCreatedException(description, + description: S.current.xmr_to_error_description); + } + final body = { - 'amount': - _request.isBTCRequest ? _request.receiveAmount : _request.amount, - 'amount_currency': _request.isBTCRequest - ? _request.to.toString() - : _request.from.toString(), + 'amount': _amount, + 'amount_currency': _amountCurrency, 'btc_dest_address': _request.address }; - final response = await post(url, - headers: {'Content-Type': 'application/json'}, body: json.encode(body)); + final response = + await post(url, headers: _headers, body: json.encode(body)); if (response.statusCode != 201) { if (response.statusCode == 400) { @@ -127,13 +143,10 @@ class XMRTOExchangeProvider extends ExchangeProvider { @override Future findTradeById({@required String id}) async { - const headers = { - 'Content-Type': 'application/json', - 'User-Agent': userAgent - }; final url = originalApiUri + _orderStatusUriSuffix; final body = {'uuid': id}; - final response = await post(url, headers: headers, body: json.encode(body)); + final response = + await post(url, headers: _headers, body: json.encode(body)); if (response.statusCode != 200) { if (response.statusCode == 400) { @@ -196,8 +209,7 @@ class XMRTOExchangeProvider extends ExchangeProvider { Future _fetchRates() async { try { final url = originalApiUri + _orderParameterUriSuffix; - final response = - await get(url, headers: {'Content-Type': 'application/json'}); + final response = await get(url, headers: _headers); final responseJSON = json.decode(response.body) as Map; final price = double.parse(responseJSON['price'] as String); diff --git a/lib/generated/i18n.dart b/lib/generated/i18n.dart index 48bff842c..59799342b 100644 --- a/lib/generated/i18n.dart +++ b/lib/generated/i18n.dart @@ -44,18 +44,21 @@ class S implements WidgetsLocalizations { String get authentication => "Authentication"; String get available_balance => "Available Balance"; String get biometric_auth_reason => "Scan your fingerprint to authenticate"; + String get bright_theme => "Bright"; String get buy => "Buy"; String get cake_wallet => "Cake Wallet"; String get cancel => "Cancel"; String get card_address => "Address:"; String get change => "Change"; String get change_currency => "Change Currency"; + String get change_current_node_title => "Change current node"; String get change_exchange_provider => "Change Exchange Provider"; String get change_language => "Change language"; String get change_wallet_alert_title => "Change current wallet"; String get choose_account => "Choose account"; String get choose_wallet_currency => "Please choose wallet currency:"; String get clear => "Clear"; + String get color_theme => "Color theme"; String get confirm => "Confirm"; String get confirm_delete_template => "This action will delete this template. Do you wish to continue?"; String get confirm_delete_wallet => "This action will delete this wallet. Do you wish to continue?"; @@ -70,6 +73,7 @@ class S implements WidgetsLocalizations { String get create_new => "Create New Wallet"; String get create_new_account => "Create new account"; String get creating_new_wallet => "Creating new wallet"; + String get dark_theme => "Dark"; String get delete => "Delete"; String get digit_pin => "-digit PIN"; String get edit => "Edit"; @@ -106,16 +110,21 @@ class S implements WidgetsLocalizations { String get id => "ID: "; String get incoming => "Incoming"; String get incorrect_seed => "The text entered is not valid."; + String get light_theme => "Light"; String get loading_your_wallet => "Loading your wallet"; String get login => "Login"; + String get new_node_testing => "New node testing"; String get new_subaddress_create => "Create"; String get new_subaddress_label_name => "Label name"; String get new_subaddress_title => "New address"; String get new_wallet => "New Wallet"; String get node_address => "Node Address"; + String get node_connection_failed => "Connection was failed"; + String get node_connection_successful => "Connection was successful"; String get node_new => "New Node"; String get node_port => "Node port"; String get node_reset_settings_title => "Reset settings"; + String get node_test => "Test"; String get nodes => "Nodes"; String get nodes_list_reset_to_default_message => "Are you sure that you want to reset settings to default?"; String get offer_expires_in => "Offer expires in: "; @@ -133,6 +142,9 @@ class S implements WidgetsLocalizations { String get please_make_selection => "Please make selection below to create or recover your wallet."; String get please_select => "Please select:"; String get please_try_to_connect_to_another_node => "Please try to connect to another node"; + String get pre_seed_button_text => "I understand. Show me my seed"; + String pre_seed_description(int words) => "On the next page you will see a series of ${words} words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app."; + String get pre_seed_title => "IMPORTANT"; String get private_key => "Private key"; String get public_key => "Public key"; String get receive => "Receive"; @@ -200,13 +212,13 @@ class S implements WidgetsLocalizations { String get send_error_currency => "Currency can only contain numbers"; String get send_error_minimum_value => "Minimum value of amount is 0.01"; String get send_estimated_fee => "Estimated fee:"; - String get send_fee => "Fee:"; + String get send_fee => "Fee"; String get send_got_it => "Got it"; String get send_name => "Name"; String get send_new => "New"; String get send_payment_id => "Payment ID (optional)"; String get send_sending => "Sending..."; - String get send_success => "Your Monero was successfully sent"; + String send_success(String crypto) => "Your ${crypto} was successfully sent"; String get send_templates => "Templates"; String get send_title => "Send"; String get send_xmr => "Send XMR"; @@ -247,7 +259,7 @@ class S implements WidgetsLocalizations { String get subaddresses => "Subaddresses"; String get sync_status_connected => "CONNECTED"; String get sync_status_connecting => "CONNECTING"; - String get sync_status_failed_connect => "FAILED CONNECT TO THE NODE"; + String get sync_status_failed_connect => "DISCONNECTED"; String get sync_status_not_connected => "NOT CONNECTED"; String get sync_status_starting_sync => "STARTING SYNC"; String get sync_status_syncronized => "SYNCHRONIZED"; @@ -295,6 +307,7 @@ class S implements WidgetsLocalizations { String get transactions => "Transactions"; String get transactions_by_date => "Transactions by date"; String get use => "Switch to "; + String get use_ssl => "Use SSL"; String get view_key_private => "View key (private)"; String get view_key_public => "View key (public)"; String get wallet_keys => "Wallet seed/keys"; @@ -317,6 +330,8 @@ class S implements WidgetsLocalizations { String get xmr_available_balance => "Available Balance"; String get xmr_full_balance => "Full Balance"; String get xmr_hidden => "Hidden"; + String get xmr_to_error => "XMR.TO error"; + String get xmr_to_error_description => "Invalid amount. Maximum limit 8 digits after the decimal point"; String get yesterday => "Yesterday"; String get you_will_get => "Convert to"; String get you_will_send => "Convert from"; @@ -337,6 +352,7 @@ class S implements WidgetsLocalizations { String min_value(String value, String currency) => "Min: ${value} ${currency}"; String openalias_alert_content(String recipient_name) => "You will be sending funds to\n${recipient_name}"; String powered_by(String title) => "Powered by ${title}"; + String provider_error(String provider) => "${provider} error"; String router_no_route(String name) => "No route defined for ${name}"; String send_address(String cryptoCurrency) => "${cryptoCurrency} address"; String send_priority(String transactionPriority) => "Currently the fee is set at ${transactionPriority} priority.\nTransaction priority can be adjusted in the settings"; @@ -382,9 +398,11 @@ class $de extends S { @override String get biometric_auth_reason => "Scannen Sie Ihren Fingerabdruck zur Authentifizierung"; @override + String get dark_theme => "Dunkel"; + @override String get transaction_sent => "Transaktion gesendet!"; @override - String get send_fee => "Gebühr:"; + String get send_fee => "Gebühr"; @override String get password => "Passwort"; @override @@ -492,14 +510,22 @@ class $de extends S { @override String get trade_details_pair => "Paar"; @override + String get use_ssl => "Verwenden Sie SSL"; + @override String get choose_wallet_currency => "Bitte wählen Sie die Brieftaschenwährung:"; @override + String pre_seed_description(int words) => "Auf der nächsten Seite sehen Sie eine Reihe von ${words} Wörtern. Dies ist Ihr einzigartiger und privater Samen und der EINZIGE Weg, um Ihren Geldbeutel im Falle eines Verlusts oder einer Fehlfunktion wiederherzustellen. Es liegt in IHRER Verantwortung, es aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet App aufzubewahren."; + @override + String get node_connection_successful => "Die Verbindung war erfolgreich"; + @override String get confirm => "Bestätigen"; @override String get settings_display_balance_as => "Kontostand anzeigen als"; @override String get change_wallet_alert_title => "Ändern Sie die aktuelle Brieftasche"; @override + String get pre_seed_button_text => "Ich verstehe. Zeig mir meinen Samen"; + @override String get placeholder_transactions => "Ihre Transaktionen werden hier angezeigt"; @override String get trade_details_provider => "Anbieter"; @@ -534,6 +560,8 @@ class $de extends S { @override String get contact => "Kontakt"; @override + String get change_current_node_title => "Ändern Sie den aktuellen Knoten"; + @override String get openalias_alert_title => "XMR-Empfänger erkannt"; @override String get auth_store_incorrect_password => "Falsches PIN"; @@ -566,11 +594,13 @@ class $de extends S { @override String get xmr_hidden => "Versteckt"; @override + String get node_connection_failed => "Verbindung fehlgeschlagen"; + @override String get send_xmr => "Senden XMR"; @override String get exchange => "Austausch"; @override - String get sync_status_failed_connect => "Verbindung zum Knoten fehlgeschlagen"; + String get sync_status_failed_connect => "GETRENNT"; @override String get send_estimated_fee => "Geschätzte Gebühr:"; @override @@ -612,7 +642,7 @@ class $de extends S { @override String get trade_details_created_at => "Hergestellt in"; @override - String get send_success => "Ihr Monero wurde erfolgreich gesendet"; + String send_success(String crypto) => "Ihr ${crypto} wurde erfolgreich gesendet"; @override String get settings_wallets => "Brieftaschen"; @override @@ -622,6 +652,8 @@ class $de extends S { @override String get filters => "Filter"; @override + String get color_theme => "Farbthema"; + @override String get settings_current_node => "Aktueller Knoten"; @override String get copy_id => "ID kopieren"; @@ -642,6 +674,8 @@ class $de extends S { @override String get widgets_restore_from_blockheight => "Aus Blockhöhe wiederherstellen"; @override + String get new_node_testing => "Neuer Knotentest"; + @override String get seed_language_chinese => "Chinesisch"; @override String get new_subaddress_label_name => "Markenname"; @@ -750,6 +784,8 @@ class $de extends S { @override String get seed_language_dutch => "Niederländisch"; @override + String get xmr_to_error => "XMR.TO-Fehler"; + @override String get restore_description_from_seed_keys => "Holen Sie sich Ihr Wallet von Seed / Schlüsseln zurück, die Sie an einem sicheren Ort aufbewahrt haben"; @override String get copy => "Kopieren"; @@ -774,6 +810,8 @@ class $de extends S { @override String get change => "Veränderung"; @override + String get node_test => "Test"; + @override String get trade_state_finished => "Fertig"; @override String get pin_is_incorrect => "PIN ist falsch"; @@ -852,14 +890,20 @@ class $de extends S { @override String get trade_details_id => "ID"; @override + String get xmr_to_error_description => "Ungültiger Betrag. Höchstgrenze 8 Stellen nach dem Dezimalpunkt"; + @override String get trade_state_confirming => "Bestätigung"; @override + String get bright_theme => "Hell"; + @override String get send => "Senden"; @override String get send_title => "Senden Sie"; @override String get error_text_keys => "Walletschlüssel können nur 64 hexadezimale Zeichen enthalten"; @override + String get light_theme => "Licht"; + @override String get settings_save_recipient_address => "Empfängeradresse speichern"; @override String get change_exchange_provider => "Wechseln Sie den Exchange-Anbieter"; @@ -942,6 +986,8 @@ class $de extends S { @override String get restore_title_from_seed => "Aus Seed wiederherstellen"; @override + String get pre_seed_title => "WICHTIG"; + @override String get restore_description_from_backup => "Sie können die gesamte Cake Wallet-App von wiederherstellen Ihre Sicherungsdatei"; @override String get error_text_node_port => "Der Knotenport kann nur Nummern zwischen 0 und 65535 enthalten"; @@ -1014,6 +1060,8 @@ class $de extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "Fehler beim Entfernen ${wallet_name} Wallet. ${error}"; @override + String provider_error(String provider) => "${provider} Error"; + @override String copied_key_to_clipboard(String key) => "Kopiert ${key} Zur Zwischenablage"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "Laden fehlgeschlagen ${wallet_name} Wallet. ${error}"; @@ -1050,6 +1098,8 @@ class $hi extends S { @override String get biometric_auth_reason => "प्रमाणित करने के लिए अपने फ़िंगरप्रिंट को स्कैन करें"; @override + String get dark_theme => "अंधेरा"; + @override String get transaction_sent => "भेजा गया लेन-देन"; @override String get send_fee => "शुल्क:"; @@ -1160,14 +1210,22 @@ class $hi extends S { @override String get trade_details_pair => "जोड़ा"; @override + String get use_ssl => "उपयोग SSL"; + @override String get choose_wallet_currency => "कृपया बटुआ मुद्रा चुनें:"; @override + String pre_seed_description(int words) => "अगले पेज पर आपको ${words} शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अद्वितीय और निजी बीज है और नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि इसे नीचे लिखें और इसे Cake Wallet ऐप के बाहर सुरक्षित स्थान पर संग्रहीत करें।"; + @override + String get node_connection_successful => "कनेक्शन सफल रहा"; + @override String get confirm => "की पुष्टि करें"; @override String get settings_display_balance_as => "के रूप में संतुलन प्रदर्शित करें"; @override String get change_wallet_alert_title => "वर्तमान बटुआ बदलें"; @override + String get pre_seed_button_text => "मै समझता हुँ। मुझे अपना बीज दिखाओ"; + @override String get placeholder_transactions => "आपके लेनदेन यहां प्रदर्शित होंगे"; @override String get trade_details_provider => "प्रदाता"; @@ -1202,6 +1260,8 @@ class $hi extends S { @override String get contact => "संपर्क करें"; @override + String get change_current_node_title => "वर्तमान नोड बदलें"; + @override String get openalias_alert_title => "XMR प्राप्तकर्ता का पता लगाया"; @override String get auth_store_incorrect_password => "गलत पिन"; @@ -1234,11 +1294,13 @@ class $hi extends S { @override String get xmr_hidden => "छिपा हुआ"; @override + String get node_connection_failed => "कनेक्शन विफल रहा"; + @override String get send_xmr => "संदेश XMR"; @override String get exchange => "अदला बदली"; @override - String get sync_status_failed_connect => "फेल हुआ कनेक्ट नोड"; + String get sync_status_failed_connect => "डिस्कनेक्ट किया गया"; @override String get send_estimated_fee => "अनुमानित शुल्क:"; @override @@ -1280,7 +1342,7 @@ class $hi extends S { @override String get trade_details_created_at => "पर बनाया गया"; @override - String get send_success => "आपका Monero सफलतापूर्वक भेजा गया"; + String send_success(String crypto) => "आपका ${crypto} सफलतापूर्वक भेजा गया"; @override String get settings_wallets => "पर्स"; @override @@ -1290,6 +1352,8 @@ class $hi extends S { @override String get filters => "फ़िल्टर"; @override + String get color_theme => "रंग विषय"; + @override String get settings_current_node => "वर्तमान नोड"; @override String get copy_id => "प्रतिलिपि ID"; @@ -1310,6 +1374,8 @@ class $hi extends S { @override String get widgets_restore_from_blockheight => "ब्लॉकचेन से पुनर्स्थापित करें"; @override + String get new_node_testing => "नई नोड परीक्षण"; + @override String get seed_language_chinese => "चीनी"; @override String get new_subaddress_label_name => "लेबल का नाम"; @@ -1418,6 +1484,8 @@ class $hi extends S { @override String get seed_language_dutch => "डच"; @override + String get xmr_to_error => "XMR.TO त्रुटि"; + @override String get restore_description_from_seed_keys => "अपने बटुए को बीज से वापस लें/वे कुंजियाँ जिन्हें आपने सुरक्षित स्थान पर सहेजा है"; @override String get copy => "प्रतिलिपि"; @@ -1442,6 +1510,8 @@ class $hi extends S { @override String get change => "परिवर्तन"; @override + String get node_test => "परीक्षा"; + @override String get trade_state_finished => "ख़त्म होना"; @override String get pin_is_incorrect => "पिन गलत है"; @@ -1520,14 +1590,20 @@ class $hi extends S { @override String get trade_details_id => "आईडी"; @override + String get xmr_to_error_description => "अवैध राशि। दशमलव बिंदु के बाद अधिकतम सीमा 8 अंक"; + @override String get trade_state_confirming => "पुष्टि"; @override + String get bright_theme => "उज्ज्वल"; + @override String get send => "संदेश"; @override String get send_title => "संदेश"; @override String get error_text_keys => "वॉलेट कीज़ में हेक्स में केवल 64 वर्ण हो सकते हैं"; @override + String get light_theme => "रोशनी"; + @override String get settings_save_recipient_address => "प्राप्तकर्ता का पता सहेजें"; @override String get change_exchange_provider => "एक्सचेंज प्रदाता बदलें"; @@ -1610,6 +1686,8 @@ class $hi extends S { @override String get restore_title_from_seed => "बीज से पुनर्स्थापित करें"; @override + String get pre_seed_title => "महत्वपूर्ण"; + @override String get restore_description_from_backup => "आप से पूरे केक वॉलेट एप्लिकेशन को पुनर्स्थापित कर सकते हैं आपकी बैक-अप फ़ाइल"; @override String get error_text_node_port => "नोड पोर्ट में केवल 0 और 65535 के बीच संख्याएँ हो सकती हैं"; @@ -1682,6 +1760,8 @@ class $hi extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "निकालने में विफल ${wallet_name} बटुआ. ${error}"; @override + String provider_error(String provider) => "${provider} त्रुटि"; + @override String copied_key_to_clipboard(String key) => "की नकल की ${key} क्लिपबोर्ड पर"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "लोड करने में विफल ${wallet_name} बटुआ. ${error}"; @@ -1718,9 +1798,11 @@ class $ru extends S { @override String get biometric_auth_reason => "Отсканируйте свой отпечаток пальца для аутентификации"; @override + String get dark_theme => "Темная"; + @override String get transaction_sent => "Tранзакция отправлена!"; @override - String get send_fee => "Комиссия:"; + String get send_fee => "Комиссия"; @override String get password => "Пароль"; @override @@ -1828,14 +1910,22 @@ class $ru extends S { @override String get trade_details_pair => "Пара"; @override + String get use_ssl => "Использовать SSL"; + @override String get choose_wallet_currency => "Пожалуйста, выберите валюту кошелька:"; @override + String pre_seed_description(int words) => "На следующей странице вы увидите серию из ${words} слов. Это ваша уникальная и личная мнемоническая фраза, и это ЕДИНСТВЕННЫЙ способ восстановить свой кошелек в случае потери или неисправности. ВАМ необходимо записать ее и хранить в надежном месте вне приложения Cake Wallet."; + @override + String get node_connection_successful => "Подключение прошло успешно"; + @override String get confirm => "Подтвердить"; @override String get settings_display_balance_as => "Отображать баланс как"; @override String get change_wallet_alert_title => "Изменить текущий кошелек"; @override + String get pre_seed_button_text => "Понятно. Покажите мнемоническую фразу"; + @override String get placeholder_transactions => "Ваши транзакции будут отображаться здесь"; @override String get trade_details_provider => "Провайдер"; @@ -1870,6 +1960,8 @@ class $ru extends S { @override String get contact => "Контакт"; @override + String get change_current_node_title => "Изменить текущую ноду"; + @override String get openalias_alert_title => "Получатель XMR обнаружен"; @override String get auth_store_incorrect_password => "Некорректный PIN"; @@ -1902,11 +1994,13 @@ class $ru extends S { @override String get xmr_hidden => "Скрыто"; @override + String get node_connection_failed => "Подключение не удалось"; + @override String get send_xmr => "Отправить XMR"; @override String get exchange => "Обмен"; @override - String get sync_status_failed_connect => "ОШИБКА ПОДКЛЮЧЕНИЯ К НОДЕ"; + String get sync_status_failed_connect => "ОТКЛЮЧЕНО"; @override String get send_estimated_fee => "Предполагаемая комиссия:"; @override @@ -1948,7 +2042,7 @@ class $ru extends S { @override String get trade_details_created_at => "Создано"; @override - String get send_success => "Ваш Monero был успешно отправлен"; + String send_success(String crypto) => "Ваш ${crypto} был успешно отправлен"; @override String get settings_wallets => "Кошельки"; @override @@ -1958,6 +2052,8 @@ class $ru extends S { @override String get filters => "Фильтр"; @override + String get color_theme => "Цветовая тема"; + @override String get settings_current_node => "Текущая нода"; @override String get copy_id => "Скопировать ID"; @@ -1978,6 +2074,8 @@ class $ru extends S { @override String get widgets_restore_from_blockheight => "Восстановить на высоте блока"; @override + String get new_node_testing => "Тестирование новой ноды"; + @override String get seed_language_chinese => "Китайский"; @override String get new_subaddress_label_name => "Имя"; @@ -2086,6 +2184,8 @@ class $ru extends S { @override String get seed_language_dutch => "Нидерландский"; @override + String get xmr_to_error => "Ошибка XMR.TO"; + @override String get restore_description_from_seed_keys => "Вы можете восстановить кошелёк из мнемонической фразы/ключей, которые вы сохранили ранее"; @override String get copy => "Скопировать"; @@ -2110,6 +2210,8 @@ class $ru extends S { @override String get change => "Изменить"; @override + String get node_test => "Тест"; + @override String get trade_state_finished => "Завершена"; @override String get pin_is_incorrect => "Некорректный PIN"; @@ -2188,14 +2290,20 @@ class $ru extends S { @override String get trade_details_id => "ID"; @override + String get xmr_to_error_description => "Недопустимая сумма. Максимум 8 цифр после десятичной точки"; + @override String get trade_state_confirming => "Подтверждение"; @override + String get bright_theme => "Яркая"; + @override String get send => "Отправить"; @override String get send_title => "Отправить"; @override String get error_text_keys => "Ключи кошелька могут содержать только 64 символа в hex"; @override + String get light_theme => "Светлая"; + @override String get settings_save_recipient_address => "Сохранять адрес получателя"; @override String get change_exchange_provider => "Изменить провайдера обмена"; @@ -2278,6 +2386,8 @@ class $ru extends S { @override String get restore_title_from_seed => "Восстановить из мнемонической фразы"; @override + String get pre_seed_title => "ВАЖНО"; + @override String get restore_description_from_backup => "Вы можете восстановить Cake Wallet из вашего back-up файла"; @override String get error_text_node_port => "Порт ноды может содержать только цифры от 0 до 65535"; @@ -2350,6 +2460,8 @@ class $ru extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "Ошибка при удалении ${wallet_name} кошелька. ${error}"; @override + String provider_error(String provider) => "${provider} ошибка"; + @override String copied_key_to_clipboard(String key) => "Скопировано ${key} в буфер обмена"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "Ошибка при загрузке ${wallet_name} кошелька. ${error}"; @@ -2386,9 +2498,11 @@ class $ko extends S { @override String get biometric_auth_reason => "지문을 스캔하여 인증"; @override + String get dark_theme => "어두운"; + @override String get transaction_sent => "거래가 전송되었습니다!"; @override - String get send_fee => "회비:"; + String get send_fee => "회비"; @override String get password => "암호"; @override @@ -2496,14 +2610,22 @@ class $ko extends S { @override String get trade_details_pair => "쌍"; @override + String get use_ssl => "SSL 사용"; + @override String get choose_wallet_currency => "지갑 통화를 선택하십시오:"; @override + String pre_seed_description(int words) => "다음 페이지에서 ${words} 개의 단어를 볼 수 있습니다. 이것은 귀하의 고유하고 개인적인 시드이며 분실 또는 오작동시 지갑을 복구하는 유일한 방법입니다. 기록해두고 Cake Wallet 앱 외부의 안전한 장소에 보관하는 것은 귀하의 책임입니다."; + @override + String get node_connection_successful => "성공적으로 연결되었습니다."; + @override String get confirm => "확인"; @override String get settings_display_balance_as => "잔액 표시"; @override String get change_wallet_alert_title => "현재 지갑 변경"; @override + String get pre_seed_button_text => "이해 했어요. 내 씨앗을 보여줘"; + @override String get placeholder_transactions => "거래가 여기에 표시됩니다"; @override String get trade_details_provider => "공급자"; @@ -2538,6 +2660,8 @@ class $ko extends S { @override String get contact => "접촉"; @override + String get change_current_node_title => "현재 노드 변경"; + @override String get openalias_alert_title => "XMR 수신자 감지"; @override String get auth_store_incorrect_password => "잘못된 PIN"; @@ -2570,11 +2694,13 @@ class $ko extends S { @override String get xmr_hidden => "숨김"; @override + String get node_connection_failed => "연결 실패"; + @override String get send_xmr => "보내다 XMR"; @override String get exchange => "교환"; @override - String get sync_status_failed_connect => "노드에 연결하지 못했습니다"; + String get sync_status_failed_connect => "연결 해제"; @override String get send_estimated_fee => "예상 수수료:"; @override @@ -2616,7 +2742,7 @@ class $ko extends S { @override String get trade_details_created_at => "에 작성"; @override - String get send_success => "Monero가 성공적으로 전송되었습니다"; + String send_success(String crypto) => "${crypto}가 성공적으로 전송되었습니다"; @override String get settings_wallets => "지갑"; @override @@ -2626,6 +2752,8 @@ class $ko extends S { @override String get filters => "필터"; @override + String get color_theme => "색상 테마"; + @override String get settings_current_node => "현재 노드"; @override String get copy_id => "부 ID"; @@ -2646,6 +2774,8 @@ class $ko extends S { @override String get widgets_restore_from_blockheight => "블록 높이에서 복원"; @override + String get new_node_testing => "새로운 노드 테스트"; + @override String get seed_language_chinese => "중국말"; @override String get new_subaddress_label_name => "라벨 이름"; @@ -2754,6 +2884,8 @@ class $ko extends S { @override String get seed_language_dutch => "네덜란드 사람"; @override + String get xmr_to_error => "XMR.TO 오류"; + @override String get restore_description_from_seed_keys => "안전한 장소에 저장 한 종자 / 키로 지갑을 되 찾으십시오."; @override String get copy => "부"; @@ -2778,6 +2910,8 @@ class $ko extends S { @override String get change => "변화"; @override + String get node_test => "테스트"; + @override String get trade_state_finished => "끝마친"; @override String get pin_is_incorrect => "PIN이 잘못되었습니다"; @@ -2856,14 +2990,20 @@ class $ko extends S { @override String get trade_details_id => "ID"; @override + String get xmr_to_error_description => "금액이 잘못되었습니다. 소수점 이하 최대 8 자리"; + @override String get trade_state_confirming => "확인 중"; @override + String get bright_theme => "선명한"; + @override String get send => "보내다"; @override String get send_title => "보내다"; @override String get error_text_keys => "지갑 키는 16 진수로 64 자만 포함 할 수 있습니다"; @override + String get light_theme => "빛"; + @override String get settings_save_recipient_address => "수신자 주소 저장"; @override String get change_exchange_provider => "교환 공급자 변경"; @@ -2946,6 +3086,8 @@ class $ko extends S { @override String get restore_title_from_seed => "종자에서 복원"; @override + String get pre_seed_title => "중대한"; + @override String get restore_description_from_backup => "백업 파일에서 전체 Cake Wallet 앱을 복원 할 수 있습니다."; @override String get error_text_node_port => "노드 포트는 0에서 65535 사이의 숫자 만 포함 할 수 있습니다"; @@ -3018,6 +3160,8 @@ class $ko extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "제거하지 못했습니다 ${wallet_name} 지갑. ${error}"; @override + String provider_error(String provider) => "${provider} 오류"; + @override String copied_key_to_clipboard(String key) => "복사 ${key} 클립 보드로"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "불러 오지 못했습니다 ${wallet_name} 지갑. ${error}"; @@ -3054,9 +3198,11 @@ class $pt extends S { @override String get biometric_auth_reason => "Digitalize sua impressão digital para autenticar"; @override + String get dark_theme => "Sombria"; + @override String get transaction_sent => "Transação enviada!"; @override - String get send_fee => "Taxa:"; + String get send_fee => "Taxa"; @override String get password => "Senha"; @override @@ -3164,14 +3310,22 @@ class $pt extends S { @override String get trade_details_pair => "Par"; @override + String get use_ssl => "Use SSL"; + @override String get choose_wallet_currency => "Escolha a moeda da carteira:"; @override + String pre_seed_description(int words) => "Na próxima página, você verá uma série de ${words} palavras. Esta é a sua semente única e privada e é a ÚNICA maneira de recuperar sua carteira em caso de perda ou mau funcionamento. É SUA responsabilidade anotá-lo e armazená-lo em um local seguro fora do aplicativo Cake Wallet."; + @override + String get node_connection_successful => "A conexão foi bem sucedida"; + @override String get confirm => "Confirmar"; @override String get settings_display_balance_as => "Saldo a exibir"; @override String get change_wallet_alert_title => "Alterar carteira atual"; @override + String get pre_seed_button_text => "Compreendo. Me mostre minha semente"; + @override String get placeholder_transactions => "Suas transações serão exibidas aqui"; @override String get trade_details_provider => "Provedor"; @@ -3206,6 +3360,8 @@ class $pt extends S { @override String get contact => "Contato"; @override + String get change_current_node_title => "Mudar o nó atual"; + @override String get openalias_alert_title => "Destinatário XMR detectado"; @override String get auth_store_incorrect_password => "PIN incorreto"; @@ -3238,11 +3394,13 @@ class $pt extends S { @override String get xmr_hidden => "Esconder saldo"; @override + String get node_connection_failed => "A conexão falhou"; + @override String get send_xmr => "Enviar XMR"; @override String get exchange => "Trocar"; @override - String get sync_status_failed_connect => "Erro ao conectar com o nó"; + String get sync_status_failed_connect => "DESCONECTADO"; @override String get send_estimated_fee => "Taxa estimada:"; @override @@ -3284,7 +3442,7 @@ class $pt extends S { @override String get trade_details_created_at => "Criada em"; @override - String get send_success => "Seu Monero foi enviado com sucesso"; + String send_success(String crypto) => "Seu ${crypto} foi enviado com sucesso"; @override String get settings_wallets => "Carteiras"; @override @@ -3294,6 +3452,8 @@ class $pt extends S { @override String get filters => "Filtro"; @override + String get color_theme => "Tema de cor"; + @override String get settings_current_node => "Nó atual"; @override String get copy_id => "Copiar ID"; @@ -3314,6 +3474,8 @@ class $pt extends S { @override String get widgets_restore_from_blockheight => "Restaurar a partir de altura do bloco"; @override + String get new_node_testing => "Teste de novo nó"; + @override String get seed_language_chinese => "Chinesa"; @override String get new_subaddress_label_name => "Nome"; @@ -3422,6 +3584,8 @@ class $pt extends S { @override String get seed_language_dutch => "Holandesa"; @override + String get xmr_to_error => "Erro XMR.TO"; + @override String get restore_description_from_seed_keys => "Restaure a sua carteira a partir de sementes/chaves que você salvou em um local seguro"; @override String get copy => "Copiar"; @@ -3446,6 +3610,8 @@ class $pt extends S { @override String get change => "Mudar"; @override + String get node_test => "Teste"; + @override String get trade_state_finished => "Finalizada"; @override String get pin_is_incorrect => "PIN incorreto"; @@ -3524,14 +3690,20 @@ class $pt extends S { @override String get trade_details_id => "ID"; @override + String get xmr_to_error_description => "Montante inválido. Limite máximo de 8 dígitos após o ponto decimal"; + @override String get trade_state_confirming => "Confirmando"; @override + String get bright_theme => "Brilhante"; + @override String get send => "Enviar"; @override String get send_title => "Enviar"; @override String get error_text_keys => "As chaves da carteira podem conter apenas 64 caracteres em hexadecimal"; @override + String get light_theme => "Luz"; + @override String get settings_save_recipient_address => "Salvar endereço do destinatário"; @override String get change_exchange_provider => "Alterar o provedor de troca"; @@ -3614,6 +3786,8 @@ class $pt extends S { @override String get restore_title_from_seed => "Restaurar a partir de semente"; @override + String get pre_seed_title => "IMPORTANTE"; + @override String get restore_description_from_backup => "Você pode restaurar todo o aplicativo Cake Wallet de seu arquivo de backup"; @override String get error_text_node_port => "A porta do nó deve conter apenas números entre 0 e 65535"; @@ -3686,6 +3860,8 @@ class $pt extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "Falha ao remover a carteira ${wallet_name}. ${error}"; @override + String provider_error(String provider) => "${provider} erro"; + @override String copied_key_to_clipboard(String key) => "${key} copiada para a área de transferência"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "Falha ao abrir a carteira ${wallet_name}. ${error}"; @@ -3722,9 +3898,11 @@ class $uk extends S { @override String get biometric_auth_reason => "Відскануйте свій відбиток пальця для аутентифікації"; @override + String get dark_theme => "Темна"; + @override String get transaction_sent => "Tранзакцію відправлено!"; @override - String get send_fee => "Комісія:"; + String get send_fee => "Комісія"; @override String get password => "Пароль"; @override @@ -3832,14 +4010,22 @@ class $uk extends S { @override String get trade_details_pair => "Пара"; @override + String get use_ssl => "Використати SSL"; + @override String get choose_wallet_currency => "Будь ласка, виберіть валюту гаманця:"; @override + String pre_seed_description(int words) => "На наступній сторінці ви побачите серію з ${words} слів. Це ваша унікальна та приватна мнемонічна фраза, і це ЄДИНИЙ спосіб відновити ваш гаманець на випадок втрати або несправності. ВАМ необхідно записати її та зберігати в безпечному місці поза програмою Cake Wallet."; + @override + String get node_connection_successful => "З'єднання було успішним"; + @override String get confirm => "Підтвердити"; @override String get settings_display_balance_as => "Відображати баланс як"; @override String get change_wallet_alert_title => "Змінити поточний гаманець"; @override + String get pre_seed_button_text => "Зрозуміло. Покажіть мнемонічну фразу"; + @override String get placeholder_transactions => "Тут відображатимуться ваші транзакції"; @override String get trade_details_provider => "Провайдер"; @@ -3874,6 +4060,8 @@ class $uk extends S { @override String get contact => "Контакт"; @override + String get change_current_node_title => "Змінити поточний вузол"; + @override String get openalias_alert_title => "Отримувача XMR виявлено"; @override String get auth_store_incorrect_password => "Некоректний PIN"; @@ -3906,11 +4094,13 @@ class $uk extends S { @override String get xmr_hidden => "Приховано"; @override + String get node_connection_failed => "Помилка з’єднання"; + @override String get send_xmr => "Відправити XMR"; @override String get exchange => "Обмін"; @override - String get sync_status_failed_connect => "ПОМИЛКА ПІДКЛЮЧЕННЯ ДО ВУЗЛУ"; + String get sync_status_failed_connect => "ВІДКЛЮЧЕНО"; @override String get send_estimated_fee => "Ймовірна комісія:"; @override @@ -3952,7 +4142,7 @@ class $uk extends S { @override String get trade_details_created_at => "Створено"; @override - String get send_success => "Ваш Monero успішно надісланий"; + String send_success(String crypto) => "Ваш ${crypto} успішно надісланий"; @override String get settings_wallets => "Гаманці"; @override @@ -3962,6 +4152,8 @@ class $uk extends S { @override String get filters => "Фільтр"; @override + String get color_theme => "Кольорова тема"; + @override String get settings_current_node => "Поточний вузол"; @override String get copy_id => "Скопіювати ID"; @@ -3982,6 +4174,8 @@ class $uk extends S { @override String get widgets_restore_from_blockheight => "Відновити на висоті блоку"; @override + String get new_node_testing => "Тестування нового вузла"; + @override String get seed_language_chinese => "Китайська"; @override String get new_subaddress_label_name => "Ім'я"; @@ -4090,6 +4284,8 @@ class $uk extends S { @override String get seed_language_dutch => "Голландська"; @override + String get xmr_to_error => "Помилка XMR.TO"; + @override String get restore_description_from_seed_keys => "Ви можете відновити гаманець з мнемонічної фрази/ключів, які ви зберегли раніше"; @override String get copy => "Скопіювати"; @@ -4114,6 +4310,8 @@ class $uk extends S { @override String get change => "Змінити"; @override + String get node_test => "Тест"; + @override String get trade_state_finished => "Завершена"; @override String get pin_is_incorrect => "Некоректний PIN"; @@ -4192,14 +4390,20 @@ class $uk extends S { @override String get trade_details_id => "ID"; @override + String get xmr_to_error_description => "Неприпустима сума. Максимум 8 цифр після десяткової коми"; + @override String get trade_state_confirming => "Підтвердження"; @override + String get bright_theme => "Яскрава"; + @override String get send => "Відправити"; @override String get send_title => "Відправити"; @override String get error_text_keys => "Ключі гаманця можуть містити тільки 64 символів в hex"; @override + String get light_theme => "Світла"; + @override String get settings_save_recipient_address => "Зберігати адресу отримувача"; @override String get change_exchange_provider => "Змінити провайдера обміну"; @@ -4282,6 +4486,8 @@ class $uk extends S { @override String get restore_title_from_seed => "Відновити з мнемонічної фрази"; @override + String get pre_seed_title => "ВАЖЛИВО"; + @override String get restore_description_from_backup => "Ви можете відновити Cake Wallet з вашого резервного файлу"; @override String get error_text_node_port => "Порт вузла може містити тільки цифри від 0 до 65535"; @@ -4354,6 +4560,8 @@ class $uk extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "Помилка при видаленні ${wallet_name} гаманця. ${error}"; @override + String provider_error(String provider) => "${provider} помилка"; + @override String copied_key_to_clipboard(String key) => "Скопійовано ${key} в буфер обміну"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "Помилка при завантаженні ${wallet_name} гаманця. ${error}"; @@ -4390,9 +4598,11 @@ class $ja extends S { @override String get biometric_auth_reason => "प指紋をスキャンして認証する"; @override + String get dark_theme => "闇"; + @override String get transaction_sent => "トランザクションが送信されました!"; @override - String get send_fee => "費用:"; + String get send_fee => "費用"; @override String get password => "パスワード"; @override @@ -4500,14 +4710,22 @@ class $ja extends S { @override String get trade_details_pair => "ペア"; @override + String get use_ssl => "SSLを使用する"; + @override String get choose_wallet_currency => "ウォレット通貨を選択してください:"; @override + String pre_seed_description(int words) => "次のページでは、一連の${words}語が表示されます。 これはあなたのユニークでプライベートなシードであり、紛失や誤動作が発生した場合にウォレットを回復する唯一の方法です。 それを書き留めて、Cake Wallet アプリの外の安全な場所に保管するのはあなたの責任です。"; + @override + String get node_connection_successful => "接続に成功しました"; + @override String get confirm => "確認する"; @override String get settings_display_balance_as => "残高を表示"; @override String get change_wallet_alert_title => "現在のウォレットを変更する"; @override + String get pre_seed_button_text => "わかります。 種を見せて"; + @override String get placeholder_transactions => "あなたの取引はここに表示されます"; @override String get trade_details_provider => "プロバイダー"; @@ -4542,6 +4760,8 @@ class $ja extends S { @override String get contact => "接触"; @override + String get change_current_node_title => "現在のノードを変更する"; + @override String get openalias_alert_title => "XMR受信者が検出されました"; @override String get auth_store_incorrect_password => "間違ったPIN"; @@ -4574,11 +4794,13 @@ class $ja extends S { @override String get xmr_hidden => "非表示"; @override + String get node_connection_failed => "接続に失敗しました"; + @override String get send_xmr => "送る XMR"; @override String get exchange => "交換する"; @override - String get sync_status_failed_connect => "ノードへの接続に失敗しました"; + String get sync_status_failed_connect => "切断されました"; @override String get send_estimated_fee => "見積手数料:"; @override @@ -4620,7 +4842,7 @@ class $ja extends S { @override String get trade_details_created_at => "で作成"; @override - String get send_success => "Moneroが送信されました"; + String send_success(String crypto) => "${crypto}が送信されました"; @override String get settings_wallets => "財布"; @override @@ -4630,6 +4852,8 @@ class $ja extends S { @override String get filters => "フィルタ"; @override + String get color_theme => "カラーテーマ"; + @override String get settings_current_node => "現在のノード"; @override String get copy_id => "IDをコピー"; @@ -4650,6 +4874,8 @@ class $ja extends S { @override String get widgets_restore_from_blockheight => "ブロックの高さから復元"; @override + String get new_node_testing => "新しいノードのテスト"; + @override String get seed_language_chinese => "中国語"; @override String get new_subaddress_label_name => "ラベル名"; @@ -4758,6 +4984,8 @@ class $ja extends S { @override String get seed_language_dutch => "オランダの"; @override + String get xmr_to_error => "XMR.TOエラー"; + @override String get restore_description_from_seed_keys => "安全な場所に保存したシード/キーから財布を取り戻す"; @override String get copy => "コピー"; @@ -4782,6 +5010,8 @@ class $ja extends S { @override String get change => "変化する"; @override + String get node_test => "テスト"; + @override String get trade_state_finished => "完成した"; @override String get pin_is_incorrect => "PINが間違っています"; @@ -4860,14 +5090,20 @@ class $ja extends S { @override String get trade_details_id => "ID"; @override + String get xmr_to_error_description => "金額が無効です。 小数点以下8桁の上限"; + @override String get trade_state_confirming => "確認中"; @override + String get bright_theme => "明るい"; + @override String get send => "送る"; @override String get send_title => "を送信"; @override String get error_text_keys => "ウォレットキーには、16進数で64文字しか含めることができません"; @override + String get light_theme => "光"; + @override String get settings_save_recipient_address => "受信者のアドレスを保存"; @override String get change_exchange_provider => "Exchangeプロバイダーの変更"; @@ -4950,6 +5186,8 @@ class $ja extends S { @override String get restore_title_from_seed => "シードから復元"; @override + String get pre_seed_title => "重要"; + @override String get restore_description_from_backup => "Cake Walletアプリ全体を復元できますバックアップファイル"; @override String get error_text_node_port => "ノードポートには、0〜65535の数字のみを含めることができます"; @@ -5022,6 +5260,8 @@ class $ja extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "削除できませんでした ${wallet_name} 財布. ${error}"; @override + String provider_error(String provider) => "${provider} エラー"; + @override String copied_key_to_clipboard(String key) => "コピー済み ${key} クリップボードへ"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "読み込みに失敗しました ${wallet_name} 財布. ${error}"; @@ -5062,9 +5302,11 @@ class $pl extends S { @override String get biometric_auth_reason => "Zeskanuj swój odcisk palca, aby go uwierzytelnić"; @override + String get dark_theme => "Ciemny"; + @override String get transaction_sent => "Transakcja wysłana!"; @override - String get send_fee => "Opłata:"; + String get send_fee => "Opłata"; @override String get password => "Hasło"; @override @@ -5172,14 +5414,22 @@ class $pl extends S { @override String get trade_details_pair => "Para"; @override + String get use_ssl => "Użyj SSL"; + @override String get choose_wallet_currency => "Wybierz walutę portfela:"; @override + String pre_seed_description(int words) => "Na następnej stronie zobaczysz serię ${words} słów. To jest Twoje unikalne i prywatne ziarno i jest to JEDYNY sposób na odzyskanie portfela w przypadku utraty lub awarii. Twoim obowiązkiem jest zapisanie go i przechowywanie w bezpiecznym miejscu poza aplikacją Cake Wallet."; + @override + String get node_connection_successful => "Połączenie powiodło się"; + @override String get confirm => "Potwierdzać"; @override String get settings_display_balance_as => "Wyświetl saldo jako"; @override String get change_wallet_alert_title => "Zmień obecny portfel"; @override + String get pre_seed_button_text => "Rozumiem. Pokaż mi moje nasienie"; + @override String get placeholder_transactions => "Twoje transakcje zostaną wyświetlone tutaj"; @override String get trade_details_provider => "Dostawca"; @@ -5214,6 +5464,8 @@ class $pl extends S { @override String get contact => "Kontakt"; @override + String get change_current_node_title => "Zmień bieżący węzeł"; + @override String get openalias_alert_title => "Wykryto odbiorcę XMR"; @override String get auth_store_incorrect_password => "Niepoprawny PIN"; @@ -5246,11 +5498,13 @@ class $pl extends S { @override String get xmr_hidden => "Ukryty"; @override + String get node_connection_failed => "Połączenie nie powiodło się"; + @override String get send_xmr => "Wysłać XMR"; @override String get exchange => "Wymieniać się"; @override - String get sync_status_failed_connect => "NIE MOŻNA PODŁĄCZYĆ DO WĘZŁA"; + String get sync_status_failed_connect => "NIEPOWIĄZANY"; @override String get send_estimated_fee => "Szacowana opłata:"; @override @@ -5292,7 +5546,7 @@ class $pl extends S { @override String get trade_details_created_at => "Utworzono w"; @override - String get send_success => "Twoje Monero zostało pomyślnie wysłane"; + String send_success(String crypto) => "Twoje ${crypto} zostało pomyślnie wysłane"; @override String get settings_wallets => "Portfele"; @override @@ -5302,6 +5556,8 @@ class $pl extends S { @override String get filters => "Filtr"; @override + String get color_theme => "Motyw kolorystyczny"; + @override String get settings_current_node => "Bieżący węzeł"; @override String get copy_id => "ID kopii"; @@ -5322,6 +5578,8 @@ class $pl extends S { @override String get widgets_restore_from_blockheight => "Przywróć z wysokości bloku"; @override + String get new_node_testing => "Testowanie nowych węzłów"; + @override String get seed_language_chinese => "Chiński"; @override String get new_subaddress_label_name => "Nazwa etykiety"; @@ -5430,6 +5688,8 @@ class $pl extends S { @override String get seed_language_dutch => "Holenderski"; @override + String get xmr_to_error => "Pomyłka XMR.TO"; + @override String get restore_description_from_seed_keys => "Odzyskaj swój portfel z nasion / kluczy, które zapisałeś w bezpiecznym miejscu"; @override String get copy => "Kopiuj"; @@ -5454,6 +5714,8 @@ class $pl extends S { @override String get change => "Zmiana"; @override + String get node_test => "Test"; + @override String get trade_state_finished => "Skończone"; @override String get pin_is_incorrect => "PPIN jest niepoprawny"; @@ -5532,14 +5794,20 @@ class $pl extends S { @override String get trade_details_id => "ID"; @override + String get xmr_to_error_description => "Nieprawidłowa kwota. Maksymalny limit 8 cyfr po przecinku"; + @override String get trade_state_confirming => "Potwierdzam"; @override + String get bright_theme => "Jasny"; + @override String get send => "Wysłać"; @override String get send_title => "Wyślij"; @override String get error_text_keys => "Klucze portfela mogą zawierać tylko 64 znaki w systemie szesnastkowym"; @override + String get light_theme => "Lekki"; + @override String get settings_save_recipient_address => "Zapisz adres odbiorcy"; @override String get change_exchange_provider => "Zmień dostawcę programu Exchange"; @@ -5622,6 +5890,8 @@ class $pl extends S { @override String get restore_title_from_seed => "Przywróć z nasion"; @override + String get pre_seed_title => "WAŻNY"; + @override String get restore_description_from_backup => "Możesz przywrócić całą aplikację Cake Wallet z plik kopii zapasowej"; @override String get error_text_node_port => "Port węzła może zawierać tylko liczby od 0 do 65535"; @@ -5694,6 +5964,8 @@ class $pl extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "Nie udało się usunąć ${wallet_name} portfel. ${error}"; @override + String provider_error(String provider) => "${provider} pomyłka"; + @override String copied_key_to_clipboard(String key) => "Skopiowane ${key} do schowka"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "Nie udało się załadować ${wallet_name} portfel. ${error}"; @@ -5730,9 +6002,11 @@ class $es extends S { @override String get biometric_auth_reason => "Escanee su huella digital para autenticar"; @override + String get dark_theme => "Oscura"; + @override String get transaction_sent => "Transacción enviada!"; @override - String get send_fee => "Cuota:"; + String get send_fee => "Cuota"; @override String get password => "Contraseña"; @override @@ -5840,14 +6114,22 @@ class $es extends S { @override String get trade_details_pair => "Par"; @override + String get use_ssl => "Utilice SSL"; + @override String get choose_wallet_currency => "Por favor, elija la moneda de la billetera:"; @override + String pre_seed_description(int words) => "En la página siguiente verá una serie de ${words} palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar su billetera en caso de pérdida o mal funcionamiento. Es SU responsabilidad escribirlo y guardarlo en un lugar seguro fuera de la aplicación Cake Wallet."; + @override + String get node_connection_successful => "La conexión fue exitosa"; + @override String get confirm => "Confirmar"; @override String get settings_display_balance_as => "Mostrar saldo como"; @override String get change_wallet_alert_title => "Cambiar billetera actual"; @override + String get pre_seed_button_text => "Entiendo. Muéstrame mi semilla"; + @override String get placeholder_transactions => "Sus transacciones se mostrarán aquí"; @override String get trade_details_provider => "Proveedor"; @@ -5882,6 +6164,8 @@ class $es extends S { @override String get contact => "Contacto"; @override + String get change_current_node_title => "Cambiar el nodo actual"; + @override String get openalias_alert_title => "Destinatario XMR detectado"; @override String get auth_store_incorrect_password => "Contraseña PIN"; @@ -5914,11 +6198,13 @@ class $es extends S { @override String get xmr_hidden => "Oculto"; @override + String get node_connection_failed => "La conexión falló"; + @override String get send_xmr => "Enviar XMR"; @override String get exchange => "Intercambiar"; @override - String get sync_status_failed_connect => "CONEXIÓN FALLIDA AL NODO"; + String get sync_status_failed_connect => "DESCONECTADO"; @override String get send_estimated_fee => "Tarifa estimada:"; @override @@ -5960,7 +6246,7 @@ class $es extends S { @override String get trade_details_created_at => "Creado en"; @override - String get send_success => "Su Monero fue enviado con éxito"; + String send_success(String crypto) => "Su ${crypto} fue enviado con éxito"; @override String get settings_wallets => "Carteras"; @override @@ -5970,6 +6256,8 @@ class $es extends S { @override String get filters => "Filtrar"; @override + String get color_theme => "Tema de color"; + @override String get settings_current_node => "Nodo actual"; @override String get copy_id => "Copiar ID"; @@ -5990,6 +6278,8 @@ class $es extends S { @override String get widgets_restore_from_blockheight => "Restaurar desde blockheight"; @override + String get new_node_testing => "Prueba de nuevos nodos"; + @override String get seed_language_chinese => "Chino"; @override String get new_subaddress_label_name => "Nombre de etiqueta"; @@ -6098,6 +6388,8 @@ class $es extends S { @override String get seed_language_dutch => "Holandés"; @override + String get xmr_to_error => "Error de XMR.TO"; + @override String get restore_description_from_seed_keys => "Recupere su billetera de las semillas/claves que ha guardado en un lugar seguro"; @override String get copy => "Dupdo"; @@ -6122,6 +6414,8 @@ class $es extends S { @override String get change => "Cambio"; @override + String get node_test => "Prueba"; + @override String get trade_state_finished => "Terminado"; @override String get pin_is_incorrect => "PIN es incorrecto"; @@ -6200,14 +6494,20 @@ class $es extends S { @override String get trade_details_id => "ID"; @override + String get xmr_to_error_description => "Monto invalido. Límite máximo de 8 dígitos después del punto decimal"; + @override String get trade_state_confirming => "Confirmando"; @override + String get bright_theme => "Brillante"; + @override String get send => "Enviar"; @override String get send_title => "Enviar"; @override String get error_text_keys => "Las llaves de billetera solo pueden contener 64 caracteres en hexadecimal"; @override + String get light_theme => "Ligera"; + @override String get settings_save_recipient_address => "Guardar dirección del destinatario"; @override String get change_exchange_provider => "Cambiar proveedor de intercambio"; @@ -6290,6 +6590,8 @@ class $es extends S { @override String get restore_title_from_seed => "De la semilla"; @override + String get pre_seed_title => "IMPORTANTE"; + @override String get restore_description_from_backup => "Puede restaurar toda la aplicación Cake Wallet desde ysu archivo de respaldo"; @override String get error_text_node_port => "El puerto de nodo solo puede contener números entre 0 y 65535"; @@ -6362,6 +6664,8 @@ class $es extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "Error al elimina ${wallet_name} billetera. ${error}"; @override + String provider_error(String provider) => "${provider} error"; + @override String copied_key_to_clipboard(String key) => "Copiado ${key} al portapapeles"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "No se pudo cargar ${wallet_name} la billetera. ${error}"; @@ -6398,9 +6702,11 @@ class $nl extends S { @override String get biometric_auth_reason => "Scan uw vingerafdruk om te verifiëren"; @override + String get dark_theme => "Donker"; + @override String get transaction_sent => "Transactie verzonden!"; @override - String get send_fee => "Vergoeding:"; + String get send_fee => "Vergoeding"; @override String get password => "Wachtwoord"; @override @@ -6508,14 +6814,22 @@ class $nl extends S { @override String get trade_details_pair => "Paar"; @override + String get use_ssl => "Gebruik SSL"; + @override String get choose_wallet_currency => "Kies een portemonnee-valuta:"; @override + String pre_seed_description(int words) => "Op de volgende pagina ziet u een reeks van ${words} woorden. Dit is uw unieke en persoonlijke zaadje en het is de ENIGE manier om uw portemonnee te herstellen in geval van verlies of storing. Het is JOUW verantwoordelijkheid om het op te schrijven en op een veilige plaats op te slaan buiten de Cake Wallet app."; + @override + String get node_connection_successful => "Verbinding is gelukt"; + @override String get confirm => "Bevestigen"; @override String get settings_display_balance_as => "Toon saldo als"; @override String get change_wallet_alert_title => "Wijzig huidige portemonnee"; @override + String get pre_seed_button_text => "Ik begrijp het. Laat me mijn zaad zien"; + @override String get placeholder_transactions => "Uw transacties worden hier weergegeven"; @override String get trade_details_provider => "Leverancier"; @@ -6550,6 +6864,8 @@ class $nl extends S { @override String get contact => "Contact"; @override + String get change_current_node_title => "Wijzig het huidige knooppunt"; + @override String get openalias_alert_title => "XMR-ontvanger gedetecteerd"; @override String get auth_store_incorrect_password => "Incorrect PIN"; @@ -6582,11 +6898,13 @@ class $nl extends S { @override String get xmr_hidden => "Verborgen"; @override + String get node_connection_failed => "De verbinding is mislukt"; + @override String get send_xmr => "Sturen XMR"; @override String get exchange => "Uitwisseling"; @override - String get sync_status_failed_connect => "MISLUKT VERBINDING MET DE NODE"; + String get sync_status_failed_connect => "LOSGEKOPPELD"; @override String get send_estimated_fee => "Geschatte vergoeding:"; @override @@ -6628,7 +6946,7 @@ class $nl extends S { @override String get trade_details_created_at => "Gemaakt bij"; @override - String get send_success => "Uw Monero is succesvol verzonden"; + String send_success(String crypto) => "Uw ${crypto} is succesvol verzonden"; @override String get settings_wallets => "Portemonnee"; @override @@ -6638,6 +6956,8 @@ class $nl extends S { @override String get filters => "Filter"; @override + String get color_theme => "Kleur thema"; + @override String get settings_current_node => "Huidige knooppunt"; @override String get copy_id => "ID kopiëren"; @@ -6658,6 +6978,8 @@ class $nl extends S { @override String get widgets_restore_from_blockheight => "Herstel vanaf blockheight"; @override + String get new_node_testing => "Nieuwe knooppunttest"; + @override String get seed_language_chinese => "Chinese"; @override String get new_subaddress_label_name => "Label naam"; @@ -6766,6 +7088,8 @@ class $nl extends S { @override String get seed_language_dutch => "Nederlands"; @override + String get xmr_to_error => "XMR.TO-fout"; + @override String get restore_description_from_seed_keys => "Ontvang uw portemonnee terug uit seed / keys die u hebt opgeslagen op een veilige plaats"; @override String get copy => "Kopiëren"; @@ -6790,6 +7114,8 @@ class $nl extends S { @override String get change => "Verandering"; @override + String get node_test => "Test"; + @override String get trade_state_finished => "Afgewerkt"; @override String get pin_is_incorrect => "PIN is onjuist"; @@ -6868,14 +7194,20 @@ class $nl extends S { @override String get trade_details_id => "ID"; @override + String get xmr_to_error_description => "Ongeldige hoeveelheid. Maximaal 8 cijfers achter de komma"; + @override String get trade_state_confirming => "Bevestiging"; @override + String get bright_theme => "Helder"; + @override String get send => "Sturen"; @override String get send_title => "Stuur"; @override String get error_text_keys => "Portefeuillesleutels kunnen maximaal 64 tekens bevatten in hexadecimale volgorde"; @override + String get light_theme => "Licht"; + @override String get settings_save_recipient_address => "Adres ontvanger opslaan"; @override String get change_exchange_provider => "Wijzig Exchange Provider"; @@ -6958,6 +7290,8 @@ class $nl extends S { @override String get restore_title_from_seed => "Herstel van zaad"; @override + String get pre_seed_title => "BELANGRIJK"; + @override String get restore_description_from_backup => "Je kunt de hele Cake Wallet-app herstellen van uw back-upbestand"; @override String get error_text_node_port => "Knooppuntpoort kan alleen nummers tussen 0 en 65535 bevatten"; @@ -7030,6 +7364,8 @@ class $nl extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "Verwijderen mislukt ${wallet_name} portemonnee. ${error}"; @override + String provider_error(String provider) => "${provider} fout"; + @override String copied_key_to_clipboard(String key) => "Gekopieerd ${key} naar het klembord"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "Laden mislukt ${wallet_name} portemonnee. ${error}"; @@ -7066,9 +7402,11 @@ class $zh extends S { @override String get biometric_auth_reason => "掃描指紋以進行身份驗證"; @override + String get dark_theme => "黑暗"; + @override String get transaction_sent => "交易已发送"; @override - String get send_fee => "費用:"; + String get send_fee => "費用"; @override String get password => "密码"; @override @@ -7176,14 +7514,22 @@ class $zh extends S { @override String get trade_details_pair => "对"; @override + String get use_ssl => "使用SSL"; + @override String get choose_wallet_currency => "請選擇錢包貨幣:"; @override + String pre_seed_description(int words) => "在下一頁上,您將看到一系列${words}個單詞。 這是您獨特的私人種子,是丟失或出現故障時恢復錢包的唯一方法。 您有責任將其寫下並存儲在Cake Wallet應用程序外部的安全地方。"; + @override + String get node_connection_successful => "連接成功"; + @override String get confirm => "确认"; @override String get settings_display_balance_as => "将余额显示为"; @override String get change_wallet_alert_title => "更換當前錢包"; @override + String get pre_seed_button_text => "我明白。 給我看我的種子"; + @override String get placeholder_transactions => "您的交易將顯示在這裡"; @override String get trade_details_provider => "提供者"; @@ -7218,6 +7564,8 @@ class $zh extends S { @override String get contact => "联系"; @override + String get change_current_node_title => "更改當前節點"; + @override String get openalias_alert_title => "檢測到XMR收件人"; @override String get auth_store_incorrect_password => "PIN码错误"; @@ -7250,11 +7598,13 @@ class $zh extends S { @override String get xmr_hidden => "隱"; @override + String get node_connection_failed => "連接失敗"; + @override String get send_xmr => "发送 XMR"; @override String get exchange => "交换"; @override - String get sync_status_failed_connect => "无法连接到节点"; + String get sync_status_failed_connect => "斷線"; @override String get send_estimated_fee => "预估费用:"; @override @@ -7296,7 +7646,7 @@ class $zh extends S { @override String get trade_details_created_at => "创建于"; @override - String get send_success => "你Monero已成功發送"; + String send_success(String crypto) => "你${crypto}已成功發送"; @override String get settings_wallets => "皮夹"; @override @@ -7306,6 +7656,8 @@ class $zh extends S { @override String get filters => "過濾"; @override + String get color_theme => "顏色主題"; + @override String get settings_current_node => "当前节点"; @override String get copy_id => "复印ID"; @@ -7326,6 +7678,8 @@ class $zh extends S { @override String get widgets_restore_from_blockheight => "从块高还原"; @override + String get new_node_testing => "新節點測試"; + @override String get seed_language_chinese => "中文"; @override String get new_subaddress_label_name => "标签名称"; @@ -7434,6 +7788,8 @@ class $zh extends S { @override String get seed_language_dutch => "荷蘭人"; @override + String get xmr_to_error => "XMR.TO錯誤"; + @override String get restore_description_from_seed_keys => "从保存到安全地方的种子/钥匙取回钱包"; @override String get copy => "复制"; @@ -7458,6 +7814,8 @@ class $zh extends S { @override String get change => "更改"; @override + String get node_test => "測試"; + @override String get trade_state_finished => "已完成"; @override String get pin_is_incorrect => "PIN码不正确"; @@ -7536,14 +7894,20 @@ class $zh extends S { @override String get trade_details_id => "ID"; @override + String get xmr_to_error_description => "無效的金額。 小數點後最多8位數字"; + @override String get trade_state_confirming => "确认中"; @override + String get bright_theme => "亮"; + @override String get send => "发送"; @override String get send_title => "發送"; @override String get error_text_keys => "钱包密钥只能包含16个字符的十六进制字符"; @override + String get light_theme => "光"; + @override String get settings_save_recipient_address => "保存收件人地址"; @override String get change_exchange_provider => "更改交易所提供商"; @@ -7626,6 +7990,8 @@ class $zh extends S { @override String get restore_title_from_seed => "从种子还原"; @override + String get pre_seed_title => "重要"; + @override String get restore_description_from_backup => "您可以从还原整个Cake Wallet应用您的备份文件"; @override String get error_text_node_port => "节点端口只能包含0到65535之间的数字"; @@ -7698,6 +8064,8 @@ class $zh extends S { @override String wallet_list_failed_to_remove(String wallet_name, String error) => "删除失败 ${wallet_name} 钱包. ${error}"; @override + String provider_error(String provider) => "${provider} 錯誤"; + @override String copied_key_to_clipboard(String key) => "复制 ${key} 到剪贴板"; @override String wallet_list_failed_to_load(String wallet_name, String error) => "加载失败 ${wallet_name} 钱包. ${error}"; diff --git a/lib/main.dart b/lib/main.dart index 9cd55e054..38460fcca 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hive/hive.dart'; @@ -83,7 +84,6 @@ void main() async { ))))); } } - Future initialSetup( {@required SharedPreferences sharedPreferences, @required Box nodes, @@ -123,27 +123,25 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { final settingsStore = getIt.get().settingsStore; - - if (settingsStore.theme == null) { - settingsStore.isDarkTheme = false; - } - final statusBarColor = Colors.transparent; - final statusBarBrightness = - settingsStore.isDarkTheme ? Brightness.light : Brightness.dark; - final statusBarIconBrightness = - settingsStore.isDarkTheme ? Brightness.light : Brightness.dark; final authenticationStore = getIt.get(); final initialRoute = authenticationStore.state == AuthenticationState.denied ? Routes.disclaimer : Routes.login; - SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( - statusBarColor: statusBarColor, - statusBarBrightness: statusBarBrightness, - statusBarIconBrightness: statusBarIconBrightness)); - return Observer(builder: (BuildContext context) { + final currentTheme = settingsStore.currentTheme; + final statusBarBrightness = currentTheme.type == ThemeType.dark + ? Brightness.light + : Brightness.dark; + final statusBarIconBrightness = currentTheme.type == ThemeType.dark + ? Brightness.light + : Brightness.dark; + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + statusBarColor: statusBarColor, + statusBarBrightness: statusBarBrightness, + statusBarIconBrightness: statusBarIconBrightness)); + return Root( authenticationStore: authenticationStore, navigatorKey: navigatorKey, diff --git a/lib/monero/monero_balance.dart b/lib/monero/monero_balance.dart index c3cca86a7..6183e087d 100644 --- a/lib/monero/monero_balance.dart +++ b/lib/monero/monero_balance.dart @@ -1,20 +1,42 @@ +import 'package:cake_wallet/entities/balance.dart'; +import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:flutter/foundation.dart'; import 'package:cake_wallet/monero/monero_amount_format.dart'; -class MoneroBalance { +class MoneroBalance extends Balance { MoneroBalance({@required this.fullBalance, @required this.unlockedBalance}) : formattedFullBalance = moneroAmountToString(amount: fullBalance), formattedUnlockedBalance = - moneroAmountToString(amount: unlockedBalance); + moneroAmountToString(amount: unlockedBalance), + super(const [ + BalanceDisplayMode.availableBalance, + BalanceDisplayMode.fullBalance + ]); MoneroBalance.fromString( {@required this.formattedFullBalance, - @required this.formattedUnlockedBalance}) + @required this.formattedUnlockedBalance}) : fullBalance = moneroParseAmount(amount: formattedFullBalance), - unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance); + unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance), + super(const [ + BalanceDisplayMode.availableBalance, + BalanceDisplayMode.fullBalance + ]); final int fullBalance; final int unlockedBalance; final String formattedFullBalance; final String formattedUnlockedBalance; -} \ No newline at end of file + + @override + String formattedBalance(BalanceDisplayMode mode) { + switch (mode) { + case BalanceDisplayMode.fullBalance: + return formattedFullBalance; + case BalanceDisplayMode.availableBalance: + return formattedUnlockedBalance; + default: + return null; + } + } +} diff --git a/lib/monero/monero_wallet.dart b/lib/monero/monero_wallet.dart index 2d4878b4c..048ca952e 100644 --- a/lib/monero/monero_wallet.dart +++ b/lib/monero/monero_wallet.dart @@ -40,6 +40,10 @@ abstract class MoneroWalletBase extends WalletBase with Store { fullBalance: monero_wallet.getFullBalance(accountIndex: 0), unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0)); _onAccountChangeReaction = reaction((_) => account, (Account account) { + balance = MoneroBalance( + fullBalance: monero_wallet.getFullBalance(accountIndex: account.id), + unlockedBalance: + monero_wallet.getUnlockedBalance(accountIndex: account.id)); subaddressList.update(accountIndex: account.id); subaddress = subaddressList.subaddresses.first; address = subaddress.address; @@ -94,7 +98,7 @@ abstract class MoneroWalletBase extends WalletBase with Store { bool _isSavingAfterNewTransaction; Future init() async { - await accountList.update(); + accountList.update(); account = accountList.accounts.first; subaddressList.update(accountIndex: account.id ?? 0); subaddress = subaddressList.getAll().first; @@ -116,6 +120,7 @@ abstract class MoneroWalletBase extends WalletBase with Store { } } + @override void close() { _listener?.stop(); _onAccountChangeReaction?.reaction?.dispose(); @@ -147,8 +152,7 @@ abstract class MoneroWalletBase extends WalletBase with Store { address: node.uri, login: node.login, password: node.password, - useSSL: false, - // FIXME: hardcoded value + useSSL: node.isSSL, isLightWallet: false); // FIXME: hardcoded value syncStatus = ConnectedSyncStatus(); } catch (e) { @@ -256,6 +260,7 @@ abstract class MoneroWalletBase extends WalletBase with Store { monero_wallet.rescanBlockchainAsync(); await startSync(); _askForUpdateBalance(); + accountList.update(); await _askForUpdateTransactionHistory(); await save(); await walletInfo.save(); @@ -311,9 +316,8 @@ abstract class MoneroWalletBase extends WalletBase with Store { } } - Future _askForUpdateTransactionHistory() async { - await transactionHistory.update(); - } + Future _askForUpdateTransactionHistory() async => + await transactionHistory.update(); int _getFullBalance() => monero_wallet.getFullBalance(accountIndex: account.id); @@ -322,13 +326,13 @@ abstract class MoneroWalletBase extends WalletBase with Store { monero_wallet.getUnlockedBalance(accountIndex: account.id); Future _afterSyncSave() async { - if (_isSavingAfterSync) { - return; - } - - _isSavingAfterSync = true; - try { + if (_isSavingAfterSync) { + return; + } + + _isSavingAfterSync = true; + final nowTimestamp = DateTime.now().millisecondsSinceEpoch; final sum = _lastAutosaveTimestamp + _autoAfterSyncSaveInterval; @@ -346,13 +350,13 @@ abstract class MoneroWalletBase extends WalletBase with Store { } Future _afterNewTransactionSave() async { - if (_isSavingAfterNewTransaction) { - return; - } - - _isSavingAfterNewTransaction = true; - try { + if (_isSavingAfterNewTransaction) { + return; + } + + _isSavingAfterNewTransaction = true; + await save(); } catch (e) { print(e.toString()); @@ -362,27 +366,38 @@ abstract class MoneroWalletBase extends WalletBase with Store { } void _onNewBlock(int height, int blocksLeft, double ptc) async { - if (walletInfo.isRecovery) { - _askForUpdateTransactionHistory(); - _askForUpdateBalance(); - } - - if (blocksLeft < 100) { - _askForUpdateBalance(); - syncStatus = SyncedSyncStatus(); - await _afterSyncSave(); - + try { if (walletInfo.isRecovery) { - setAsRecovered(); + await _askForUpdateTransactionHistory(); + _askForUpdateBalance(); + accountList.update(); } - } else { - syncStatus = SyncingSyncStatus(blocksLeft, ptc); + + if (blocksLeft < 100) { + await _askForUpdateTransactionHistory(); + _askForUpdateBalance(); + accountList.update(); + syncStatus = SyncedSyncStatus(); + await _afterSyncSave(); + + if (walletInfo.isRecovery) { + await setAsRecovered(); + } + } else { + syncStatus = SyncingSyncStatus(blocksLeft, ptc); + } + } catch (e) { + print(e.toString()); } } void _onNewTransaction() { - _askForUpdateTransactionHistory(); - _askForUpdateBalance(); - Timer(Duration(seconds: 1), () => _afterNewTransactionSave()); + try { + _askForUpdateTransactionHistory(); + _askForUpdateBalance(); + Timer(Duration(seconds: 1), () => _afterNewTransactionSave()); + } catch (e) { + print(e.toString()); + } } } diff --git a/lib/palette.dart b/lib/palette.dart index f9d0a1e3c..5f18ac62f 100644 --- a/lib/palette.dart +++ b/lib/palette.dart @@ -4,7 +4,7 @@ class Palette { static const Color green = Color.fromRGBO(39, 206, 80, 1.0); static const Color red = Color.fromRGBO(255, 51, 51, 1.0); static const Color darkRed = Color.fromRGBO(204, 38, 38, 1.0); - static const Color blueAlice = Color.fromRGBO(231, 240, 253, 1.0); + static const Color blueAlice = Color.fromRGBO(229, 247, 255, 1.0); static const Color lightBlue = Color.fromRGBO(172, 203, 238, 1.0); static const Color lavender = Color.fromRGBO(237, 245, 252, 1.0); static const Color oceanBlue = Color.fromRGBO(30, 52, 78, 1.0); @@ -13,7 +13,6 @@ class Palette { static const Color blue = Color.fromRGBO(88, 143, 252, 1.0); static const Color darkLavender = Color.fromRGBO(229, 238, 250, 1.0); static const Color nightBlue = Color.fromRGBO(46, 57, 96, 1.0); - static const Color moderateOrangeYellow = Color.fromRGBO(245, 134, 82, 1.0); static const Color moderateOrange = Color.fromRGBO(235, 117, 63, 1.0); static const Color shineGreen = Color.fromRGBO(76, 189, 87, 1.0); @@ -22,9 +21,8 @@ class Palette { static const Color royalBlue = Color.fromRGBO(43, 114, 221, 1.0); static const Color lightRed = Color.fromRGBO(227, 87, 87, 1.0); static const Color persianRed = Color.fromRGBO(206, 55, 55, 1.0); - - // NEW DESIGN static const Color blueCraiola = Color.fromRGBO(69, 110, 255, 1.0); + static const Color blueGreyCraiola = Color.fromRGBO(106, 177, 207, 1.0); static const Color darkBlueCraiola = Color.fromRGBO(53, 86, 136, 1.0); static const Color pinkFlamingo = Color.fromRGBO(240, 60, 243, 1.0); static const Color redHat = Color.fromRGBO(209, 68, 37, 1.0); @@ -43,26 +41,17 @@ class Palette { static const Color alizarinRed = Color.fromRGBO(233, 45, 45, 1.0); static const Color moderateSlateBlue = Color.fromRGBO(129, 93, 251, 1.0); static const Color brightOrange = Color.fromRGBO(255, 102, 0, 1.0); - - // FIXME: Rename. - static const Color eee = Color.fromRGBO(236, 239, 245, 1.0); - static const Color xxx = Color.fromRGBO(72, 89, 109, 1); + static const Color dullGray = Color.fromRGBO(98, 98, 98, 1.0); + static const Color protectiveBlue = Color.fromRGBO(33, 148, 255, 1.0); } class PaletteDark { - //static const Color distantBlue = Color.fromRGBO(70, 85, 133, 1.0); // mainBackgroundColor - static const Color lightDistantBlue = Color.fromRGBO(81, 96, 147, 1.0); // borderCardColor - static const Color gray = Color.fromRGBO(140, 153, 201, 1.0); // walletCardText - //static const Color violetBlue = Color.fromRGBO(51, 63, 104, 1.0); // walletCardAddressField - //static const Color moderateBlue = Color.fromRGBO(63, 77, 122, 1.0); // walletCardSubAddressField - //static const Color darkNightBlue = Color.fromRGBO(33, 43, 73, 1.0); // historyPanel - static const Color pigeonBlue = Color.fromRGBO(91, 112, 146, 1.0); // historyPanelText - static const Color moderateNightBlue = Color.fromRGBO(39, 53, 96, 1.0); // historyPanelButton - static const Color headerNightBlue = Color.fromRGBO(41, 52, 84, 1.0); // menuHeader - //static const Color lightNightBlue = Color.fromRGBO(48, 59, 95, 1.0); // menuList - static const Color moderatePurpleBlue = Color.fromRGBO(57, 74, 95, 1.0); // selectButtonText - - // NEW DESIGN + static const Color lightDistantBlue = Color.fromRGBO(81, 96, 147, 1.0); + static const Color gray = Color.fromRGBO(140, 153, 201, 1.0); + static const Color pigeonBlue = Color.fromRGBO(91, 112, 146, 1.0); + static const Color moderateNightBlue = Color.fromRGBO(39, 53, 96, 1.0); + static const Color headerNightBlue = Color.fromRGBO(41, 52, 84, 1.0); + static const Color moderatePurpleBlue = Color.fromRGBO(57, 74, 95, 1.0); static const Color backgroundColor = Color.fromRGBO(25, 35, 60, 1.0); static const Color nightBlue = Color.fromRGBO(35, 47, 79, 1.0); static const Color wildNightBlue = Color.fromRGBO(39, 53, 96, 1.0); @@ -94,8 +83,5 @@ class PaletteDark { static const Color deepVioletBlue = Color.fromRGBO(52, 66, 104, 1.0); static const Color lightPurpleBlue = Color.fromRGBO(120, 133, 170, 1.0); static const Color indicatorVioletBlue = Color.fromRGBO(59, 72, 119, 1.0); - - // FIXME: Rename. - static const Color eee = Color.fromRGBO(236, 239, 245, 1.0); - static const Color xxx = Color.fromRGBO(72, 89, 109, 1); + static const Color granite = Color.fromRGBO(48, 51, 60, 1.0); } \ No newline at end of file diff --git a/lib/reactions/fiat_rate_update.dart b/lib/reactions/fiat_rate_update.dart index 7d2f1a097..365423ef4 100644 --- a/lib/reactions/fiat_rate_update.dart +++ b/lib/reactions/fiat_rate_update.dart @@ -12,12 +12,13 @@ Future startFiatRateUpdate(AppStore appStore, SettingsStore settingsStore, return; } - fiatConversionStore.price = await FiatConversionService.fetchPrice( - appStore.wallet.currency, settingsStore.fiatCurrency); + fiatConversionStore.prices[appStore.wallet.currency] = + await FiatConversionService.fetchPrice( + appStore.wallet.currency, settingsStore.fiatCurrency); _timer = Timer.periodic( Duration(seconds: 30), - (_) async => fiatConversionStore.price = + (_) async => fiatConversionStore.prices[appStore.wallet.currency] = await FiatConversionService.fetchPrice( appStore.wallet.currency, settingsStore.fiatCurrency)); } diff --git a/lib/reactions/on_current_fiat_change.dart b/lib/reactions/on_current_fiat_change.dart index 28c6f621c..d7d9c2e82 100644 --- a/lib/reactions/on_current_fiat_change.dart +++ b/lib/reactions/on_current_fiat_change.dart @@ -7,12 +7,13 @@ import 'package:cake_wallet/entities/fiat_currency.dart'; ReactionDisposer _onCurrentFiatCurrencyChangeDisposer; -void startCurrentFiatChangeReaction(AppStore appStore, SettingsStore settingsStore, FiatConversionStore fiatConversionStore) { +void startCurrentFiatChangeReaction(AppStore appStore, + SettingsStore settingsStore, FiatConversionStore fiatConversionStore) { _onCurrentFiatCurrencyChangeDisposer?.reaction?.dispose(); _onCurrentFiatCurrencyChangeDisposer = reaction( - (_) => settingsStore.fiatCurrency, (FiatCurrency fiatCurrency) async { + (_) => settingsStore.fiatCurrency, (FiatCurrency fiatCurrency) async { final cryptoCurrency = appStore.wallet.currency; - fiatConversionStore.price = await FiatConversionService.fetchPrice( - cryptoCurrency, fiatCurrency); + fiatConversionStore.prices[appStore.wallet.currency] = + await FiatConversionService.fetchPrice(cryptoCurrency, fiatCurrency); }); -} \ No newline at end of file +} diff --git a/lib/reactions/on_current_node_change.dart b/lib/reactions/on_current_node_change.dart index c03bbac21..2922e6ec7 100644 --- a/lib/reactions/on_current_node_change.dart +++ b/lib/reactions/on_current_node_change.dart @@ -6,10 +6,9 @@ ReactionDisposer _onCurrentNodeChangeReaction; void startOnCurrentNodeChangeReaction(AppStore appStore) { _onCurrentNodeChangeReaction?.reaction?.dispose(); - _onCurrentNodeChangeReaction = - reaction((_) => appStore.settingsStore.currentNode, (Node node) async { + appStore.settingsStore.nodes.observe((change) async { try { - await appStore.wallet.connectToNode(node: node); + await appStore.wallet.connectToNode(node: change.newValue); } catch (e) { print(e.toString()); } diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 6b9b447b6..117eafd33 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -12,6 +12,7 @@ import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; ReactionDisposer _onCurrentWalletChangeReaction; +ReactionDisposer _onCurrentWalletChangeFiatRateUpdateReaction; void startCurrentWalletChangeReaction(AppStore appStore, SettingsStore settingsStore, FiatConversionStore fiatConversionStore) { @@ -29,8 +30,16 @@ void startCurrentWalletChangeReaction(AppStore appStore, await getIt.get().setInt( PreferencesKey.currentWalletType, serializeToInt(wallet.type)); await wallet.connectToNode(node: node); + } catch (e) { + print(e.toString()); + } + }); - fiatConversionStore.price = await FiatConversionService.fetchPrice( + _onCurrentWalletChangeFiatRateUpdateReaction = + reaction((_) => appStore.wallet, (WalletBase wallet) async { + try { + fiatConversionStore.prices[wallet.currency] = 0; + fiatConversionStore.prices[wallet.currency] = await FiatConversionService.fetchPrice( wallet.currency, settingsStore.fiatCurrency); } catch (e) { print(e.toString()); diff --git a/lib/router.dart b/lib/router.dart index acad61d04..93b56071b 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -2,7 +2,9 @@ import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; +import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -60,32 +62,22 @@ Route createRoute(RouteSettings settings) { case Routes.newWalletFromWelcome: return CupertinoPageRoute( - builder: (_) => getIt.get(param1: - (PinCodeState context, dynamic _) async { - try { - context.changeProcessText( - 'Creating new wallet'); // FIXME: Unnamed constant - final newWalletVM = - getIt.get(param1: WalletType.monero); - await newWalletVM.create( - options: 'English'); // FIXME: Unnamed constant - context.hideProgressText(); - await Navigator.of(context.context) - .pushNamed(Routes.seed, arguments: true); - } catch (e) { - context.changeProcessText('Error: ${e.toString()}'); - } - }), + builder: (_) => getIt.get( + param1: (PinCodeState context, dynamic _) => + Navigator.of(context.context) + .pushNamed(Routes.newWalletType)), fullscreenDialog: true); case Routes.newWalletType: return CupertinoPageRoute( - builder: (_) => NewWalletTypePage( - onTypeSelected: (context, type) => Navigator.of(context) - .pushNamed(Routes.newWallet, arguments: type))); + builder: (_) => getIt.get( + param1: (BuildContext context, WalletType type) => + Navigator.of(context) + .pushNamed(Routes.preSeed, arguments: type), + param2: true)); case Routes.newWallet: - final type = WalletType.monero; // settings.arguments as WalletType; + final type = settings.arguments as WalletType; final walletNewVM = getIt.get(param1: type); return CupertinoPageRoute( @@ -104,11 +96,11 @@ Route createRoute(RouteSettings settings) { case Routes.restoreWalletType: return CupertinoPageRoute( - builder: (_) => NewWalletTypePage( - onTypeSelected: (context, type) => Navigator.of(context) - .pushNamed(Routes.restoreWalletOptions, arguments: type), - isNewWallet: false, - )); + builder: (_) => getIt.get( + param1: (BuildContext context, WalletType type) => + Navigator.of(context) + .pushNamed(Routes.restoreWallet, arguments: type), + param2: false)); case Routes.restoreOptions: final type = settings.arguments as WalletType; @@ -146,7 +138,8 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute( builder: (_) => getIt.get( param1: (PinCodeState context, dynamic _) => - Navigator.pushNamed(context.context, Routes.restoreWallet)), + Navigator.pushNamed( + context.context, Routes.restoreWalletType)), fullscreenDialog: true); case Routes.seed: @@ -156,16 +149,11 @@ Route createRoute(RouteSettings settings) { case Routes.restoreWallet: return MaterialPageRoute( - builder: (_) => - getIt.get(param1: WalletType.monero)); + builder: (_) => getIt.get( + param1: settings.arguments as WalletType)); case Routes.restoreWalletFromSeed: - // final args = settings.arguments as List; - final type = WalletType.monero; //args.first as WalletType; - // final language = type == WalletType.monero - // ? args[1] as String - // : LanguageList.english; - + final type = settings.arguments as WalletType; return CupertinoPageRoute( builder: (_) => RestoreWalletFromSeedPage(type: type)); @@ -261,7 +249,8 @@ Route createRoute(RouteSettings settings) { case Routes.accountCreation: return CupertinoPageRoute( - builder: (_) => getIt.get()); + builder: (_) => getIt.get( + param1: settings.arguments as AccountListItem)); case Routes.addressBook: return MaterialPageRoute( @@ -322,6 +311,11 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute( builder: (_) => getIt.get()); + case Routes.preSeed: + return MaterialPageRoute( + builder: (_) => + getIt.get(param1: settings.arguments as WalletType)); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index 13a365990..a9c9a5b34 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -47,4 +47,5 @@ class Routes { static const exchangeTemplate = '/exchange_template'; static const restoreWalletType = '/restore_wallet_type'; static const restoreWallet = '/restore_wallet'; + static const preSeed = '/pre_seed'; } \ No newline at end of file diff --git a/lib/src/screens/base_page.dart b/lib/src/screens/base_page.dart index b34cb7310..77e0e92fe 100644 --- a/lib/src/screens/base_page.dart +++ b/lib/src/screens/base_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/palette.dart'; @@ -9,13 +10,13 @@ enum AppBarStyle { regular, withShadow, transparent } abstract class BasePage extends StatelessWidget { BasePage() - : _scaffoldKey = GlobalKey(), - _closeButtonImage = Image.asset('assets/images/close_button.png'), - _closeButtonImageDarkTheme = - Image.asset('assets/images/close_button_dark_theme.png'); + : _scaffoldKey = GlobalKey(); final GlobalKey _scaffoldKey; - final Image _closeButtonImage; - final Image _closeButtonImageDarkTheme; + + final Image closeButtonImage = + Image.asset('assets/images/close_button.png'); + final Image closeButtonImageDarkTheme = + Image.asset('assets/images/close_button_dark_theme.png'); String get title => null; @@ -37,7 +38,7 @@ abstract class BasePage extends StatelessWidget { Widget Function(BuildContext, Widget) get rootWrapper => null; - bool get isDarkTheme => getIt.get().isDarkTheme; + ThemeBase get currentTheme => getIt.get().currentTheme; void onOpenEndDrawer() => _scaffoldKey.currentState.openEndDrawer(); @@ -51,8 +52,8 @@ abstract class BasePage extends StatelessWidget { final _backButton = Icon(Icons.arrow_back_ios, color: titleColor ?? Theme.of(context).primaryTextTheme.title.color, size: 16,); - final _closeButton = - isDarkTheme ? _closeButtonImageDarkTheme : _closeButtonImage; + final _closeButton = currentTheme.type == ThemeType.dark + ? closeButtonImageDarkTheme : closeButtonImage; return SizedBox( height: 37, @@ -88,8 +89,8 @@ abstract class BasePage extends StatelessWidget { Widget floatingActionButton(BuildContext context) => null; ObstructingPreferredSizeWidget appBar(BuildContext context) { - final appBarColor = - isDarkTheme ? backgroundDarkColor : backgroundLightColor; + final appBarColor = currentTheme.type == ThemeType.dark + ? backgroundDarkColor : backgroundLightColor; switch (appBarStyle) { case AppBarStyle.regular: @@ -131,10 +132,12 @@ abstract class BasePage extends StatelessWidget { @override Widget build(BuildContext context) { + final _backgroundColor = currentTheme.type == ThemeType.dark + ? backgroundDarkColor : backgroundLightColor; + final root = Scaffold( key: _scaffoldKey, - backgroundColor: - isDarkTheme ? backgroundDarkColor : backgroundLightColor, + backgroundColor: _backgroundColor, resizeToAvoidBottomPadding: resizeToAvoidBottomPadding, extendBodyBehindAppBar: extendBodyBehindAppBar, endDrawer: endDrawer, diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index 0d7347d24..b88afced6 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; @@ -20,7 +21,8 @@ class DashboardPage extends BasePage { }); @override - Color get backgroundLightColor => Colors.transparent; + Color get backgroundLightColor => currentTheme.type == ThemeType.bright + ? Colors.transparent : Colors.white; @override Color get backgroundDarkColor => Colors.transparent; @@ -50,7 +52,8 @@ class DashboardPage extends BasePage { @override Widget trailing(BuildContext context) { final menuButton = - Image.asset('assets/images/menu.png', color: Colors.white); + Image.asset('assets/images/menu.png', + color: Theme.of(context).accentTextTheme.display3.backgroundColor); return Container( alignment: Alignment.centerRight, @@ -65,12 +68,6 @@ class DashboardPage extends BasePage { final DashboardViewModel walletViewModel; final WalletAddressListViewModel addressListViewModel; - final sendImage = Image.asset('assets/images/upload.png', - height: 22.24, width: 24, color: Colors.white); - final exchangeImage = Image.asset('assets/images/transfer.png', - height: 24.27, width: 22.25, color: Colors.white); - final receiveImage = Image.asset('assets/images/download.png', - height: 22.24, width: 24, color: Colors.white); final controller = PageController(initialPage: 1); var pages = []; @@ -78,6 +75,15 @@ class DashboardPage extends BasePage { @override Widget body(BuildContext context) { + final sendImage = Image.asset('assets/images/upload.png', + height: 22.24, width: 24, + color: Theme.of(context).accentTextTheme.display3.backgroundColor); + final exchangeImage = Image.asset('assets/images/transfer.png', + height: 24.27, width: 22.25, + color: Theme.of(context).accentTextTheme.display3.backgroundColor); + final receiveImage = Image.asset('assets/images/download.png', + height: 22.24, width: 24, + color: Theme.of(context).accentTextTheme.display3.backgroundColor); _setEffects(); return SafeArea( @@ -100,7 +106,8 @@ class DashboardPage extends BasePage { dotWidth: 6.0, dotHeight: 6.0, dotColor: Theme.of(context).indicatorColor, - activeDotColor: Colors.white), + activeDotColor: Theme.of(context).accentTextTheme.display1 + .backgroundColor), )), Container( padding: EdgeInsets.only(left: 45, right: 45, bottom: 24), diff --git a/lib/src/screens/dashboard/wallet_menu.dart b/lib/src/screens/dashboard/wallet_menu.dart index a4142d426..273cdd9bf 100644 --- a/lib/src/screens/dashboard/wallet_menu.dart +++ b/lib/src/screens/dashboard/wallet_menu.dart @@ -9,44 +9,53 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; // FIXME: terrible design class WalletMenu { - WalletMenu(this.context, this.reconnect); - - final List items = [ - WalletMenuItem( - title: S.current.reconnect, - image: Image.asset('assets/images/reconnect_menu.png', - height: 16, width: 16)), - WalletMenuItem( - title: S.current.rescan, - image: Image.asset('assets/images/filter_icon.png', - height: 16, width: 16)), - WalletMenuItem( - title: S.current.wallets, - image: Image.asset('assets/images/wallet_menu.png', - height: 16, width: 16)), - WalletMenuItem( - title: S.current.nodes, - image: - Image.asset('assets/images/nodes_menu.png', height: 16, width: 16)), - WalletMenuItem( - title: S.current.show_keys, - image: - Image.asset('assets/images/key_menu.png', height: 16, width: 16)), - WalletMenuItem( - title: S.current.address_book_menu, - image: Image.asset('assets/images/open_book_menu.png', - height: 16, width: 16)), - WalletMenuItem( - title: S.current.settings_title, - image: Image.asset('assets/images/settings_menu.png', - height: 16, width: 16)), - ]; + WalletMenu(this.context, this.reconnect, this.hasRescan) : items = [] { + items.addAll([ + WalletMenuItem( + title: S.current.reconnect, + image: Image.asset('assets/images/reconnect_menu.png', + height: 16, width: 16)), + if (hasRescan) + WalletMenuItem( + title: S.current.rescan, + image: Image.asset('assets/images/filter_icon.png', + height: 16, width: 16)), + WalletMenuItem( + title: S.current.wallets, + image: Image.asset('assets/images/wallet_menu.png', + height: 16, width: 16)), + WalletMenuItem( + title: S.current.nodes, + image: Image.asset('assets/images/nodes_menu.png', + height: 16, width: 16)), + WalletMenuItem( + title: S.current.show_keys, + image: + Image.asset('assets/images/key_menu.png', height: 16, width: 16)), + WalletMenuItem( + title: S.current.address_book_menu, + image: Image.asset('assets/images/open_book_menu.png', + height: 16, width: 16)), + WalletMenuItem( + title: S.current.settings_title, + image: Image.asset('assets/images/settings_menu.png', + height: 16, width: 16)), + ]); + } + final List items; final BuildContext context; final Future Function() reconnect; + final bool hasRescan; void action(int index) { - switch (index) { + var indx = index; + + if (index > 0 && !hasRescan) { + indx += 1; + } + + switch (indx) { case 0: _presentReconnectAlert(context); break; diff --git a/lib/src/screens/dashboard/widgets/action_button.dart b/lib/src/screens/dashboard/widgets/action_button.dart index 9f9fbc418..96635cbbb 100644 --- a/lib/src/screens/dashboard/widgets/action_button.dart +++ b/lib/src/screens/dashboard/widgets/action_button.dart @@ -39,7 +39,10 @@ class ActionButton extends StatelessWidget { SizedBox(height: 15), Text( title, - style: TextStyle(fontSize: 14, color: Colors.white), + style: TextStyle( + fontSize: 14, + color: Theme.of(context).accentTextTheme.display3 + .backgroundColor), ) ], ), diff --git a/lib/src/screens/dashboard/widgets/address_page.dart b/lib/src/screens/dashboard/widgets/address_page.dart index c50b1b566..c3fb5ae7c 100644 --- a/lib/src/screens/dashboard/widgets/address_page.dart +++ b/lib/src/screens/dashboard/widgets/address_page.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_v import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; class AddressPage extends StatelessWidget { AddressPage({@required this.addressListViewModel}); @@ -16,10 +17,9 @@ class AddressPage extends StatelessWidget { child: Column( children: [ Expanded( - child: Center( - child: QRWidget(addressListViewModel: addressListViewModel), - ) - ), + child: Center( + child: QRWidget(addressListViewModel: addressListViewModel), + )), GestureDetector( onTap: () => Navigator.of(context).pushNamed(Routes.receive), child: Container( @@ -27,29 +27,35 @@ class AddressPage extends StatelessWidget { padding: EdgeInsets.only(left: 24, right: 12), alignment: Alignment.center, decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(25)), - border: Border.all( - color: Theme.of(context).textTheme.subhead.color, - width: 1 - ), - color: Theme.of(context).buttonColor - ), + borderRadius: BorderRadius.all(Radius.circular(25)), + border: Border.all( + color: Theme.of(context).textTheme.subhead.color, + width: 1), + color: Theme.of(context).buttonColor), child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - S.of(context).addresses, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.white - ), - ), + Observer( + builder: (_) => Text( + addressListViewModel.hasAccounts + ? S.of(context).accounts_subaddresses + : S.of(context).addresses, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .accentTextTheme + .display3 + .backgroundColor), + )), Icon( Icons.arrow_forward_ios, size: 14, - color: Colors.white, + color: Theme.of(context) + .accentTextTheme + .display3 + .backgroundColor, ) ], ), @@ -59,4 +65,4 @@ class AddressPage extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/src/screens/dashboard/widgets/balance_page.dart b/lib/src/screens/dashboard/widgets/balance_page.dart index 5470c859d..01fb42495 100644 --- a/lib/src/screens/dashboard/widgets/balance_page.dart +++ b/lib/src/screens/dashboard/widgets/balance_page.dart @@ -10,59 +10,77 @@ class BalancePage extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.all(24), - child: GestureDetector( - onTapUp: (_) => dashboardViewModel.balanceViewModel.isReversing = false, - onTapDown: (_) => dashboardViewModel.balanceViewModel.isReversing = true, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Observer(builder: (_) { - return Text( - dashboardViewModel.balanceViewModel.currency.toString(), - style: TextStyle( - fontSize: 40, - fontWeight: FontWeight.bold, - color: Theme.of(context).indicatorColor, - height: 1), - ); - }), - Observer(builder: (_) { - return Text( - dashboardViewModel.balanceViewModel.displayMode.toString(), - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Theme.of(context).indicatorColor, - height: 1), - ); - }), - SizedBox(height: 10), - Observer(builder: (_) { - return AutoSizeText(dashboardViewModel.balanceViewModel.cryptoBalance, + return GestureDetector( + onTapUp: (_) { + if (dashboardViewModel.balanceViewModel.canReverse) { + dashboardViewModel.balanceViewModel.isReversing = false; + } + }, + onTapDown: (_) { + if (dashboardViewModel.balanceViewModel.canReverse) { + dashboardViewModel.balanceViewModel.isReversing = true; + } + }, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Observer(builder: (_) { + return Text( + dashboardViewModel.balanceViewModel.currency.toString(), + style: TextStyle( + fontSize: 40, + fontWeight: FontWeight.bold, + color: Theme.of(context) + .accentTextTheme + .display2 + .backgroundColor, + height: 1), + ); + }), + Observer(builder: (_) { + return Text( + dashboardViewModel.balanceViewModel.displayMode.toString(), + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Theme.of(context) + .accentTextTheme + .display2 + .backgroundColor, + height: 1), + ); + }), + SizedBox(height: 10), + Observer(builder: (_) { + return AutoSizeText( + dashboardViewModel.balanceViewModel.cryptoBalance, style: TextStyle( fontSize: 54, fontWeight: FontWeight.bold, - color: Colors.white, + color: Theme.of(context) + .accentTextTheme + .display3 + .backgroundColor, height: 1), - maxLines: 1, - textAlign: TextAlign.center); - }), - SizedBox(height: 10), - Observer(builder: (_) { - return Text(dashboardViewModel.balanceViewModel.fiatBalance, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).indicatorColor, - height: 1), - textAlign: TextAlign.center); - }), - ], - ), - ) - ); + maxLines: 1, + textAlign: TextAlign.center); + }), + SizedBox(height: 10), + Observer(builder: (_) { + return Text(dashboardViewModel.balanceViewModel.fiatBalance, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).indicatorColor, + height: 1), + textAlign: TextAlign.center); + }), + ], + ), + )); } } diff --git a/lib/src/screens/dashboard/widgets/header_row.dart b/lib/src/screens/dashboard/widgets/header_row.dart index 5b63f7a0e..2ba4cd8c9 100644 --- a/lib/src/screens/dashboard/widgets/header_row.dart +++ b/lib/src/screens/dashboard/widgets/header_row.dart @@ -27,14 +27,15 @@ class HeaderRow extends StatelessWidget { style: TextStyle( fontSize: 20, fontWeight: FontWeight.w500, - color: Colors.white + color: Theme.of(context).accentTextTheme.display3.backgroundColor ), ), GestureDetector( onTap: () { showPopUp( context: context, - builder: (context) => FilterWidget(dashboardViewModel: dashboardViewModel) + builder: (context) => + FilterWidget(dashboardViewModel: dashboardViewModel) ); }, child: Container( diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index 7701d0ad3..ed90f9be1 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/src/screens/dashboard/wallet_menu.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; // FIXME: terrible design. @@ -65,8 +66,10 @@ class MenuWidgetState extends State { @override Widget build(BuildContext context) { - final walletMenu = - WalletMenu(context, () async => widget.dashboardViewModel.reconnect()); + final walletMenu = WalletMenu( + context, + () async => widget.dashboardViewModel.reconnect(), + widget.dashboardViewModel.hasRescan); final itemCount = walletMenu.items.length; moneroIcon = Image.asset('assets/images/monero_menu.png', @@ -147,16 +150,19 @@ class MenuWidgetState extends State { ), if (widget.dashboardViewModel.subname != null) - Text( - widget.dashboardViewModel.subname, - style: TextStyle( - color: Theme.of(context) - .accentTextTheme - .overline - .decorationColor, - fontWeight: FontWeight.w500, - fontSize: 12), - ) + Observer( + builder: (_) => Text( + widget.dashboardViewModel + .subname, + style: TextStyle( + color: Theme.of(context) + .accentTextTheme + .overline + .decorationColor, + fontWeight: + FontWeight.w500, + fontSize: 12), + )) ], ), )) diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index 82bf2fc3e..449c4d016 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -25,54 +25,53 @@ class TradeRow extends StatelessWidget { return InkWell( onTap: onTap, child: Container( - height: 52, + padding: EdgeInsets.fromLTRB(24, 8, 24, 8), color: Colors.transparent, - padding: EdgeInsets.only(left: 24, right: 24), child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - _getPoweredImage(provider), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 12), - child: Container( - height: 46, - child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _getPoweredImage(provider), + SizedBox(width: 12), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('${from.toString()} → ${to.toString()}', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.white - )), - formattedAmount != null - ? Text(formattedAmount + ' ' + amountCrypto, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.white - )) - : Container() - ]), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(createdAtFormattedDate, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).textTheme - .overline.backgroundColor)) - ]), - ], - ), - ), - )) - ]), + Text('${from.toString()} → ${to.toString()}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme. + display3.backgroundColor + )), + formattedAmount != null + ? Text(formattedAmount + ' ' + amountCrypto, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme. + display3.backgroundColor + )) + : Container() + ]), + SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(createdAtFormattedDate, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).textTheme + .overline.backgroundColor)) + ]) + ], + ) + ) + ], + ), )); } diff --git a/lib/src/screens/dashboard/widgets/transaction_raw.dart b/lib/src/screens/dashboard/widgets/transaction_raw.dart index 67e13ce43..23a3eccec 100644 --- a/lib/src/screens/dashboard/widgets/transaction_raw.dart +++ b/lib/src/screens/dashboard/widgets/transaction_raw.dart @@ -23,73 +23,73 @@ class TransactionRow extends StatelessWidget { return InkWell( onTap: onTap, child: Container( - height: 62, + padding: EdgeInsets.fromLTRB(24, 8, 24, 8), color: Colors.transparent, - padding: EdgeInsets.only(left: 24, right: 24), child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - height: 36, - width: 36, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).textTheme.overline.decorationColor - ), - child: Image.asset( - direction == TransactionDirection.incoming - ? 'assets/images/down_arrow.png' - : 'assets/images/up_arrow.png'), + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 36, + width: 36, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).textTheme.overline.decorationColor ), - Expanded( - child: Container( - padding: const EdgeInsets.only(left: 12), - height: 56, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - (direction == TransactionDirection.incoming - ? S.of(context).received - : S.of(context).sent) + - (isPending ? S.of(context).pending : ''), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.white)), - Text(formattedAmount, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.white)) - ]), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(formattedDate, - style: TextStyle( - fontSize: 14, - color: Theme.of(context) - .textTheme - .overline - .backgroundColor)), - Text(formattedFiatAmount, - style: TextStyle( - fontSize: 14, - color: Theme.of(context) - .textTheme - .overline - .backgroundColor)) - ]), - ], - ), - ), - ) - ]), + child: Image.asset( + direction == TransactionDirection.incoming + ? 'assets/images/down_arrow.png' + : 'assets/images/up_arrow.png'), + ), + SizedBox(width: 12), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + (direction == TransactionDirection.incoming + ? S.of(context).received + : S.of(context).sent) + + (isPending ? S.of(context).pending : ''), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme. + display3.backgroundColor)), + Text(formattedAmount, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme. + display3.backgroundColor)) + ]), + SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(formattedDate, + style: TextStyle( + fontSize: 14, + color: Theme.of(context) + .textTheme + .overline + .backgroundColor)), + Text(formattedFiatAmount, + style: TextStyle( + fontSize: 14, + color: Theme.of(context) + .textTheme + .overline + .backgroundColor)) + ]) + ], + ) + ) + ], + ), )); } } diff --git a/lib/src/screens/dashboard/widgets/transactions_page.dart b/lib/src/screens/dashboard/widgets/transactions_page.dart index 15b2eedaa..c8dd2bfa1 100644 --- a/lib/src/screens/dashboard/widgets/transactions_page.dart +++ b/lib/src/screens/dashboard/widgets/transactions_page.dart @@ -46,7 +46,8 @@ class TransactionsPage extends StatelessWidget { if (item is TransactionListItem) { final transaction = item.transaction; - return TransactionRow( + return Observer( + builder: (_) => TransactionRow( onTap: () => Navigator.of(context).pushNamed( Routes.transactionDetails, arguments: transaction), @@ -55,7 +56,7 @@ class TransactionsPage extends StatelessWidget { .format(transaction.date), formattedAmount: item.formattedCryptoAmount, formattedFiatAmount: item.formattedFiatAmount, - isPending: transaction.isPending); + isPending: transaction.isPending)); } if (item is TradeListItem) { diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 7d90c177c..e085d7880 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -95,9 +95,8 @@ class ExchangePage extends BasePage { return KeyboardActions( config: KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.IOS, - keyboardBarColor: isDarkTheme - ? Color.fromRGBO(48, 51, 60, 1.0) - : Color.fromRGBO(98, 98, 98, 1.0), + keyboardBarColor: Theme.of(context).accentTextTheme.body2 + .backgroundColor, nextFocus: false, actions: [ KeyboardActionsItem( @@ -547,7 +546,7 @@ class ExchangePage extends BasePage { context: context, builder: (BuildContext context) { return AlertWithOneAction( - alertTitle: S.of(context).error, + alertTitle: S.of(context).provider_error(state.title), alertContent: state.error, buttonText: S.of(context).ok, buttonAction: () => Navigator.of(context).pop()); @@ -555,6 +554,7 @@ class ExchangePage extends BasePage { }); } if (state is TradeIsCreatedSuccessfully) { + exchangeViewModel.reset(); Navigator.of(context).pushNamed(Routes.exchangeConfirm); } }); diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index d74f7bcca..45f6f475c 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -166,8 +166,8 @@ class ExchangeCardState extends State { fontSize: 16, fontWeight: FontWeight.w600, color: Theme.of(context) - .textTheme - .subhead + .accentTextTheme + .display4 .decorationColor), validator: _isAmountEditable ? widget.currencyValueValidator @@ -211,8 +211,8 @@ class ExchangeCardState extends State { fontSize: 10, height: 1.2, color: Theme.of(context) - .textTheme - .subhead + .accentTextTheme + .display4 .decorationColor), ) : Offstage(), @@ -224,8 +224,8 @@ class ExchangeCardState extends State { fontSize: 10, height: 1.2, color: Theme.of(context) - .textTheme - .subhead + .accentTextTheme + .display4 .decorationColor)) : Offstage(), ]), @@ -239,7 +239,10 @@ class ExchangeCardState extends State { fontSize: 14, fontWeight: FontWeight.w500, color: - Theme.of(context).textTheme.subhead.decorationColor), + Theme.of(context) + .accentTextTheme + .display4 + .decorationColor), )) : Offstage(), _isAddressEditable @@ -263,7 +266,10 @@ class ExchangeCardState extends State { fontSize: 16, fontWeight: FontWeight.w600, color: - Theme.of(context).textTheme.subhead.decorationColor), + Theme.of(context) + .accentTextTheme + .display4 + .decorationColor), buttonColor: widget.addressButtonsColor, validator: widget.addressTextFieldValidator, ), diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index aced6304a..e43b8d8f1 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -227,16 +227,15 @@ class ExchangeTradeState extends State { final sendingState = widget.exchangeTradeViewModel.sendViewModel.state; - return trade.from == CryptoCurrency.xmr && !(sendingState is TransactionCommitted) + return widget.exchangeTradeViewModel.isSendable && + !(sendingState is TransactionCommitted) ? LoadingPrimaryButton( isDisabled: trade.inputAddress == null || trade.inputAddress.isEmpty, isLoading: sendingState is IsExecutingState, onPressed: () => widget.exchangeTradeViewModel.confirmSending(), - text: trade.provider == ExchangeProviderDescription.xmrto - ? S.of(context).confirm - : S.of(context).send_xmr, + text: S.of(context).confirm, color: Theme.of(context).accentTextTheme.body2.color, textColor: Colors.white) : Offstage(); @@ -306,7 +305,11 @@ class ExchangeTradeState extends State { padding: EdgeInsets.only( top: 220, left: 24, right: 24), child: Text( - S.of(context).send_success, + S.of(context).send_success(widget + .exchangeTradeViewModel + .wallet + .currency + .toString()), textAlign: TextAlign.center, style: TextStyle( fontSize: 22, diff --git a/lib/src/screens/monero_accounts/monero_account_list_page.dart b/lib/src/screens/monero_accounts/monero_account_list_page.dart index 623f3cb90..47404e443 100644 --- a/lib/src/screens/monero_accounts/monero_account_list_page.dart +++ b/lib/src/screens/monero_accounts/monero_account_list_page.dart @@ -103,7 +103,12 @@ class MoneroAccountListPage extends StatelessWidget { accountListViewModel .select(account); Navigator.of(context).pop(); - }); + }, + onEdit: () async => + await Navigator.of(context) + .pushNamed( + Routes.accountCreation, + arguments: account)); }, ), isAlwaysShowScrollThumb diff --git a/lib/src/screens/monero_accounts/widgets/account_tile.dart b/lib/src/screens/monero_accounts/widgets/account_tile.dart index eb9c7f25d..f07539333 100644 --- a/lib/src/screens/monero_accounts/widgets/account_tile.dart +++ b/lib/src/screens/monero_accounts/widgets/account_tile.dart @@ -1,15 +1,19 @@ import 'package:flutter/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:cake_wallet/generated/i18n.dart'; class AccountTile extends StatelessWidget { AccountTile({ @required this.isCurrent, @required this.accountName, - @required this.onTap + @required this.onTap, + @required this.onEdit }); final bool isCurrent; final String accountName; - final VoidCallback onTap; + final Function() onTap; + final Function() onEdit; @override Widget build(BuildContext context) { @@ -20,7 +24,7 @@ class AccountTile extends StatelessWidget { ? Theme.of(context).textTheme.subtitle.color : Theme.of(context).textTheme.display4.color; - return GestureDetector( + final Widget cell = GestureDetector( onTap: onTap, child: Container( height: 77, @@ -39,5 +43,17 @@ class AccountTile extends StatelessWidget { ), ), ); + + return isCurrent ? cell : Slidable( + key: Key(accountName), + child: cell, + actionPane: SlidableDrawerActionPane(), + secondaryActions: [ + IconSlideAction( + caption: S.of(context).edit, + color: Colors.blue, + icon: Icons.edit, + onTap: () => onEdit?.call()) + ]); } } \ No newline at end of file diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index 33a20793e..e6e1a958d 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -1,6 +1,5 @@ -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -22,17 +21,24 @@ class NewWalletPage extends BasePage { final WalletNewVM _walletNewVM; + final walletNameImage = Image.asset('assets/images/wallet_name.png'); + final walletNameLightImage = + Image.asset('assets/images/wallet_name_light.png'); + @override String get title => S.current.new_wallet; @override - Widget body(BuildContext context) => WalletNameForm(_walletNewVM); + Widget body(BuildContext context) => WalletNameForm(_walletNewVM, + currentTheme.type == ThemeType.dark + ? walletNameImage : walletNameLightImage); } class WalletNameForm extends StatefulWidget { - WalletNameForm(this._walletNewVM); + WalletNameForm(this._walletNewVM, this.walletImage); final WalletNewVM _walletNewVM; + final Image walletImage; @override _WalletNameFormState createState() => _WalletNameFormState(_walletNewVM); @@ -43,9 +49,6 @@ class _WalletNameFormState extends State { static const aspectRatioImage = 1.22; - final walletNameImage = Image.asset('assets/images/wallet_name.png'); - final walletNameLightImage = - Image.asset('assets/images/wallet_name_light.png'); final _formKey = GlobalKey(); final _languageSelectorKey = GlobalKey(); ReactionDisposer _stateReaction; @@ -78,10 +81,6 @@ class _WalletNameFormState extends State { @override Widget build(BuildContext context) { - final walletImage = getIt.get().isDarkTheme - ? walletNameImage - : walletNameLightImage; - return Container( padding: EdgeInsets.only(top: 24), child: ScrollableWithBottomSection( @@ -92,7 +91,7 @@ class _WalletNameFormState extends State { padding: EdgeInsets.only(left: 12, right: 12), child: AspectRatio( aspectRatio: aspectRatioImage, - child: FittedBox(child: walletImage, fit: BoxFit.fill)), + child: FittedBox(child: widget.walletImage, fit: BoxFit.fill)), ), Padding( padding: EdgeInsets.only(top: 24), diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index cf3b3b896..76415f9dc 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -1,6 +1,13 @@ +import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cake_wallet/view_model/wallet_new_vm.dart'; +import 'package:flushbar/flushbar.dart'; +import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -10,25 +17,36 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; class NewWalletTypePage extends BasePage { - NewWalletTypePage({this.onTypeSelected, this.isNewWallet = true}); + NewWalletTypePage(this.walletNewVM, {this.onTypeSelected, this.isNewWallet}); final void Function(BuildContext, WalletType) onTypeSelected; final bool isNewWallet; + final WalletNewVM walletNewVM; + + final walletTypeImage = Image.asset('assets/images/wallet_type.png'); + final walletTypeLightImage = + Image.asset('assets/images/wallet_type_light.png'); @override - String get title => isNewWallet - ? S.current.new_wallet - : S.current.wallet_list_restore_wallet; + String get title => + isNewWallet ? S.current.new_wallet : S.current.wallet_list_restore_wallet; @override - Widget body(BuildContext context) => - WalletTypeForm(onTypeSelected: onTypeSelected); + Widget body(BuildContext context) => WalletTypeForm(walletNewVM, isNewWallet, + onTypeSelected: onTypeSelected, + walletImage: currentTheme.type == ThemeType.dark + ? walletTypeImage + : walletTypeLightImage); } class WalletTypeForm extends StatefulWidget { - WalletTypeForm({this.onTypeSelected}); + WalletTypeForm(this.walletNewVM, this.isNewWallet, + {this.onTypeSelected, this.walletImage}); final void Function(BuildContext, WalletType) onTypeSelected; + final WalletNewVM walletNewVM; + final bool isNewWallet; + final Image walletImage; @override WalletTypeFormState createState() => WalletTypeFormState(); @@ -42,10 +60,12 @@ class WalletTypeFormState extends State { final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); final walletTypeImage = Image.asset('assets/images/wallet_type.png'); - final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png'); + final walletTypeLightImage = + Image.asset('assets/images/wallet_type_light.png'); WalletType selected; List types; + Flushbar _progressBar; @override void initState() { @@ -55,11 +75,8 @@ class WalletTypeFormState extends State { @override Widget build(BuildContext context) { - final walletImage = getIt.get().isDarkTheme - ? walletTypeImage : walletTypeLightImage; - return Container( - padding: EdgeInsets.only(top: 24), + padding: EdgeInsets.only(top: 24, bottom: 24), child: ScrollableWithBottomSection( contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), content: Column( @@ -69,7 +86,8 @@ class WalletTypeFormState extends State { padding: EdgeInsets.only(left: 12, right: 12), child: AspectRatio( aspectRatio: aspectRatioImage, - child: FittedBox(child: walletImage, fit: BoxFit.fill)), + child: + FittedBox(child: widget.walletImage, fit: BoxFit.fill)), ), Padding( padding: EdgeInsets.only(top: 48), @@ -86,7 +104,7 @@ class WalletTypeFormState extends State { padding: EdgeInsets.only(top: 24), child: SelectButton( image: _iconFor(type), - text: walletTypeToString(type), + text: walletTypeToDisplayName(type), isSelected: selected == type, onTap: () => setState(() => selected = type)), )) @@ -94,9 +112,9 @@ class WalletTypeFormState extends State { ), bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), bottomSection: PrimaryButton( - onPressed: () => widget.onTypeSelected(context, selected), + onPressed: () => onTypeSelected(), text: S.of(context).seed_language_next, - color: Colors.green, + color: Theme.of(context).accentTextTheme.body2.color, textColor: Colors.white, isDisabled: selected == null, ), @@ -114,4 +132,35 @@ class WalletTypeFormState extends State { return null; } } + + Future onTypeSelected() async { + if (!widget.isNewWallet) { + widget.onTypeSelected(context, selected); + return; + } + + try { + _changeProcessText(S.of(context).creating_new_wallet); + widget.walletNewVM.type = selected; + await widget.walletNewVM + .create(options: 'English'); // FIXME: Unnamed constant + await _progressBar?.dismiss(); + final state = widget.walletNewVM.state; + + if (state is ExecutedSuccessfullyState) { + widget.onTypeSelected(context, selected); + } + + if (state is FailureState) { + _changeProcessText( + S.of(context).creating_new_wallet_error(state.error)); + } + } catch (e) { + _changeProcessText(S.of(context).creating_new_wallet_error(e.toString())); + } + } + + void _changeProcessText(String text) { + _progressBar = createBar(text, duration: null)..show(context); + } } diff --git a/lib/src/screens/new_wallet/widgets/select_button.dart b/lib/src/screens/new_wallet/widgets/select_button.dart index bd69a8015..42b99e40e 100644 --- a/lib/src/screens/new_wallet/widgets/select_button.dart +++ b/lib/src/screens/new_wallet/widgets/select_button.dart @@ -16,7 +16,7 @@ class SelectButton extends StatelessWidget { @override Widget build(BuildContext context) { final color = isSelected - ? Theme.of(context).accentTextTheme.subtitle.decorationColor + ? Colors.green : Theme.of(context).accentTextTheme.caption.color; final textColor = isSelected ? Theme.of(context).accentTextTheme.headline.decorationColor diff --git a/lib/src/screens/nodes/node_create_or_edit_page.dart b/lib/src/screens/nodes/node_create_or_edit_page.dart index ee13984c5..6cfa67285 100644 --- a/lib/src/screens/nodes/node_create_or_edit_page.dart +++ b/lib/src/screens/nodes/node_create_or_edit_page.dart @@ -1,3 +1,7 @@ +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -67,6 +71,39 @@ class NodeCreateOrEditPage extends BasePage { @override Widget body(BuildContext context) { + + reaction((_) => nodeCreateOrEditViewModel.connectionState, + (ExecutionState state) { + if (state is ExecutedSuccessfullyState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext context) => + AlertWithOneAction( + alertTitle: S.of(context).new_node_testing, + alertContent: state.payload as bool + ? S.of(context).node_connection_successful + : S.of(context).node_connection_failed, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop())); + }); + } + + if (state is FailureState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: state.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + }); + } + }); + return Container( padding: EdgeInsets.only(left: 24, right: 24), child: ScrollableWithBottomSection( @@ -122,7 +159,22 @@ class NodeCreateOrEditPage extends BasePage { ) ) ], - ) + ), + Padding( + padding: EdgeInsets.only(top: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Observer( + builder: (_) => StandardCheckbox( + value: nodeCreateOrEditViewModel.useSSL, + onChanged: (value) => + nodeCreateOrEditViewModel.useSSL = value, + caption: S.of(context).use_ssl, + )) + ], + )) ] ], )), @@ -133,9 +185,18 @@ class NodeCreateOrEditPage extends BasePage { Flexible( child: Container( padding: EdgeInsets.only(right: 8.0), - child: PrimaryButton( - onPressed: () => nodeCreateOrEditViewModel.reset(), - text: S.of(context).reset, + child: LoadingPrimaryButton( + onPressed: () async { + if (!_formKey.currentState.validate()) { + return; + } + + await nodeCreateOrEditViewModel.connect(); + }, + isLoading: nodeCreateOrEditViewModel + .connectionState is IsExecutingState, + text: S.of(context).node_test, + isDisabled: !nodeCreateOrEditViewModel.isReady, color: Colors.orange, textColor: Colors.white), )), @@ -154,7 +215,9 @@ class NodeCreateOrEditPage extends BasePage { text: S.of(context).save, color: Theme.of(context).accentTextTheme.body2.color, textColor: Colors.white, - isDisabled: !nodeCreateOrEditViewModel.isReady, + isDisabled: (!nodeCreateOrEditViewModel.isReady)|| + (nodeCreateOrEditViewModel + .connectionState is IsExecutingState), ), )), ], diff --git a/lib/src/screens/nodes/nodes_list_page.dart b/lib/src/screens/nodes/nodes_list_page.dart index 20abdae32..87753e1ce 100644 --- a/lib/src/screens/nodes/nodes_list_page.dart +++ b/lib/src/screens/nodes/nodes_list_page.dart @@ -65,98 +65,90 @@ class NodeListPage extends BasePage { padding: EdgeInsets.only(top: 10), child: Observer( builder: (BuildContext context) { - return nodeListViewModel.nodes.isNotEmpty - ? SectionStandardList( - sectionCount: 2, - context: context, - itemCounter: (int sectionIndex) { - if (sectionIndex == 0) { - return 1; - } + return SectionStandardList( + sectionCount: 2, + context: context, + itemCounter: (int sectionIndex) { + if (sectionIndex == 0) { + return 1; + } - return nodeListViewModel.nodes.length; - }, - itemBuilder: (_, sectionIndex, index) { - if (sectionIndex == 0) { - return NodeHeaderListRow( - title: S.of(context).add_new_node, - onTap: (_) async => await Navigator.of(context) - .pushNamed(Routes.newNode)); - } + return nodeListViewModel.nodes.length; + }, + itemBuilder: (_, sectionIndex, index) { + if (sectionIndex == 0) { + return NodeHeaderListRow( + title: S.of(context).add_new_node, + onTap: (_) async => await Navigator.of(context) + .pushNamed(Routes.newNode)); + } - final node = nodeListViewModel.nodes[index]; - final isSelected = node.keyIndex == - nodeListViewModel.settingsStore.currentNode.keyIndex; - final nodeListRow = NodeListRow( - title: node.uri, - isSelected: isSelected, - isAlive: node.requestNode(), - onTap: (_) async { - if (isSelected) { - return; + final node = nodeListViewModel.nodes[index]; + final isSelected = + node.keyIndex == nodeListViewModel.currentNode?.keyIndex; + final nodeListRow = NodeListRow( + title: node.uri, + isSelected: isSelected, + isAlive: node.requestNode(), + onTap: (_) async { + if (isSelected) { + return; + } + + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: + S.of(context).change_current_node_title, + alertContent: + S.of(context).change_current_node(node.uri), + leftButtonText: S.of(context).cancel, + rightButtonText: S.of(context).change, + actionLeftButton: () => + Navigator.of(context).pop(), + actionRightButton: () async { + await nodeListViewModel.setAsCurrent(node); + Navigator.of(context).pop(); + }); + }); + }); + + final dismissibleRow = Slidable( + key: Key('${node.keyIndex}'), + actionPane: SlidableDrawerActionPane(), + child: nodeListRow, + secondaryActions: [ + IconSlideAction( + caption: S.of(context).delete, + color: Colors.red, + icon: CupertinoIcons.delete, + onTap: () async { + final confirmed = await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).remove_node, + alertContent: + S.of(context).remove_node_message, + rightButtonText: S.of(context).remove, + leftButtonText: S.of(context).cancel, + actionRightButton: () => + Navigator.pop(context, true), + actionLeftButton: () => + Navigator.pop(context, false)); + }) ?? + false; + + if (confirmed) { + await nodeListViewModel.delete(node); } + }, + ), + ]); - await showPopUp( - context: context, - builder: (BuildContext context) { - // FIXME: Add translation. - return AlertWithTwoActions( - alertTitle: 'Change current node', - alertContent: S - .of(context) - .change_current_node(node.uri), - leftButtonText: S.of(context).cancel, - rightButtonText: S.of(context).change, - actionLeftButton: () => - Navigator.of(context).pop(), - actionRightButton: () async { - await nodeListViewModel - .setAsCurrent(node); - Navigator.of(context).pop(); - }); - }); - }); - - final dismissibleRow = Slidable( - key: Key('${node.keyIndex}'), - actionPane: SlidableDrawerActionPane(), - child: nodeListRow, - secondaryActions: [ - IconSlideAction( - caption: S.of(context).delete, - color: Colors.red, - icon: CupertinoIcons.delete, - onTap: () async { - final confirmed = await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: - S.of(context).remove_node, - alertContent: S - .of(context) - .remove_node_message, - rightButtonText: - S.of(context).remove, - leftButtonText: - S.of(context).cancel, - actionRightButton: () => - Navigator.pop(context, true), - actionLeftButton: () => - Navigator.pop(context, false)); - }) ?? - false; - - if (confirmed) { - await nodeListViewModel.delete(node); - } - }, - ), - ]); - - return isSelected ? nodeListRow : dismissibleRow; - }) - : Container(); + return isSelected ? nodeListRow : dismissibleRow; + }); }, ), ); diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 30d2b7cea..c441bd380 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; @@ -27,16 +28,47 @@ class ReceivePage extends BasePage { String get title => S.current.receive; @override - Color get backgroundLightColor => Colors.transparent; + Color get backgroundLightColor => currentTheme.type == ThemeType.bright + ? Colors.transparent : Colors.white; @override Color get backgroundDarkColor => Colors.transparent; - @override - Color get titleColor => Colors.white; - final FocusNode _cryptoAmountFocus; + @override + Widget leading(BuildContext context) { + final _backButton = Icon(Icons.arrow_back_ios, + color: Theme.of(context).accentTextTheme.display3.backgroundColor, + size: 16,); + + return SizedBox( + height: 37, + width: 37, + child: ButtonTheme( + minWidth: double.minPositive, + child: FlatButton( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + padding: EdgeInsets.all(0), + onPressed: () => onClose(context), + child: _backButton), + ), + ); + } + + @override + Widget middle(BuildContext context) { + return Text( + title, + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + fontFamily: 'Lato', + color: Theme.of(context).accentTextTheme.display3.backgroundColor), + ); + } + @override Widget Function(BuildContext, Widget) get rootWrapper => (BuildContext context, Widget scaffold) => Container( @@ -51,7 +83,8 @@ class ReceivePage extends BasePage { @override Widget trailing(BuildContext context) { final shareImage = - Image.asset('assets/images/share.png', color: Colors.white); + Image.asset('assets/images/share.png', + color: Theme.of(context).accentTextTheme.display3.backgroundColor); return SizedBox( height: 20.0, @@ -74,9 +107,8 @@ class ReceivePage extends BasePage { return KeyboardActions( config: KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.IOS, - keyboardBarColor: isDarkTheme - ? Color.fromRGBO(48, 51, 60, 1.0) - : Color.fromRGBO(98, 98, 98, 1.0), + keyboardBarColor: Theme.of(context).accentTextTheme.body2 + .backgroundColor, nextFocus: false, actions: [ KeyboardActionsItem( @@ -117,7 +149,7 @@ class ReceivePage extends BasePage { Icons.arrow_forward_ios, size: 14, color: - Theme.of(context).textTheme.display1.color, + Theme.of(context).textTheme.display1.color, )); } @@ -130,7 +162,7 @@ class ReceivePage extends BasePage { Icons.add, size: 20, color: - Theme.of(context).textTheme.display1.color, + Theme.of(context).textTheme.display1.color, )); } @@ -140,13 +172,13 @@ class ReceivePage extends BasePage { addressListViewModel.address.address; final backgroundColor = isCurrent ? Theme.of(context) - .textTheme - .display3 - .decorationColor + .textTheme + .display3 + .decorationColor : Theme.of(context) - .textTheme - .display2 - .decorationColor; + .textTheme + .display2 + .decorationColor; final textColor = isCurrent ? Theme.of(context).textTheme.display3.color : Theme.of(context).textTheme.display2.color; @@ -155,8 +187,7 @@ class ReceivePage extends BasePage { isCurrent: isCurrent, backgroundColor: backgroundColor, textColor: textColor, - onTap: (_) => - addressListViewModel.setAddress(item), + onTap: (_) => addressListViewModel.setAddress(item), onEdit: () => Navigator.of(context).pushNamed( Routes.newSubaddress, arguments: item)); @@ -166,11 +197,11 @@ class ReceivePage extends BasePage { return index != 0 ? cell : ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30)), - child: cell, - ); + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30)), + child: cell, + ); })), ], ), diff --git a/lib/src/screens/receive/widgets/address_cell.dart b/lib/src/screens/receive/widgets/address_cell.dart index 6306ae209..703067dcf 100644 --- a/lib/src/screens/receive/widgets/address_cell.dart +++ b/lib/src/screens/receive/widgets/address_cell.dart @@ -14,6 +14,7 @@ class AddressCell extends StatelessWidget { address: item.address, name: item.name, isCurrent: isCurrent, + isPrimary: item.isPrimary, backgroundColor: backgroundColor, textColor: textColor, onTap: onTap, @@ -23,6 +24,7 @@ class AddressCell extends StatelessWidget { {@required this.address, @required this.name, @required this.isCurrent, + @required this.isPrimary, @required this.backgroundColor, @required this.textColor, this.onTap, @@ -31,6 +33,7 @@ class AddressCell extends StatelessWidget { final String address; final String name; final bool isCurrent; + final bool isPrimary; final Color backgroundColor; final Color textColor; final Function(String) onTap; @@ -56,7 +59,7 @@ class AddressCell extends StatelessWidget { ), )); - return isCurrent + return (isCurrent || isPrimary) ? cell : Slidable( key: Key(address), diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index ba2101d93..812255fa4 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -47,24 +47,14 @@ class QRWidget extends StatelessWidget { child: QrImage( data: addressListViewModel.uri.toString(), backgroundColor: Colors.transparent, - foregroundColor: Colors.white, - //Theme.of(context).textTheme.headline.color, + foregroundColor: Theme.of(context).accentTextTheme. + display3.backgroundColor, ))))), Spacer(flex: 3) ]), - Padding( - padding: EdgeInsets.only(top: 20), - child: Text( - S.of(context).scan_qr_code, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Theme.of(context).indicatorColor), - ), - ), isAmountFieldShow ? Padding( - padding: EdgeInsets.only(top: 40), + padding: EdgeInsets.only(top: 60), child: Row( children: [ Expanded( @@ -77,11 +67,12 @@ class QRWidget extends StatelessWidget { decimal: true), inputFormatters: [ BlacklistingTextInputFormatter( - RegExp('[\\-|\\ |\\,]')) + RegExp('[\\-|\\ ]')) ], textAlign: TextAlign.center, hintText: S.of(context).receive_amount, - textColor: Colors.white, + textColor: Theme.of(context).accentTextTheme. + display3.backgroundColor, borderColor: Theme.of(context) .textTheme .headline @@ -120,7 +111,8 @@ class QRWidget extends StatelessWidget { style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, - color: Colors.white), + color: Theme.of(context).accentTextTheme. + display3.backgroundColor), ), ), Padding( diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index 6044531d9..e94f63bf0 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -7,12 +9,20 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; class WalletRestoreFromSeedForm extends StatefulWidget { - WalletRestoreFromSeedForm({Key key, this.blockHeightFocusNode, - this.onHeightOrDateEntered}) + WalletRestoreFromSeedForm( + {Key key, + @required this.displayLanguageSelector, + @required this.displayBlockHeightSelector, + @required this.type, + this.blockHeightFocusNode, + this.onHeightOrDateEntered}) : super(key: key); + final WalletType type; + final bool displayLanguageSelector; + final bool displayBlockHeightSelector; final FocusNode blockHeightFocusNode; - final Function (bool) onHeightOrDateEntered; + final Function(bool) onHeightOrDateEntered; @override WalletRestoreFromSeedFormState createState() => @@ -41,32 +51,35 @@ class WalletRestoreFromSeedFormState extends State { return Container( padding: EdgeInsets.only(left: 25, right: 25), child: Column(children: [ - SeedWidget(key: seedWidgetStateKey, language: language), - GestureDetector( - onTap: () async { - final selected = await showPopUp( - context: context, - builder: (BuildContext context) => - SeedLanguagePicker(selected: language)); + SeedWidget( + key: seedWidgetStateKey, language: language, type: widget.type), + if (widget.displayLanguageSelector) + GestureDetector( + onTap: () async { + final selected = await showPopUp( + context: context, + builder: (BuildContext context) => + SeedLanguagePicker(selected: language)); - if (selected == null || selected.isEmpty) { - return; - } + if (selected == null || selected.isEmpty) { + return; + } - _changeLanguage(selected); - }, - child: Container( - color: Colors.transparent, - padding: EdgeInsets.only(top: 20.0), - child: IgnorePointer( - child: BaseTextFormField( - controller: languageController, - enableInteractiveSelection: false, - readOnly: true)))), - BlockchainHeightWidget( - focusNode: widget.blockHeightFocusNode, - key: blockchainHeightKey, - onHeightOrDateEntered: widget.onHeightOrDateEntered) + _changeLanguage(selected); + }, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.only(top: 20.0), + child: IgnorePointer( + child: BaseTextFormField( + controller: languageController, + enableInteractiveSelection: false, + readOnly: true)))), + if (widget.displayBlockHeightSelector) + BlockchainHeightWidget( + focusNode: widget.blockHeightFocusNode, + key: blockchainHeightKey, + onHeightOrDateEntered: widget.onHeightOrDateEntered) ])); } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 1a00639b6..adae48c89 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -25,16 +25,30 @@ class WalletRestorePage extends BasePage { _pages = [], _blockHeightFocusNode = FocusNode(), _controller = PageController(initialPage: 0) { - _pages.addAll([ - WalletRestoreFromSeedForm( - key: walletRestoreFromSeedFormKey, - blockHeightFocusNode: _blockHeightFocusNode, - onHeightOrDateEntered: (value) - => walletRestoreViewModel.isButtonEnabled = value), - WalletRestoreFromKeysFrom(key: walletRestoreFromKeysFormKey, - onHeightOrDateEntered: (value) - => walletRestoreViewModel.isButtonEnabled = value) - ]); + walletRestoreViewModel.availableModes.forEach((mode) { + switch (mode) { + case WalletRestoreMode.seed: + _pages.add(WalletRestoreFromSeedForm( + displayBlockHeightSelector: + walletRestoreViewModel.hasBlockchainHeightLanguageSelector, + displayLanguageSelector: + walletRestoreViewModel.hasSeedLanguageSelector, + type: walletRestoreViewModel.type, + key: walletRestoreFromSeedFormKey, + blockHeightFocusNode: _blockHeightFocusNode, + onHeightOrDateEntered: (value) => + walletRestoreViewModel.isButtonEnabled = value)); + break; + case WalletRestoreMode.keys: + _pages.add(WalletRestoreFromKeysFrom( + key: walletRestoreFromKeysFormKey, + onHeightOrDateEntered: (value) => + walletRestoreViewModel.isButtonEnabled = value)); + break; + default: + break; + } + }); } @override @@ -76,20 +90,19 @@ class WalletRestorePage extends BasePage { } }); - reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode) - { - walletRestoreViewModel.isButtonEnabled = false; + reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode) { + walletRestoreViewModel.isButtonEnabled = false; - walletRestoreFromSeedFormKey.currentState.blockchainHeightKey - .currentState.restoreHeightController.text = ''; - walletRestoreFromSeedFormKey.currentState.blockchainHeightKey - .currentState.dateController.text = ''; + walletRestoreFromSeedFormKey.currentState.blockchainHeightKey.currentState + .restoreHeightController.text = ''; + walletRestoreFromSeedFormKey.currentState.blockchainHeightKey.currentState + .dateController.text = ''; - walletRestoreFromKeysFormKey.currentState.blockchainHeightKey - .currentState.restoreHeightController.text = ''; - walletRestoreFromKeysFormKey.currentState.blockchainHeightKey - .currentState.dateController.text = ''; - }); + walletRestoreFromKeysFormKey.currentState.blockchainHeightKey.currentState + .restoreHeightController.text = ''; + walletRestoreFromKeysFormKey.currentState.blockchainHeightKey.currentState + .dateController.text = ''; + }); return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( @@ -100,40 +113,37 @@ class WalletRestorePage extends BasePage { }, controller: _controller, itemCount: _pages.length, - itemBuilder: (_, index) => SingleChildScrollView(child: _pages[index]))), - Padding( - padding: EdgeInsets.only(top: 10), - child: SmoothPageIndicator( - controller: _controller, - count: _pages.length, - effect: ColorTransitionEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context).hintColor.withOpacity(0.5), - activeDotColor: Theme.of(context).hintColor), - )), + itemBuilder: (_, index) => + SingleChildScrollView(child: _pages[index]))), + if (_pages.length > 1) + Padding( + padding: EdgeInsets.only(top: 10), + child: SmoothPageIndicator( + controller: _controller, + count: _pages.length, + effect: ColorTransitionEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context).hintColor.withOpacity(0.5), + activeDotColor: Theme.of(context).hintColor), + )), Padding( padding: EdgeInsets.only(top: 20, bottom: 40, left: 25, right: 25), child: Observer( builder: (context) { return LoadingPrimaryButton( - onPressed: () => - walletRestoreViewModel.create(options: _credentials()), - text: S.of(context).restore_recover, - color: Theme - .of(context) - .accentTextTheme - .subtitle - .decorationColor, - textColor: Theme - .of(context) - .accentTextTheme - .headline - .decorationColor, - isLoading: walletRestoreViewModel.state is IsExecutingState, - isDisabled: !walletRestoreViewModel.isButtonEnabled,); + onPressed: () => + walletRestoreViewModel.create(options: _credentials()), + text: S.of(context).restore_recover, + color: + Theme.of(context).accentTextTheme.subtitle.decorationColor, + textColor: + Theme.of(context).accentTextTheme.headline.decorationColor, + isLoading: walletRestoreViewModel.state is IsExecutingState, + isDisabled: !walletRestoreViewModel.isButtonEnabled, + ); }, )) ]); @@ -145,8 +155,11 @@ class WalletRestorePage extends BasePage { if (walletRestoreViewModel.mode == WalletRestoreMode.seed) { credentials['seed'] = walletRestoreFromSeedFormKey .currentState.seedWidgetStateKey.currentState.text; - credentials['height'] = walletRestoreFromSeedFormKey - .currentState.blockchainHeightKey.currentState.height; + + if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { + credentials['height'] = walletRestoreFromSeedFormKey + .currentState.blockchainHeightKey.currentState.height; + } } else { credentials['address'] = walletRestoreFromKeysFormKey.currentState.addressController.text; diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart new file mode 100644 index 000000000..babbf33ec --- /dev/null +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -0,0 +1,75 @@ +import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class PreSeedPage extends BasePage { + PreSeedPage(this.type) + : imageLight = Image.asset('assets/images/pre_seed_light.png'), + imageDark = Image.asset('assets/images/pre_seed_dark.png'), + wordsCount = type == WalletType.monero + ? 25 + : 12; // FIXME: Stupid fast implementation + + final Image imageDark; + final Image imageLight; + final WalletType type; + final int wordsCount; + + @override + Widget leading(BuildContext context) => null; + + @override + String get title => S.current.pre_seed_title; + + @override + Widget body(BuildContext context) { + final image = currentTheme.type == ThemeType.dark ? imageDark : imageLight; + + return WillPopScope( + onWillPop: () async => false, + child: Container( + padding: EdgeInsets.all(24), + child: Column( + children: [ + Flexible( + flex: 2, + child: AspectRatio( + aspectRatio: 1, + child: FittedBox(child: image, fit: BoxFit.contain))), + Flexible( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsets.only(top: 70, left: 16, right: 16), + child: Text( + S.of(context).pre_seed_description(wordsCount), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context) + .primaryTextTheme + .caption + .color), + ), + ), + PrimaryButton( + onPressed: () => Navigator.of(context) + .popAndPushNamed(Routes.seed, arguments: true), + text: S.of(context).pre_seed_button_text, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white) + ], + )) + ], + ), + )); + } +} diff --git a/lib/src/screens/seed/wallet_seed_page.dart b/lib/src/screens/seed/wallet_seed_page.dart index 72c80d503..5bdd07be6 100644 --- a/lib/src/screens/seed/wallet_seed_page.dart +++ b/lib/src/screens/seed/wallet_seed_page.dart @@ -1,7 +1,6 @@ -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; -import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/cupertino.dart'; @@ -17,8 +16,8 @@ import 'package:cake_wallet/view_model/wallet_seed_view_model.dart'; class WalletSeedPage extends BasePage { WalletSeedPage(this.walletSeedViewModel, {@required this.isNewWalletCreated}); - static final imageLight = Image.asset('assets/images/crypto_lock_light.png'); - static final imageDark = Image.asset('assets/images/crypto_lock.png'); + final imageLight = Image.asset('assets/images/crypto_lock_light.png'); + final imageDark = Image.asset('assets/images/crypto_lock.png'); @override String get title => S.current.seed_title; @@ -83,10 +82,9 @@ class WalletSeedPage extends BasePage { @override Widget body(BuildContext context) { - final image = - getIt.get().isDarkTheme ? imageDark : imageLight; + final image = currentTheme.type == ThemeType.dark ? imageDark : imageLight; - return Container( + return WillPopScope(onWillPop: () async => false, child: Container( padding: EdgeInsets.all(24), child: Column( children: [ @@ -194,6 +192,6 @@ class WalletSeedPage extends BasePage { ], )) ], - )); + ))); } } diff --git a/lib/src/screens/seed_language/seed_language_page.dart b/lib/src/screens/seed_language/seed_language_page.dart index 701b5dccc..541c9b8c1 100644 --- a/lib/src/screens/seed_language/seed_language_page.dart +++ b/lib/src/screens/seed_language/seed_language_page.dart @@ -1,6 +1,5 @@ -import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/src/widgets/seed_language_selector.dart'; -import 'package:cake_wallet/store/settings_store.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; @@ -15,17 +14,26 @@ class SeedLanguage extends BasePage { final Function(BuildContext, String) onConfirm; + final walletNameImage = Image.asset('assets/images/wallet_name.png'); + final walletNameLightImage = + Image.asset('assets/images/wallet_name_light.png'); + @override String get title => S.current.wallet_list_restore_wallet; @override - Widget body(BuildContext context) => SeedLanguageForm(onConfirm: onConfirm); + Widget body(BuildContext context) => + SeedLanguageForm( + onConfirm: onConfirm, + walletImage: currentTheme.type == ThemeType.dark + ? walletNameImage : walletNameLightImage); } class SeedLanguageForm extends StatefulWidget { - SeedLanguageForm({this.onConfirm}); + SeedLanguageForm({this.onConfirm, this.walletImage}); final Function(BuildContext, String) onConfirm; + final Image walletImage; @override SeedLanguageFormState createState() => SeedLanguageFormState(); @@ -33,18 +41,10 @@ class SeedLanguageForm extends StatefulWidget { class SeedLanguageFormState extends State { static const aspectRatioImage = 1.22; - - final walletNameImage = Image.asset('assets/images/wallet_name.png'); - final walletNameLightImage = - Image.asset('assets/images/wallet_name_light.png'); final _languageSelectorKey = GlobalKey(); @override Widget build(BuildContext context) { - final walletImage = getIt.get().isDarkTheme - ? walletNameImage - : walletNameLightImage; - return Container( padding: EdgeInsets.only(top: 24), child: ScrollableWithBottomSection( @@ -55,7 +55,8 @@ class SeedLanguageFormState extends State { padding: EdgeInsets.only(left: 12, right: 12), child: AspectRatio( aspectRatio: aspectRatioImage, - child: FittedBox(child: walletImage, fit: BoxFit.fill)), + child: FittedBox(child: widget.walletImage, + fit: BoxFit.fill)), ), Padding( padding: EdgeInsets.only(top: 40), diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index cd4c5b06d..1a18b5ba6 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -83,9 +83,8 @@ class SendPage extends BasePage { return KeyboardActions( config: KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.IOS, - keyboardBarColor: isDarkTheme - ? Color.fromRGBO(48, 51, 60, 1.0) - : Color.fromRGBO(98, 98, 98, 1.0), + keyboardBarColor: Theme.of(context).accentTextTheme.body2 + .backgroundColor, nextFocus: false, actions: [ KeyboardActionsItem( @@ -133,7 +132,8 @@ class SendPage extends BasePage { if (uri != null) { address = uri.path; - amount = uri.queryParameters['tx_amount']; + amount = uri.queryParameters['tx_amount'] ?? + uri.queryParameters['amount']; } else { address = uri.toString(); } @@ -497,14 +497,8 @@ class SendPage extends BasePage { } }, text: S.of(context).send, - color: Theme.of(context) - .accentTextTheme - .subtitle - .decorationColor, - textColor: Theme.of(context) - .accentTextTheme - .headline - .decorationColor, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, isLoading: sendViewModel.state is IsExecutingState || sendViewModel.state is TransactionCommitting, isDisabled: @@ -634,7 +628,9 @@ class SendPage extends BasePage { padding: EdgeInsets.only( top: 220, left: 24, right: 24), child: Text( - S.of(context).send_success, + S.of(context).send_success( + sendViewModel.currency + .toString()), textAlign: TextAlign.center, style: TextStyle( fontSize: 22, @@ -748,7 +744,7 @@ class SendPage extends BasePage { } Future _setTransactionPriority(BuildContext context) async { - final items = TransactionPriority.all; + final items = TransactionPriority.forWalletType(sendViewModel.walletType); final selectedItem = items.indexOf(sendViewModel.transactionPriority); await showPopUp( diff --git a/lib/src/screens/send/send_template_page.dart b/lib/src/screens/send/send_template_page.dart index b53117309..6d3ac7132 100644 --- a/lib/src/screens/send/send_template_page.dart +++ b/lib/src/screens/send/send_template_page.dart @@ -49,9 +49,8 @@ class SendTemplatePage extends BasePage { return KeyboardActions( config: KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.IOS, - keyboardBarColor: isDarkTheme - ? Color.fromRGBO(48, 51, 60, 1.0) - : Color.fromRGBO(98, 98, 98, 1.0), + keyboardBarColor: Theme.of(context).accentTextTheme.body2 + .backgroundColor, nextFocus: false, actions: [ KeyboardActionsItem( @@ -113,7 +112,8 @@ class SendTemplatePage extends BasePage { if (uri != null) { address = uri.path; - amount = uri.queryParameters['tx_amount']; + amount = uri.queryParameters['tx_amount'] ?? + uri.queryParameters['amount']; } else { address = uri.toString(); } @@ -246,9 +246,8 @@ class SendTemplatePage extends BasePage { } }, text: S.of(context).save, - color: Theme.of(context).accentTextTheme.subtitle.decorationColor, - textColor: - Theme.of(context).accentTextTheme.headline.decorationColor, + color: Colors.green, + textColor: Colors.white, ), ), )); diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart index 12798ed54..f1ca297f2 100644 --- a/lib/src/screens/transaction_details/transaction_details_page.dart +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -49,6 +49,7 @@ class TransactionDetailsPage extends BasePage { value: tx.amountFormatted()), StandartListItem(title: S.current.send_fee, value: tx.feeFormatted()) ]; + if (showRecipientAddress) { final recipientAddress = transactionDescriptionBox.values .firstWhere((val) => val.id == transactionInfo.id, @@ -61,6 +62,12 @@ class TransactionDetailsPage extends BasePage { value: recipientAddress)); } } + + if (tx.key?.isNotEmpty ?? null) { + // FIXME: add translation + items.add(StandartListItem(title: 'Transaction Key', value: tx.key)); + } + _items.addAll(items); _items.add(StandartListItem( title: "View in Block Explorer", @@ -81,9 +88,8 @@ class TransactionDetailsPage extends BasePage { StandartListItem( title: S.current.transaction_details_amount, value: tx.amountFormatted()), - StandartListItem( - title: "View in Block Explorer", - value: "https://www.blockchain.com/btc/tx/${tx.id}"), + if (tx.feeFormatted()?.isNotEmpty) + StandartListItem(title: S.current.send_fee, value: tx.feeFormatted()) ]; _items.addAll(items); @@ -91,6 +97,18 @@ class TransactionDetailsPage extends BasePage { title: "View in Block Explorer", value: "https://www.blockchain.com/btc/tx/${tx.id}")); } + + if (showRecipientAddress) { + final recipientAddress = transactionDescriptionBox.values + .firstWhere((val) => val.id == transactionInfo.id, orElse: () => null) + ?.recipientAddress; + + if (recipientAddress?.isNotEmpty ?? false) { + _items.add(StandartListItem( + title: S.current.transaction_details_recipient_address, + value: recipientAddress)); + } + } } @override diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 5162b1c5b..de0addd1d 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -1,5 +1,4 @@ import 'package:cake_wallet/src/screens/auth/auth_page.dart'; -import 'package:cake_wallet/src/screens/wallet_list/widgets/wallet_menu_alert.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -15,7 +14,6 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; -import 'package:cake_wallet/src/screens/wallet_list/wallet_menu.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; class WalletListPage extends BasePage { @@ -51,7 +49,7 @@ class WalletListBodyState extends State { final newWalletImage = Image.asset('assets/images/new_wallet.png', height: 12, width: 12, - color: Theme.of(context).accentTextTheme.headline.decorationColor); + color: Colors.white); final restoreWalletImage = Image.asset('assets/images/restore_wallet.png', height: 12, width: 12, @@ -165,17 +163,16 @@ class WalletListBodyState extends State { ), bottomSection: Column(children: [ PrimaryImageButton( - onPressed: () => _generateNewWallet(), + onPressed: () => Navigator.of(context).pushNamed(Routes.newWalletType), image: newWalletImage, text: S.of(context).wallet_list_create_new_wallet, - color: Theme.of(context).accentTextTheme.subtitle.decorationColor, - textColor: - Theme.of(context).accentTextTheme.headline.decorationColor, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, ), SizedBox(height: 10.0), PrimaryImageButton( onPressed: () => - Navigator.of(context).pushNamed(Routes.restoreWallet), + Navigator.of(context).pushNamed(Routes.restoreWalletType), image: restoreWalletImage, text: S.of(context).wallet_list_restore_wallet, color: Theme.of(context).accentTextTheme.caption.color, @@ -243,7 +240,7 @@ class WalletListBodyState extends State { changeProcessText(S.of(context).creating_new_wallet); await widget.walletListViewModel.walletNewVM.create(options: 'English'); // FIXME: Unnamed constant hideProgressText(); - await Navigator.of(context).pushNamed(Routes.seed, arguments: true); + await Navigator.of(context).pushNamed(Routes.preSeed); } catch(e) { changeProcessText(S.of(context).creating_new_wallet_error(e.toString())); } diff --git a/lib/src/screens/welcome/welcome_page.dart b/lib/src/screens/welcome/welcome_page.dart index 7a5b65516..35934f64f 100644 --- a/lib/src/screens/welcome/welcome_page.dart +++ b/lib/src/screens/welcome/welcome_page.dart @@ -1,5 +1,4 @@ -import 'package:cake_wallet/di.dart'; -import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; @@ -23,9 +22,7 @@ class WelcomePage extends BasePage { @override Widget body(BuildContext context) { - final welcomeImage = getIt - .get() - .isDarkTheme + final welcomeImage = currentTheme.type == ThemeType.dark ? welcomeImageDark : welcomeImageLight; final newWalletImage = Image.asset('assets/images/new_wallet.png', diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index 6074319e7..8c18d84a1 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -204,7 +204,7 @@ class AddressTextField extends StatelessWidget { onURIScanned(uri); } } catch (e) { - print('Error $e'); + print(e.toString()); } } diff --git a/lib/src/widgets/alert_with_one_action.dart b/lib/src/widgets/alert_with_one_action.dart index 5a39adbc5..5aedbe9da 100644 --- a/lib/src/widgets/alert_with_one_action.dart +++ b/lib/src/widgets/alert_with_one_action.dart @@ -31,7 +31,7 @@ class AlertWithOneAction extends BaseAlertDialog { width: 300, height: 52, padding: EdgeInsets.only(left: 12, right: 12), - color: Theme.of(context).accentTextTheme.body2.color, + color: Theme.of(context).accentTextTheme.body1.backgroundColor, child: ButtonTheme( minWidth: double.infinity, child: FlatButton( @@ -44,7 +44,8 @@ class AlertWithOneAction extends BaseAlertDialog { style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: Colors.white, + color: Theme.of(context).primaryTextTheme.body1 + .backgroundColor, decoration: TextDecoration.none, ), )), diff --git a/lib/src/widgets/base_alert_dialog.dart b/lib/src/widgets/base_alert_dialog.dart index 069c2bcf9..19c00ca43 100644 --- a/lib/src/widgets/base_alert_dialog.dart +++ b/lib/src/widgets/base_alert_dialog.dart @@ -61,18 +61,24 @@ class BaseAlertDialog extends StatelessWidget { fontSize: 15, fontFamily: 'Lato', fontWeight: FontWeight.w600, - color: Colors.white, + color: Theme.of(context).primaryTextTheme.body2 + .backgroundColor, decoration: TextDecoration.none, ), )), ), ) ), + Container( + width: 1, + height: 52, + color: Theme.of(context).dividerColor, + ), Flexible( child: Container( height: 52, padding: EdgeInsets.only(left: 6, right: 6), - color: Theme.of(context).accentTextTheme.body2.color, + color: Theme.of(context).accentTextTheme.body1.backgroundColor, child: ButtonTheme( minWidth: double.infinity, child: FlatButton( @@ -86,7 +92,8 @@ class BaseAlertDialog extends StatelessWidget { fontSize: 15, fontFamily: 'Lato', fontWeight: FontWeight.w600, - color: Colors.white, + color: Theme.of(context).primaryTextTheme.body1 + .backgroundColor, decoration: TextDecoration.none, ), )), @@ -133,6 +140,10 @@ class BaseAlertDialog extends StatelessWidget { ], ), ), + Container( + height: 1, + color: Theme.of(context).dividerColor, + ), actionButtons(context) ], ), diff --git a/lib/src/widgets/seed_widget.dart b/lib/src/widgets/seed_widget.dart index ddebbffa0..b53dd8644 100644 --- a/lib/src/widgets/seed_widget.dart +++ b/lib/src/widgets/seed_widget.dart @@ -12,20 +12,21 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter/widgets.dart'; class SeedWidget extends StatefulWidget { - SeedWidget({Key key, this.language}) : super(key: key); + SeedWidget({Key key, this.language, this.type}) : super(key: key); final String language; + final WalletType type; @override - SeedWidgetState createState() => SeedWidgetState(language); + SeedWidgetState createState() => SeedWidgetState(language, type); } class SeedWidgetState extends State { - SeedWidgetState(String language) + SeedWidgetState(String language, this.type) : controller = TextEditingController(), focusNode = FocusNode(), words = SeedValidator.getWordList( - type: WalletType.monero, language: language) { + type:type, language: language) { focusNode.addListener(() { setState(() { if (!focusNode.hasFocus && controller.text.isEmpty) { @@ -41,6 +42,7 @@ class SeedWidgetState extends State { final TextEditingController controller; final FocusNode focusNode; + final WalletType type; List words; bool _showPlaceholder; @@ -55,7 +57,7 @@ class SeedWidgetState extends State { void changeSeedLanguage(String language) { setState(() { words = SeedValidator.getWordList( - type: WalletType.monero, language: language); + type: type, language: language); }); } diff --git a/lib/src/widgets/standard_checkbox.dart b/lib/src/widgets/standard_checkbox.dart new file mode 100644 index 000000000..774acc9b7 --- /dev/null +++ b/lib/src/widgets/standard_checkbox.dart @@ -0,0 +1,76 @@ +import 'dart:ui'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class StandardCheckbox extends StatefulWidget { + StandardCheckbox({ + @required this.value, + this.caption = '', + @required this.onChanged}); + + final bool value; + final String caption; + final Function(bool) onChanged; + + @override + StandardCheckboxState createState() => + StandardCheckboxState(value, caption, onChanged); +} + +class StandardCheckboxState extends State { + StandardCheckboxState(this.value, this.caption, this.onChanged); + + bool value; + String caption; + Function(bool) onChanged; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + value = !value; + onChanged(value); + setState(() {}); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + height: 24.0, + width: 24.0, + margin: EdgeInsets.only( + right: 10.0, + ), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context) + .primaryTextTheme + .caption + .color, + width: 1.0), + borderRadius: BorderRadius.all( + Radius.circular(8.0)), + color: Theme.of(context).backgroundColor), + child: value + ? Icon( + Icons.check, + color: Colors.blue, + size: 20.0, + ) + : Offstage(), + ), + Text( + caption, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context) + .primaryTextTheme + .title + .color), + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/widgets/trail_button.dart b/lib/src/widgets/trail_button.dart index a7c4caecb..8fa1bf5e5 100644 --- a/lib/src/widgets/trail_button.dart +++ b/lib/src/widgets/trail_button.dart @@ -21,7 +21,10 @@ class TrailButton extends StatelessWidget { child: Text( caption, style: TextStyle( - color: Theme.of(context).textTheme.subhead.decorationColor, + color: Theme.of(context) + .accentTextTheme + .display4 + .decorationColor, fontWeight: FontWeight.w500, fontSize: 14), ), diff --git a/lib/store/app_store.dart b/lib/store/app_store.dart index 7421fa54e..6c95ebafe 100644 --- a/lib/store/app_store.dart +++ b/lib/store/app_store.dart @@ -26,4 +26,10 @@ abstract class AppStoreBase with Store { SettingsStore settingsStore; NodeListStore nodeListStore; + + @action + void changeCurrentWallet(WalletBase wallet) { + this.wallet?.close(); + this.wallet = wallet; + } } diff --git a/lib/store/dashboard/fiat_conversion_store.dart b/lib/store/dashboard/fiat_conversion_store.dart index 48aed82b7..386ad6455 100644 --- a/lib/store/dashboard/fiat_conversion_store.dart +++ b/lib/store/dashboard/fiat_conversion_store.dart @@ -1,13 +1,13 @@ +import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:mobx/mobx.dart'; part 'fiat_conversion_store.g.dart'; -class FiatConversionStore = FiatConversionStoreBase - with _$FiatConversionStore; +class FiatConversionStore = FiatConversionStoreBase with _$FiatConversionStore; abstract class FiatConversionStoreBase with Store { - FiatConversionStoreBase() : price = 0.0; + FiatConversionStoreBase() : prices = ObservableMap(); @observable - double price; + ObservableMap prices; } diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart index 46e1193bb..02ad383e7 100644 --- a/lib/store/dashboard/trade_filter_store.dart +++ b/lib/store/dashboard/trade_filter_store.dart @@ -3,7 +3,7 @@ import 'package:mobx/mobx.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart'; -part 'trade_filter_store.g.dart'; +part'trade_filter_store.g.dart'; class TradeFilterStore = TradeFilterStoreBase with _$TradeFilterStore; diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index b4b76bb8d..30cf4f238 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/entities/preferences_key.dart'; -import 'package:cake_wallet/themes.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/themes/theme_list.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; @@ -28,7 +29,7 @@ abstract class SettingsStoreBase with Store { @required BalanceDisplayMode initialBalanceDisplayMode, @required bool initialSaveRecipientAddress, @required bool initialAllowBiometricalAuthentication, - @required bool initialDarkTheme, + @required ThemeBase initialTheme, @required int initialPinLength, @required String initialLanguageCode, // @required String initialCurrentLocale, @@ -40,10 +41,9 @@ abstract class SettingsStoreBase with Store { balanceDisplayMode = initialBalanceDisplayMode; shouldSaveRecipientAddress = initialSaveRecipientAddress; allowBiometricalAuthentication = initialAllowBiometricalAuthentication; - isDarkTheme = initialDarkTheme; + currentTheme = initialTheme; pinCodeLength = initialPinLength; languageCode = initialLanguageCode; - currentNode = nodes[WalletType.monero]; this.nodes = ObservableMap.of(nodes); _sharedPreferences = sharedPreferences; @@ -65,9 +65,9 @@ abstract class SettingsStoreBase with Store { shouldSaveRecipientAddress)); reaction( - (_) => isDarkTheme, - (bool isDarkTheme) => sharedPreferences.setBool( - PreferencesKey.currentDarkTheme, isDarkTheme)); + (_) => currentTheme, + (ThemeBase theme) => + sharedPreferences.setInt(PreferencesKey.currentTheme, theme.raw)); reaction( (_) => allowBiometricalAuthentication, @@ -80,13 +80,14 @@ abstract class SettingsStoreBase with Store { (int pinLength) => sharedPreferences.setInt( PreferencesKey.currentPinLength, pinLength)); - reaction((_) => currentNode, - (Node node) => _saveCurrentNode(node, WalletType.monero)); - reaction( (_) => languageCode, (String languageCode) => sharedPreferences.setString( PreferencesKey.currentLanguageCode, languageCode)); + + this + .nodes + .observe((change) => _saveCurrentNode(change.newValue, change.key)); } static const defaultPinLength = 4; @@ -111,16 +112,13 @@ abstract class SettingsStoreBase with Store { bool allowBiometricalAuthentication; @observable - bool isDarkTheme; + ThemeBase currentTheme; @observable int pinCodeLength; - @observable - Node currentNode; - @computed - ThemeData get theme => isDarkTheme ? Themes.darkTheme : Themes.lightTheme; + ThemeData get theme => currentTheme.themeData; @observable String languageCode; @@ -154,8 +152,14 @@ abstract class SettingsStoreBase with Store { final allowBiometricalAuthentication = sharedPreferences .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? false; - final savedDarkTheme = - sharedPreferences.getBool(PreferencesKey.currentDarkTheme) ?? false; + final legacyTheme = + sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) + ? ThemeType.dark.index + : ThemeType.bright.index; + final savedTheme = ThemeList.deserialize( + raw: sharedPreferences.getInt(PreferencesKey.currentTheme) ?? + legacyTheme ?? + 0); final actionListDisplayMode = ObservableList(); actionListDisplayMode.addAll(deserializeActionlistDisplayModes( sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? @@ -188,7 +192,7 @@ abstract class SettingsStoreBase with Store { initialBalanceDisplayMode: currentBalanceDisplayMode, initialSaveRecipientAddress: shouldSaveRecipientAddress, initialAllowBiometricalAuthentication: allowBiometricalAuthentication, - initialDarkTheme: savedDarkTheme, + initialTheme: savedTheme, actionlistDisplayMode: actionListDisplayMode, initialPinLength: pinLength, initialLanguageCode: savedLanguageCode); diff --git a/lib/themes.dart b/lib/themes.dart deleted file mode 100644 index aa7af6cf2..000000000 --- a/lib/themes.dart +++ /dev/null @@ -1,353 +0,0 @@ -import 'package:flutter/material.dart'; -import 'palette.dart'; - -class Themes { - - static final ThemeData lightTheme = ThemeData( - fontFamily: 'Lato', - brightness: Brightness.light, - backgroundColor: Colors.white, - accentColor: Palette.blueCraiola, // first gradient color - scaffoldBackgroundColor: Palette.pinkFlamingo, // second gradient color - primaryColor: Palette.redHat, // third gradient color - buttonColor: Colors.white.withOpacity(0.2), // action buttons on dashboard page - indicatorColor: Colors.white.withOpacity(0.5), // page indicator - hoverColor: Colors.white, // amount hint text (receive page) - dividerColor: Palette.paleBlue, - hintColor: Palette.gray, - textTheme: TextTheme( - title: TextStyle( - color: Colors.white, // sync_indicator text - backgroundColor: Colors.white.withOpacity(0.2), // synced sync_indicator - decorationColor: Colors.white.withOpacity(0.15), // not synced sync_indicator - ), - caption: TextStyle( - color: Palette.shineOrange, // not synced light - decorationColor: Colors.white, // filter icon - ), - overline: TextStyle( - color: Colors.white.withOpacity(0.2), // filter button - backgroundColor: Colors.white.withOpacity(0.5), // date section row - decorationColor: Colors.white.withOpacity(0.2) // icons (transaction and trade rows) - ), - subhead: TextStyle( - color: Colors.white.withOpacity(0.2), // address button border - decorationColor: Colors.white.withOpacity(0.4), // copy button (qr widget) - ), - headline: TextStyle( - color: Colors.white, // qr code - decorationColor: Colors.white.withOpacity(0.5), // bottom border of amount (receive page) - ), - display1: TextStyle( - color: PaletteDark.lightBlueGrey, // icons color (receive page) - decorationColor: Palette.lavender, // icons background (receive page) - ), - display2: TextStyle( - color: Palette.darkBlueCraiola, // text color of tiles (receive page) - decorationColor: Colors.white // background of tiles (receive page) - ), - display3: TextStyle( - color: Colors.white, // text color of current tile (receive page), - //decorationColor: Palette.blueCraiola // background of current tile (receive page) - decorationColor: Palette.moderateSlateBlue // background of current tile (receive page) - ), - display4: TextStyle( - color: Palette.violetBlue, // text color of tiles (account list) - decorationColor: Colors.white // background of tiles (account list) - ), - subtitle: TextStyle( - color: Palette.moderateSlateBlue, // text color of current tile (account list) - decorationColor: Colors.white // background of current tile (account list) - ), - body1: TextStyle( - color: Palette.moderatePurpleBlue, // scrollbar thumb - decorationColor: Palette.periwinkleCraiola // scrollbar background - ), - body2: TextStyle( - color: Palette.moderateLavender, // menu header - decorationColor: Colors.white, // menu background - ) - ), - primaryTextTheme: TextTheme( - title: TextStyle( - color: Palette.darkBlueCraiola, // title color - backgroundColor: Palette.wildPeriwinkle // textfield underline - ), - caption: TextStyle( - color: PaletteDark.pigeonBlue, // secondary text - decorationColor: Palette.wildLavender // menu divider - ), - overline: TextStyle( - color: Palette.darkGray, // transaction/trade details titles - decorationColor: Colors.white.withOpacity(0.5), // placeholder - ), - subhead: TextStyle( - color: Palette.blueCraiola, // first gradient color (send page) - decorationColor: Palette.pinkFlamingo // second gradient color (send page) - ), - headline: TextStyle( - color: Colors.white.withOpacity(0.5), // text field border color (send page) - decorationColor: Colors.white.withOpacity(0.5), // text field hint color (send page) - ), - display1: TextStyle( - color: Colors.white.withOpacity(0.2), // text field button color (send page) - decorationColor: Colors.white // text field button icon color (send page) - ), - display2: TextStyle( - color: Colors.white.withOpacity(0.5), // estimated fee (send page) - decorationColor: Palette.shadowWhite // template dotted border (send page) - ), - display3: TextStyle( - color: Palette.darkBlueCraiola, // template new text (send page) - decorationColor: Palette.shadowWhite // template background color (send page) - ), - display4: TextStyle( - color: Palette.darkBlueCraiola, // template title (send page) - decorationColor: Palette.niagara // receive amount text (exchange page) - ), - subtitle: TextStyle( - color: Palette.blueCraiola, // first gradient color top panel (exchange page) - decorationColor: Palette.pinkFlamingo // second gradient color top panel (exchange page) - ), - body1: TextStyle( - color: Palette.blueCraiola.withOpacity(0.7), // first gradient color bottom panel (exchange page) - decorationColor: Palette.pinkFlamingo.withOpacity(0.7) // second gradient color bottom panel (exchange page) - ), - body2: TextStyle( - color: Colors.white.withOpacity(0.5), // text field border on top panel (exchange page) - decorationColor: Colors.white.withOpacity(0.5), // text field border on bottom panel (exchange page) - ) - ), - focusColor: Colors.white.withOpacity(0.2), // text field button (exchange page) - accentTextTheme: TextTheme( - title: TextStyle( - color: Colors.white, // picker background - backgroundColor: Palette.periwinkleCraiola, // picker divider - decorationColor: Colors.white // dialog background - ), - caption: TextStyle( - color: Palette.moderateLavender, // container (confirm exchange) - backgroundColor: Palette.moderateLavender, // button background (confirm exchange) - decorationColor: Palette.darkBlueCraiola, // text color (information page) - ), - subtitle: TextStyle( - color: Palette.darkBlueCraiola, // QR code (exchange trade page) - backgroundColor: Palette.wildPeriwinkle, // divider (exchange trade page) - //decorationColor: Palette.blueCraiola // crete new wallet button background (wallet list page) - decorationColor: Palette.moderateSlateBlue // crete new wallet button background (wallet list page) - ), - headline: TextStyle( - color: Palette.moderateLavender, // first gradient color of wallet action buttons (wallet list page) - backgroundColor: Palette.moderateLavender, // second gradient color of wallet action buttons (wallet list page) - decorationColor: Colors.white // restore wallet button text color (wallet list page) - ), - subhead: TextStyle( - color: Palette.darkGray, // titles color (filter widget) - backgroundColor: Palette.periwinkle, // divider color (filter widget) - decorationColor: Colors.white // checkbox background (filter widget) - ), - overline: TextStyle( - color: Palette.wildPeriwinkle, // checkbox bounds (filter widget) - decorationColor: Colors.white, // menu subname - ), - display1: TextStyle( - color: Palette.blueCraiola, // first gradient color (menu header) - decorationColor: Palette.pinkFlamingo // second gradient color(menu header) - ), - display2: TextStyle( - color: Palette.shadowWhite, // action button color (address text field) - decorationColor: Palette.darkGray // hint text (seed widget) - ), - display3: TextStyle( - color: Palette.darkGray, // hint text (new wallet page) - decorationColor: Palette.periwinkleCraiola // underline (new wallet page) - ), - display4: TextStyle( - color: Palette.darkGray, // switch background (settings page) - ), - body1: TextStyle( - color: Palette.darkGray, // indicators (PIN code) - decorationColor: Palette.darkGray // switch (PIN code) - ), - body2: TextStyle( - color: Palette.moderateSlateBlue, // primary buttons, alert right buttons - decorationColor: Palette.brightOrange // alert left button - ), - ), - cardColor: Palette.moderateSlateBlue // bottom button (action list) - ); - - - static final ThemeData darkTheme = ThemeData( - fontFamily: 'Lato', - brightness: Brightness.dark, - backgroundColor: PaletteDark.backgroundColor, - accentColor: PaletteDark.backgroundColor, // first gradient color - scaffoldBackgroundColor: PaletteDark.backgroundColor, // second gradient color - primaryColor: PaletteDark.backgroundColor, // third gradient color - buttonColor: PaletteDark.nightBlue, // action buttons on dashboard page - indicatorColor: PaletteDark.cyanBlue, // page indicator - hoverColor: PaletteDark.cyanBlue, // amount hint text (receive page) - dividerColor: PaletteDark.dividerColor, - hintColor: PaletteDark.pigeonBlue, // menu - textTheme: TextTheme( - title: TextStyle( - color: PaletteDark.wildBlue, // sync_indicator text - backgroundColor: PaletteDark.lightNightBlue, // synced sync_indicator - decorationColor: PaletteDark.oceanBlue // not synced sync_indicator - ), - caption: TextStyle( - color: PaletteDark.orangeYellow, // not synced light - decorationColor: PaletteDark.wildBlue, // filter icon - ), - overline: TextStyle( - color: PaletteDark.oceanBlue, // filter button - backgroundColor: PaletteDark.darkCyanBlue, // date section row - decorationColor: PaletteDark.wildNightBlue // icons (transaction and trade rows) - ), - subhead: TextStyle( - color: PaletteDark.nightBlue, // address button border - decorationColor: PaletteDark.lightBlueGrey, // copy button (qr widget) - ), - headline: TextStyle( - color: PaletteDark.lightBlueGrey, // qr code - decorationColor: PaletteDark.darkGrey, // bottom border of amount (receive page) - ), - display1: TextStyle( - color: Colors.white, // icons color (receive page) - decorationColor: PaletteDark.distantNightBlue, // icons background (receive page) - ), - display2: TextStyle( - color: Colors.white, // text color of tiles (receive page) - decorationColor: PaletteDark.nightBlue // background of tiles (receive page) - ), - display3: TextStyle( - color: Palette.blueCraiola, // text color of current tile (receive page) - decorationColor: PaletteDark.lightOceanBlue // background of current tile (receive page) - ), - display4: TextStyle( - color: Colors.white, // text color of tiles (account list) - decorationColor: PaletteDark.darkOceanBlue // background of tiles (account list) - ), - subtitle: TextStyle( - color: Palette.blueCraiola, // text color of current tile (account list) - decorationColor: PaletteDark.darkNightBlue // background of current tile (account list) - ), - body1: TextStyle( - color: PaletteDark.wildBlueGrey, // scrollbar thumb - decorationColor: PaletteDark.violetBlue // scrollbar background - ), - body2: TextStyle( - color: PaletteDark.deepPurpleBlue, // menu header - decorationColor: PaletteDark.deepPurpleBlue, // menu background - ) - ), - primaryTextTheme: TextTheme( - title: TextStyle( - color: Colors.white, // title color - backgroundColor: PaletteDark.darkOceanBlue // textfield underline - ), - caption: TextStyle( - color: PaletteDark.darkCyanBlue, // secondary text - decorationColor: PaletteDark.darkOceanBlue // menu divider - ), - overline: TextStyle( - color: PaletteDark.lightBlueGrey, // transaction/trade details titles - decorationColor: Colors.grey, // placeholder - ), - subhead: TextStyle( - color: PaletteDark.darkNightBlue, // first gradient color (send page) - decorationColor: PaletteDark.darkNightBlue // second gradient color (send page) - ), - headline: TextStyle( - color: PaletteDark.lightVioletBlue, // text field border color (send page) - decorationColor: PaletteDark.darkCyanBlue, // text field hint color (send page) - ), - display1: TextStyle( - color: PaletteDark.buttonNightBlue, // text field button color (send page) - decorationColor: PaletteDark.gray // text field button icon color (send page) - ), - display2: TextStyle( - color: Colors.white, // estimated fee (send page) - decorationColor: PaletteDark.darkCyanBlue // template dotted border (send page) - ), - display3: TextStyle( - color: PaletteDark.darkCyanBlue, // template new text (send page) - decorationColor: PaletteDark.darkVioletBlue // template background color (send page) - ), - display4: TextStyle( - color: PaletteDark.cyanBlue, // template title (send page) - decorationColor: PaletteDark.darkCyanBlue // receive amount text (exchange page) - ), - subtitle: TextStyle( - color: PaletteDark.wildVioletBlue, // first gradient color top panel (exchange page) - decorationColor: PaletteDark.wildVioletBlue // second gradient color top panel (exchange page) - ), - body1: TextStyle( - color: PaletteDark.darkNightBlue, // first gradient color bottom panel (exchange page) - decorationColor: PaletteDark.darkNightBlue // second gradient color bottom panel (exchange page) - ), - body2: TextStyle( - color: PaletteDark.blueGrey, // text field border on top panel (exchange page) - decorationColor: PaletteDark.moderateVioletBlue, // text field border on bottom panel (exchange page) - ) - ), - focusColor: PaletteDark.moderateBlue, // text field button (exchange page) - accentTextTheme: TextTheme( - title: TextStyle( - color: PaletteDark.nightBlue, // picker background - backgroundColor: PaletteDark.dividerColor, // picker divider - decorationColor: PaletteDark.darkNightBlue // dialog background - ), - caption: TextStyle( - color: PaletteDark.nightBlue, // container (confirm exchange) - backgroundColor: PaletteDark.deepVioletBlue, // button background (confirm exchange) - decorationColor: Palette.darkLavender, // text color (information page) - ), - subtitle: TextStyle( - //color: PaletteDark.lightBlueGrey, // QR code (exchange trade page) - color: Colors.white, // QR code (exchange trade page) - backgroundColor: PaletteDark.deepVioletBlue, // divider (exchange trade page) - decorationColor: Colors.white // crete new wallet button background (wallet list page) - ), - headline: TextStyle( - color: PaletteDark.distantBlue, // first gradient color of wallet action buttons (wallet list page) - backgroundColor: PaletteDark.distantNightBlue, // second gradient color of wallet action buttons (wallet list page) - decorationColor: Palette.darkBlueCraiola // restore wallet button text color (wallet list page) - ), - subhead: TextStyle( - color: Colors.white, // titles color (filter widget) - backgroundColor: PaletteDark.darkOceanBlue, // divider color (filter widget) - decorationColor: PaletteDark.wildVioletBlue.withOpacity(0.3) // checkbox background (filter widget) - ), - overline: TextStyle( - color: PaletteDark.wildVioletBlue, // checkbox bounds (filter widget) - decorationColor: PaletteDark.darkCyanBlue, // menu subname - ), - display1: TextStyle( - color: PaletteDark.deepPurpleBlue, // first gradient color (menu header) - decorationColor: PaletteDark.deepPurpleBlue // second gradient color(menu header) - ), - display2: TextStyle( - color: PaletteDark.nightBlue, // action button color (address text field) - decorationColor: PaletteDark.darkCyanBlue // hint text (seed widget) - ), - display3: TextStyle( - color: PaletteDark.cyanBlue, // hint text (new wallet page) - decorationColor: PaletteDark.darkGrey // underline (new wallet page) - ), - display4: TextStyle( - color: PaletteDark.deepVioletBlue, // switch background (settings page) - ), - body1: TextStyle( - color: PaletteDark.indicatorVioletBlue, // indicators (PIN code) - decorationColor: PaletteDark.lightPurpleBlue // switch (PIN code) - ), - body2: TextStyle( - color: Palette.blueCraiola, // primary buttons, alert right buttons - decorationColor: Palette.alizarinRed // alert left button - ), - ), - cardColor: PaletteDark.darkNightBlue // bottom button (action list) - ); -} \ No newline at end of file diff --git a/lib/themes/bright_theme.dart b/lib/themes/bright_theme.dart new file mode 100644 index 000000000..39c33f8f1 --- /dev/null +++ b/lib/themes/bright_theme.dart @@ -0,0 +1,197 @@ +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:flutter/material.dart'; + +class BrightTheme extends ThemeBase { + BrightTheme({@required int raw}) : super(raw: raw); + + @override + String get title => S.current.bright_theme; + + @override + ThemeType get type => ThemeType.bright; + + @override + ThemeData get themeData => ThemeData( + fontFamily: 'Lato', + brightness: Brightness.light, + backgroundColor: Colors.white, + accentColor: Palette.blueCraiola, // first gradient color + scaffoldBackgroundColor: Palette.pinkFlamingo, // second gradient color + primaryColor: Palette.redHat, // third gradient color + buttonColor: Colors.white.withOpacity(0.2), // action buttons on dashboard page + indicatorColor: Colors.white.withOpacity(0.5), // page indicator + hoverColor: Colors.white, // amount hint text (receive page) + dividerColor: Palette.paleBlue, + hintColor: Palette.gray, + textTheme: TextTheme( + title: TextStyle( + color: Colors.white, // sync_indicator text + backgroundColor: Colors.white.withOpacity(0.2), // synced sync_indicator + decorationColor: Colors.white.withOpacity(0.15), // not synced sync_indicator + ), + caption: TextStyle( + color: Palette.shineOrange, // not synced light + decorationColor: Colors.white, // filter icon + ), + overline: TextStyle( + color: Colors.white.withOpacity(0.2), // filter button + backgroundColor: Colors.white.withOpacity(0.5), // date section row + decorationColor: Colors.white.withOpacity(0.2) // icons (transaction and trade rows) + ), + subhead: TextStyle( + color: Colors.white.withOpacity(0.2), // address button border + decorationColor: Colors.white.withOpacity(0.4), // copy button (qr widget) + ), + headline: TextStyle( + color: Colors.white, // qr code + decorationColor: Colors.white.withOpacity(0.5), // bottom border of amount (receive page) + ), + display1: TextStyle( + color: PaletteDark.lightBlueGrey, // icons color (receive page) + decorationColor: Palette.lavender, // icons background (receive page) + ), + display2: TextStyle( + color: Palette.darkBlueCraiola, // text color of tiles (receive page) + decorationColor: Colors.white // background of tiles (receive page) + ), + display3: TextStyle( + color: Colors.white, // text color of current tile (receive page), + //decorationColor: Palette.blueCraiola // background of current tile (receive page) + decorationColor: Palette.moderateSlateBlue // background of current tile (receive page) + ), + display4: TextStyle( + color: Palette.violetBlue, // text color of tiles (account list) + decorationColor: Colors.white // background of tiles (account list) + ), + subtitle: TextStyle( + color: Palette.moderateSlateBlue, // text color of current tile (account list) + decorationColor: Colors.white // background of current tile (account list) + ), + body1: TextStyle( + color: Palette.moderatePurpleBlue, // scrollbar thumb + decorationColor: Palette.periwinkleCraiola // scrollbar background + ), + body2: TextStyle( + color: Palette.moderateLavender, // menu header + decorationColor: Colors.white, // menu background + ) + ), + primaryTextTheme: TextTheme( + title: TextStyle( + color: Palette.darkBlueCraiola, // title color + backgroundColor: Palette.wildPeriwinkle // textfield underline + ), + caption: TextStyle( + color: PaletteDark.pigeonBlue, // secondary text + decorationColor: Palette.wildLavender // menu divider + ), + overline: TextStyle( + color: Palette.darkGray, // transaction/trade details titles + decorationColor: Colors.white.withOpacity(0.5), // placeholder + ), + subhead: TextStyle( + color: Palette.blueCraiola, // first gradient color (send page) + decorationColor: Palette.pinkFlamingo // second gradient color (send page) + ), + headline: TextStyle( + color: Colors.white.withOpacity(0.5), // text field border color (send page) + decorationColor: Colors.white.withOpacity(0.5), // text field hint color (send page) + ), + display1: TextStyle( + color: Colors.white.withOpacity(0.2), // text field button color (send page) + decorationColor: Colors.white // text field button icon color (send page) + ), + display2: TextStyle( + color: Colors.white.withOpacity(0.5), // estimated fee (send page) + decorationColor: Palette.shadowWhite // template dotted border (send page) + ), + display3: TextStyle( + color: Palette.darkBlueCraiola, // template new text (send page) + decorationColor: Palette.shadowWhite // template background color (send page) + ), + display4: TextStyle( + color: Palette.darkBlueCraiola, // template title (send page) + decorationColor: Palette.niagara // receive amount text (exchange page) + ), + subtitle: TextStyle( + color: Palette.blueCraiola, // first gradient color top panel (exchange page) + decorationColor: Palette.pinkFlamingo // second gradient color top panel (exchange page) + ), + body1: TextStyle( + color: Palette.blueCraiola.withOpacity(0.7), // first gradient color bottom panel (exchange page) + decorationColor: Palette.pinkFlamingo.withOpacity(0.7), // second gradient color bottom panel (exchange page) + backgroundColor: Palette.moderateSlateBlue // alert right button text + ), + body2: TextStyle( + color: Colors.white.withOpacity(0.5), // text field border on top panel (exchange page) + decorationColor: Colors.white.withOpacity(0.5), // text field border on bottom panel (exchange page) + backgroundColor: Palette.brightOrange // alert left button text + ) + ), + focusColor: Colors.white.withOpacity(0.2), // text field button (exchange page) + accentTextTheme: TextTheme( + title: TextStyle( + color: Colors.white, // picker background + backgroundColor: Palette.periwinkleCraiola, // picker divider + decorationColor: Colors.white // dialog background + ), + caption: TextStyle( + color: Palette.moderateLavender, // container (confirm exchange) + backgroundColor: Palette.moderateLavender, // button background (confirm exchange) + decorationColor: Palette.darkBlueCraiola, // text color (information page) + ), + subtitle: TextStyle( + color: Palette.darkBlueCraiola, // QR code (exchange trade page) + backgroundColor: Palette.wildPeriwinkle, // divider (exchange trade page) + //decorationColor: Palette.blueCraiola // crete new wallet button background (wallet list page) + decorationColor: Palette.moderateSlateBlue // crete new wallet button background (wallet list page) + ), + headline: TextStyle( + color: Palette.moderateLavender, // first gradient color of wallet action buttons (wallet list page) + backgroundColor: Palette.moderateLavender, // second gradient color of wallet action buttons (wallet list page) + decorationColor: Colors.white // restore wallet button text color (wallet list page) + ), + subhead: TextStyle( + color: Palette.darkGray, // titles color (filter widget) + backgroundColor: Palette.periwinkle, // divider color (filter widget) + decorationColor: Colors.white // checkbox background (filter widget) + ), + overline: TextStyle( + color: Palette.wildPeriwinkle, // checkbox bounds (filter widget) + decorationColor: Colors.white, // menu subname + ), + display1: TextStyle( + color: Palette.blueCraiola, // first gradient color (menu header) + decorationColor: Palette.pinkFlamingo, // second gradient color(menu header) + backgroundColor: Colors.white // active dot color + ), + display2: TextStyle( + color: Palette.shadowWhite, // action button color (address text field) + decorationColor: Palette.darkGray, // hint text (seed widget) + backgroundColor: Colors.white.withOpacity(0.5) // text on balance page + ), + display3: TextStyle( + color: Palette.darkGray, // hint text (new wallet page) + decorationColor: Palette.periwinkleCraiola, // underline (new wallet page) + backgroundColor: Colors.white // menu, icons, balance (dashboard page) + ), + display4: TextStyle( + color: Palette.darkGray, // switch background (settings page) + decorationColor: Colors.white.withOpacity(0.4) // hint text (exchange page) + ), + body1: TextStyle( + color: Palette.darkGray, // indicators (PIN code) + decorationColor: Palette.darkGray, // switch (PIN code) + backgroundColor: Colors.white // alert right button + ), + body2: TextStyle( + color: Palette.moderateSlateBlue, // primary buttons + decorationColor: Colors.white, // alert left button, + backgroundColor: Palette.dullGray // keyboard bar color + ), + ), + cardColor: Palette.moderateSlateBlue // bottom button (action list) + ); +} \ No newline at end of file diff --git a/lib/themes/dark_theme.dart b/lib/themes/dark_theme.dart new file mode 100644 index 000000000..09fb5526f --- /dev/null +++ b/lib/themes/dark_theme.dart @@ -0,0 +1,196 @@ +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:flutter/material.dart'; + +class DarkTheme extends ThemeBase { + DarkTheme({@required int raw}) : super(raw: raw); + + @override + String get title => S.current.dark_theme; + + @override + ThemeType get type => ThemeType.dark; + + @override + ThemeData get themeData => ThemeData( + fontFamily: 'Lato', + brightness: Brightness.dark, + backgroundColor: PaletteDark.backgroundColor, + accentColor: PaletteDark.backgroundColor, // first gradient color + scaffoldBackgroundColor: PaletteDark.backgroundColor, // second gradient color + primaryColor: PaletteDark.backgroundColor, // third gradient color + buttonColor: PaletteDark.nightBlue, // action buttons on dashboard page + indicatorColor: PaletteDark.cyanBlue, // page indicator + hoverColor: PaletteDark.cyanBlue, // amount hint text (receive page) + dividerColor: PaletteDark.dividerColor, + hintColor: PaletteDark.pigeonBlue, // menu + textTheme: TextTheme( + title: TextStyle( + color: PaletteDark.wildBlue, // sync_indicator text + backgroundColor: PaletteDark.lightNightBlue, // synced sync_indicator + decorationColor: PaletteDark.oceanBlue // not synced sync_indicator + ), + caption: TextStyle( + color: PaletteDark.orangeYellow, // not synced light + decorationColor: PaletteDark.wildBlue, // filter icon + ), + overline: TextStyle( + color: PaletteDark.oceanBlue, // filter button + backgroundColor: PaletteDark.darkCyanBlue, // date section row + decorationColor: PaletteDark.wildNightBlue // icons (transaction and trade rows) + ), + subhead: TextStyle( + color: PaletteDark.nightBlue, // address button border + decorationColor: PaletteDark.lightBlueGrey, // copy button (qr widget) + ), + headline: TextStyle( + color: PaletteDark.lightBlueGrey, // qr code + decorationColor: PaletteDark.darkGrey, // bottom border of amount (receive page) + ), + display1: TextStyle( + color: Colors.white, // icons color (receive page) + decorationColor: PaletteDark.distantNightBlue, // icons background (receive page) + ), + display2: TextStyle( + color: Colors.white, // text color of tiles (receive page) + decorationColor: PaletteDark.nightBlue // background of tiles (receive page) + ), + display3: TextStyle( + color: Palette.blueCraiola, // text color of current tile (receive page) + decorationColor: PaletteDark.lightOceanBlue // background of current tile (receive page) + ), + display4: TextStyle( + color: Colors.white, // text color of tiles (account list) + decorationColor: PaletteDark.darkOceanBlue // background of tiles (account list) + ), + subtitle: TextStyle( + color: Palette.blueCraiola, // text color of current tile (account list) + decorationColor: PaletteDark.darkNightBlue // background of current tile (account list) + ), + body1: TextStyle( + color: PaletteDark.wildBlueGrey, // scrollbar thumb + decorationColor: PaletteDark.violetBlue // scrollbar background + ), + body2: TextStyle( + color: PaletteDark.deepPurpleBlue, // menu header + decorationColor: PaletteDark.deepPurpleBlue, // menu background + ) + ), + primaryTextTheme: TextTheme( + title: TextStyle( + color: Colors.white, // title color + backgroundColor: PaletteDark.darkOceanBlue // textfield underline + ), + caption: TextStyle( + color: PaletteDark.darkCyanBlue, // secondary text + decorationColor: PaletteDark.darkOceanBlue // menu divider + ), + overline: TextStyle( + color: PaletteDark.lightBlueGrey, // transaction/trade details titles + decorationColor: Colors.grey, // placeholder + ), + subhead: TextStyle( + color: PaletteDark.darkNightBlue, // first gradient color (send page) + decorationColor: PaletteDark.darkNightBlue // second gradient color (send page) + ), + headline: TextStyle( + color: PaletteDark.lightVioletBlue, // text field border color (send page) + decorationColor: PaletteDark.darkCyanBlue, // text field hint color (send page) + ), + display1: TextStyle( + color: PaletteDark.buttonNightBlue, // text field button color (send page) + decorationColor: PaletteDark.gray // text field button icon color (send page) + ), + display2: TextStyle( + color: Colors.white, // estimated fee (send page) + decorationColor: PaletteDark.darkCyanBlue // template dotted border (send page) + ), + display3: TextStyle( + color: PaletteDark.darkCyanBlue, // template new text (send page) + decorationColor: PaletteDark.darkVioletBlue // template background color (send page) + ), + display4: TextStyle( + color: PaletteDark.cyanBlue, // template title (send page) + decorationColor: PaletteDark.darkCyanBlue // receive amount text (exchange page) + ), + subtitle: TextStyle( + color: PaletteDark.wildVioletBlue, // first gradient color top panel (exchange page) + decorationColor: PaletteDark.wildVioletBlue // second gradient color top panel (exchange page) + ), + body1: TextStyle( + color: PaletteDark.darkNightBlue, // first gradient color bottom panel (exchange page) + decorationColor: PaletteDark.darkNightBlue, // second gradient color bottom panel (exchange page) + backgroundColor: Palette.blueCraiola // alert right button text + ), + body2: TextStyle( + color: PaletteDark.blueGrey, // text field border on top panel (exchange page) + decorationColor: PaletteDark.moderateVioletBlue, // text field border on bottom panel (exchange page) + backgroundColor: Palette.alizarinRed // alert left button text + ) + ), + focusColor: PaletteDark.moderateBlue, // text field button (exchange page) + accentTextTheme: TextTheme( + title: TextStyle( + color: PaletteDark.nightBlue, // picker background + backgroundColor: PaletteDark.dividerColor, // picker divider + decorationColor: PaletteDark.darkNightBlue // dialog background + ), + caption: TextStyle( + color: PaletteDark.nightBlue, // container (confirm exchange) + backgroundColor: PaletteDark.deepVioletBlue, // button background (confirm exchange) + decorationColor: Palette.darkLavender, // text color (information page) + ), + subtitle: TextStyle( + //color: PaletteDark.lightBlueGrey, // QR code (exchange trade page) + color: Colors.white, // QR code (exchange trade page) + backgroundColor: PaletteDark.deepVioletBlue, // divider (exchange trade page) + decorationColor: Colors.white // crete new wallet button background (wallet list page) + ), + headline: TextStyle( + color: PaletteDark.distantBlue, // first gradient color of wallet action buttons (wallet list page) + backgroundColor: PaletteDark.distantNightBlue, // second gradient color of wallet action buttons (wallet list page) + decorationColor: Palette.darkBlueCraiola // restore wallet button text color (wallet list page) + ), + subhead: TextStyle( + color: Colors.white, // titles color (filter widget) + backgroundColor: PaletteDark.darkOceanBlue, // divider color (filter widget) + decorationColor: PaletteDark.wildVioletBlue.withOpacity(0.3) // checkbox background (filter widget) + ), + overline: TextStyle( + color: PaletteDark.wildVioletBlue, // checkbox bounds (filter widget) + decorationColor: PaletteDark.darkCyanBlue, // menu subname + ), + display1: TextStyle( + color: PaletteDark.deepPurpleBlue, // first gradient color (menu header) + decorationColor: PaletteDark.deepPurpleBlue, // second gradient color(menu header) + backgroundColor: Colors.white // active dot color + ), + display2: TextStyle( + color: PaletteDark.nightBlue, // action button color (address text field) + decorationColor: PaletteDark.darkCyanBlue, // hint text (seed widget) + backgroundColor: PaletteDark.cyanBlue // text on balance page + ), + display3: TextStyle( + color: PaletteDark.cyanBlue, // hint text (new wallet page) + decorationColor: PaletteDark.darkGrey, // underline (new wallet page) + backgroundColor: Colors.white // menu, icons, balance (dashboard page) + ), + display4: TextStyle( + color: PaletteDark.deepVioletBlue, // switch background (settings page) + decorationColor: PaletteDark.lightBlueGrey // hint text (exchange page) + ), + body1: TextStyle( + color: PaletteDark.indicatorVioletBlue, // indicators (PIN code) + decorationColor: PaletteDark.lightPurpleBlue, // switch (PIN code) + backgroundColor: PaletteDark.darkNightBlue // alert right button + ), + body2: TextStyle( + color: Palette.blueCraiola, // primary buttons + decorationColor: PaletteDark.darkNightBlue, // alert left button + backgroundColor: PaletteDark.granite // keyboard bar color + ), + ), + cardColor: PaletteDark.darkNightBlue // bottom button (action list) + ); +} \ No newline at end of file diff --git a/lib/themes/light_theme.dart b/lib/themes/light_theme.dart new file mode 100644 index 000000000..7d1486a91 --- /dev/null +++ b/lib/themes/light_theme.dart @@ -0,0 +1,196 @@ +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:flutter/material.dart'; + +class LightTheme extends ThemeBase { + LightTheme({@required int raw}) : super(raw: raw); + + @override + String get title => S.current.light_theme; + + @override + ThemeType get type => ThemeType.light; + + @override + ThemeData get themeData => ThemeData( + fontFamily: 'Lato', + brightness: Brightness.light, + backgroundColor: Colors.white, + accentColor: Colors.white, // first gradient color + scaffoldBackgroundColor: Colors.white, // second gradient color + primaryColor: Colors.white, // third gradient color + buttonColor: Palette.blueAlice, // action buttons on dashboard page + indicatorColor: PaletteDark.darkCyanBlue.withOpacity(0.67), // page indicator + hoverColor: Palette.darkBlueCraiola, // amount hint text (receive page) + dividerColor: Palette.paleBlue, + hintColor: Palette.gray, + textTheme: TextTheme( + title: TextStyle( + color: Palette.darkBlueCraiola, // sync_indicator text + backgroundColor: Palette.blueAlice, // synced sync_indicator + decorationColor: Palette.blueAlice.withOpacity(0.75), // not synced sync_indicator + ), + caption: TextStyle( + color: Palette.shineOrange, // not synced light + decorationColor: PaletteDark.wildBlue, // filter icon + ), + overline: TextStyle( + color: Palette.blueAlice, // filter button + backgroundColor: PaletteDark.darkCyanBlue, // date section row + decorationColor: Palette.blueAlice // icons (transaction and trade rows) + ), + subhead: TextStyle( + color: Palette.blueAlice, // address button border + decorationColor: PaletteDark.lightBlueGrey, // copy button (qr widget) + ), + headline: TextStyle( + color: Colors.white, // qr code + decorationColor: Palette.darkBlueCraiola, // bottom border of amount (receive page) + ), + display1: TextStyle( + color: PaletteDark.lightBlueGrey, // icons color (receive page) + decorationColor: Palette.moderateLavender, // icons background (receive page) + ), + display2: TextStyle( + color: Palette.darkBlueCraiola, // text color of tiles (receive page) + decorationColor: Palette.blueAlice // background of tiles (receive page) + ), + display3: TextStyle( + color: Colors.white, // text color of current tile (receive page), + //decorationColor: Palette.blueCraiola // background of current tile (receive page) + decorationColor: Palette.blueCraiola // background of current tile (receive page) + ), + display4: TextStyle( + color: Palette.violetBlue, // text color of tiles (account list) + decorationColor: Colors.white // background of tiles (account list) + ), + subtitle: TextStyle( + color: Palette.protectiveBlue, // text color of current tile (account list) + decorationColor: Colors.white // background of current tile (account list) + ), + body1: TextStyle( + color: Palette.moderatePurpleBlue, // scrollbar thumb + decorationColor: Palette.periwinkleCraiola // scrollbar background + ), + body2: TextStyle( + color: Palette.moderateLavender, // menu header + decorationColor: Colors.white, // menu background + ) + ), + primaryTextTheme: TextTheme( + title: TextStyle( + color: Palette.darkBlueCraiola, // title color + backgroundColor: Palette.wildPeriwinkle // textfield underline + ), + caption: TextStyle( + color: PaletteDark.pigeonBlue, // secondary text + decorationColor: Palette.wildLavender // menu divider + ), + overline: TextStyle( + color: Palette.darkGray, // transaction/trade details titles + decorationColor: PaletteDark.darkCyanBlue, // placeholder + ), + subhead: TextStyle( + color: Palette.blueCraiola, // first gradient color (send page) + decorationColor: Palette.blueGreyCraiola // second gradient color (send page) + ), + headline: TextStyle( + color: Colors.white.withOpacity(0.5), // text field border color (send page) + decorationColor: Colors.white.withOpacity(0.5), // text field hint color (send page) + ), + display1: TextStyle( + color: Colors.white.withOpacity(0.2), // text field button color (send page) + decorationColor: Colors.white // text field button icon color (send page) + ), + display2: TextStyle( + color: Colors.white.withOpacity(0.5), // estimated fee (send page) + decorationColor: Palette.moderateLavender // template dotted border (send page) + ), + display3: TextStyle( + color: Palette.darkBlueCraiola, // template new text (send page) + decorationColor: Palette.blueAlice // template background color (send page) + ), + display4: TextStyle( + color: Palette.darkBlueCraiola, // template title (send page) + decorationColor: Palette.niagara // receive amount text (exchange page) + ), + subtitle: TextStyle( + color: Palette.blueCraiola, // first gradient color top panel (exchange page) + decorationColor: Palette.blueGreyCraiola // second gradient color top panel (exchange page) + ), + body1: TextStyle( + color: Palette.blueCraiola.withOpacity(0.7), // first gradient color bottom panel (exchange page) + decorationColor: Palette.blueGreyCraiola.withOpacity(0.7), // second gradient color bottom panel (exchange page) + backgroundColor: Palette.protectiveBlue // alert right button text + ), + body2: TextStyle( + color: Colors.white.withOpacity(0.5), // text field border on top panel (exchange page) + decorationColor: Colors.white.withOpacity(0.5), // text field border on bottom panel (exchange page) + backgroundColor: Palette.brightOrange // alert left button text + ) + ), + focusColor: Colors.white.withOpacity(0.2), // text field button (exchange page) + accentTextTheme: TextTheme( + title: TextStyle( + color: Colors.white, // picker background + backgroundColor: Palette.periwinkleCraiola, // picker divider + decorationColor: Colors.white // dialog background + ), + caption: TextStyle( + color: Palette.blueAlice, // container (confirm exchange) + backgroundColor: Palette.blueAlice, // button background (confirm exchange) + decorationColor: Palette.darkBlueCraiola, // text color (information page) + ), + subtitle: TextStyle( + color: Palette.darkBlueCraiola, // QR code (exchange trade page) + backgroundColor: Palette.wildPeriwinkle, // divider (exchange trade page) + decorationColor: Palette.protectiveBlue // crete new wallet button background (wallet list page) + ), + headline: TextStyle( + color: Palette.moderateLavender, // first gradient color of wallet action buttons (wallet list page) + backgroundColor: Palette.moderateLavender, // second gradient color of wallet action buttons (wallet list page) + decorationColor: Colors.white // restore wallet button text color (wallet list page) + ), + subhead: TextStyle( + color: Palette.darkGray, // titles color (filter widget) + backgroundColor: Palette.periwinkle, // divider color (filter widget) + decorationColor: Colors.white // checkbox background (filter widget) + ), + overline: TextStyle( + color: Palette.wildPeriwinkle, // checkbox bounds (filter widget) + decorationColor: Colors.white, // menu subname + ), + display1: TextStyle( + color: Palette.blueCraiola, // first gradient color (menu header) + decorationColor: Palette.blueGreyCraiola, // second gradient color(menu header) + backgroundColor: PaletteDark.darkNightBlue // active dot color + ), + display2: TextStyle( + color: Palette.shadowWhite, // action button color (address text field) + decorationColor: Palette.darkGray, // hint text (seed widget) + backgroundColor: Palette.darkBlueCraiola.withOpacity(0.67) // text on balance page + ), + display3: TextStyle( + color: Palette.darkGray, // hint text (new wallet page) + decorationColor: Palette.periwinkleCraiola, // underline (new wallet page) + backgroundColor: Palette.darkBlueCraiola // menu, icons, balance (dashboard page) + ), + display4: TextStyle( + color: Palette.darkGray, // switch background (settings page) + decorationColor: Colors.white.withOpacity(0.4) // hint text (exchange page) + ), + body1: TextStyle( + color: Palette.darkGray, // indicators (PIN code) + decorationColor: Palette.darkGray, // switch (PIN code) + backgroundColor: Colors.white // alert right button + ), + body2: TextStyle( + color: Palette.protectiveBlue, // primary buttons + decorationColor: Colors.white, // alert left button, + backgroundColor: Palette.dullGray // keyboard bar color + ), + ), + cardColor: Palette.protectiveBlue // bottom button (action list) + ); +} \ No newline at end of file diff --git a/lib/themes/theme_base.dart b/lib/themes/theme_base.dart new file mode 100644 index 000000000..bc7baf70c --- /dev/null +++ b/lib/themes/theme_base.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +enum ThemeType {light, bright, dark} + +abstract class ThemeBase { + ThemeBase({@required this.raw}); + + final int raw; + String get title; + ThemeData get themeData; + ThemeType get type; + + @override + String toString() { + return title; + } +} \ No newline at end of file diff --git a/lib/themes/theme_list.dart b/lib/themes/theme_list.dart new file mode 100644 index 000000000..b4cea1391 --- /dev/null +++ b/lib/themes/theme_list.dart @@ -0,0 +1,25 @@ +import 'package:cake_wallet/themes/bright_theme.dart'; +import 'package:cake_wallet/themes/dark_theme.dart'; +import 'package:cake_wallet/themes/light_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; + +class ThemeList { + static final all = [lightTheme, brightTheme, darkTheme]; + + static final lightTheme = LightTheme(raw: 0); + static final brightTheme = BrightTheme(raw: 1); + static final darkTheme = DarkTheme(raw: 2); + + static ThemeBase deserialize({int raw}) { + switch (raw) { + case 0: + return lightTheme; + case 1: + return brightTheme; + case 2: + return darkTheme; + default: + return null; + } + } +} \ No newline at end of file diff --git a/lib/utils/mobx.dart b/lib/utils/mobx.dart index 7ae97d56f..a5bfb2220 100644 --- a/lib/utils/mobx.dart +++ b/lib/utils/mobx.dart @@ -57,7 +57,8 @@ extension MobxBindable on Box { Filter filter, }) { if (initialFire) { - dest.addAll(values); + final res = filter != null ? values.where(filter) : values; + dest.addAll(res); } return watch().listen((event) { diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 37f6e5247..401351f13 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -15,31 +15,34 @@ part 'balance_view_model.g.dart'; class BalanceViewModel = BalanceViewModelBase with _$BalanceViewModel; abstract class BalanceViewModelBase with Store { - BalanceViewModelBase({ - @required this.appStore, - @required this.settingsStore, - @required this.fiatConvertationStore - }) : isReversing = false; + BalanceViewModelBase( + {@required this.appStore, + @required this.settingsStore, + @required this.fiatConvertationStore}) + : isReversing = false; final AppStore appStore; final SettingsStore settingsStore; final FiatConversionStore fiatConvertationStore; + bool get canReverse => + (appStore.wallet.balance.availableModes as List).length > 1; + @observable bool isReversing; + @computed + double get price => fiatConvertationStore.prices[appStore.wallet.currency]; + @computed BalanceDisplayMode get savedDisplayMode => settingsStore.balanceDisplayMode; @computed BalanceDisplayMode get displayMode => isReversing - ? (savedDisplayMode == BalanceDisplayMode.availableBalance - ? BalanceDisplayMode.fullBalance - : BalanceDisplayMode.availableBalance) - : savedDisplayMode; - - @computed - double get price => fiatConvertationStore.price; + ? (savedDisplayMode == BalanceDisplayMode.availableBalance + ? BalanceDisplayMode.fullBalance + : BalanceDisplayMode.availableBalance) + : savedDisplayMode; @computed String get cryptoBalance { @@ -63,15 +66,11 @@ abstract class BalanceViewModelBase with Store { final fiatCurrency = settingsStore.fiatCurrency; var balance = '---'; - final totalBalance = _getFiatBalance( - price: price, - cryptoAmount: walletBalance.totalBalance - ); + final totalBalance = + _getFiatBalance(price: price, cryptoAmount: walletBalance.totalBalance); final unlockedBalance = _getFiatBalance( - price: price, - cryptoAmount: walletBalance.unlockedBalance - ); + price: price, cryptoAmount: walletBalance.unlockedBalance); if (displayMode == BalanceDisplayMode.availableBalance) { balance = fiatCurrency.toString() + ' ' + unlockedBalance ?? '0.00'; @@ -89,14 +88,14 @@ abstract class BalanceViewModelBase with Store { final _wallet = appStore.wallet; if (_wallet is MoneroWallet) { - return WalletBalance( + return WalletBalance( unlockedBalance: _wallet.balance.formattedUnlockedBalance, totalBalance: _wallet.balance.formattedFullBalance); } if (_wallet is BitcoinWallet) { return WalletBalance( - unlockedBalance: _wallet.balance.totalFormatted, + unlockedBalance: _wallet.balance.availableBalanceFormatted, totalBalance: _wallet.balance.totalFormatted); } @@ -113,4 +112,4 @@ abstract class BalanceViewModelBase with Store { return calculateFiatAmount(price: price, cryptoAmount: cryptoAmount); } -} \ No newline at end of file +} diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index e76a663ec..420586997 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -1,5 +1,10 @@ import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; +import 'package:cake_wallet/entities/transaction_history.dart'; +import 'package:cake_wallet/monero/account.dart'; +import 'package:cake_wallet/monero/monero_balance.dart'; +import 'package:cake_wallet/monero/monero_transaction_history.dart'; +import 'package:cake_wallet/monero/monero_transaction_info.dart'; import 'package:cake_wallet/monero/monero_wallet.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/crypto_currency.dart'; @@ -74,28 +79,53 @@ abstract class DashboardViewModelBase with Store { wallet ??= appStore.wallet; type = wallet.type; - transactions = ObservableList.of(wallet - .transactionHistory.transactions.values - .map((transaction) => TransactionListItem( - transaction: transaction, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore))); - _reaction = reaction((_) => appStore.wallet, _onWalletChange); - // FIXME: fixme + + final _wallet = wallet; + + if (_wallet is MoneroWallet) { + subname = _wallet.account?.label; + + _onMoneroAccountChangeReaction = reaction((_) => _wallet.account, + (Account account) => _onMoneroAccountChange(_wallet)); + + _onMoneroBalanceChangeReaction = reaction((_) => _wallet.balance, + (MoneroBalance balance) => _onMoneroTransactionsUpdate(_wallet)); + + final _accountTransactions = _wallet + .transactionHistory.transactions.values + .where((tx) => tx.accountIndex == _wallet.account.id) + .toList(); + + transactions = ObservableList.of(_accountTransactions.map((transaction) => + TransactionListItem( + transaction: transaction, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore))); + } else { + transactions = ObservableList.of(wallet + .transactionHistory.transactions.values + .map((transaction) => TransactionListItem( + transaction: transaction, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore))); + } + connectMapToListWithTransform( appStore.wallet.transactionHistory.transactions, transactions, (TransactionInfo val) => TransactionListItem( transaction: val, balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore)); + settingsStore: appStore.settingsStore), + filter: (TransactionInfo tx) { + final wallet = _wallet; + if (tx is MoneroTransactionInfo && wallet is MoneroWallet) { + return tx.accountIndex == wallet.account.id; + } - final _wallet = wallet; - - if (_wallet is MoneroWallet) { - subname = _wallet.account?.label; - } + return true; + }); } @observable @@ -156,6 +186,8 @@ abstract class DashboardViewModelBase with Store { @observable WalletBase wallet; + bool get hasRescan => wallet.type == WalletType.monero; + BalanceViewModel balanceViewModel; AppStore appStore; @@ -170,6 +202,10 @@ abstract class DashboardViewModelBase with Store { ReactionDisposer _reaction; + ReactionDisposer _onMoneroAccountChangeReaction; + + ReactionDisposer _onMoneroBalanceChangeReaction; + Future reconnect() async { final node = appStore.settingsStore.getCurrentNode(wallet.type); await wallet.connectToNode(node: node); @@ -178,10 +214,64 @@ abstract class DashboardViewModelBase with Store { @action void _onWalletChange(WalletBase wallet) { this.wallet = wallet; + type = wallet.type; name = wallet.name; + + if (wallet is MoneroWallet) { + subname = wallet.account?.label; + + _onMoneroAccountChangeReaction?.reaction?.dispose(); + _onMoneroBalanceChangeReaction?.reaction?.dispose(); + + _onMoneroAccountChangeReaction = reaction((_) => wallet.account, + (Account account) => _onMoneroAccountChange(wallet)); + + _onMoneroBalanceChangeReaction = reaction((_) => wallet.balance, + (MoneroBalance balance) => _onMoneroTransactionsUpdate(wallet)); + + _onMoneroTransactionsUpdate(wallet); + } else { + transactions.clear(); + + transactions.addAll(wallet.transactionHistory.transactions.values.map( + (transaction) => TransactionListItem( + transaction: transaction, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore))); + } + + connectMapToListWithTransform( + appStore.wallet.transactionHistory.transactions, + transactions, + (TransactionInfo val) => TransactionListItem( + transaction: val, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore), + filter: (TransactionInfo tx) { + if (tx is MoneroTransactionInfo && wallet is MoneroWallet) { + return tx.accountIndex == wallet.account.id; + } + + return true; + }); + } + + @action + void _onMoneroAccountChange(MoneroWallet wallet) { + subname = wallet.account?.label; + _onMoneroTransactionsUpdate(wallet); + } + + @action + void _onMoneroTransactionsUpdate(MoneroWallet wallet) { transactions.clear(); - transactions.addAll(wallet.transactionHistory.transactions.values.map( - (transaction) => TransactionListItem( + + final _accountTransactions = wallet.transactionHistory.transactions.values + .where((tx) => tx.accountIndex == wallet.account.id) + .toList(); + + transactions.addAll(_accountTransactions.map((transaction) => + TransactionListItem( transaction: transaction, balanceViewModel: balanceViewModel, settingsStore: appStore.settingsStore))); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 1b14980dd..e533e1e93 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -174,7 +174,7 @@ abstract class ExchangeViewModelBase with Store { return; } - final _amount = double.parse(amount.replaceAll(',', '.')); + final _amount = double.parse(amount.replaceAll(',', '.')) ?? 0; provider .calculateAmount( from: depositCurrency, @@ -247,10 +247,12 @@ abstract class ExchangeViewModelBase with Store { if (limitsState is LimitsLoadedSuccessfully && amount != null) { if (double.parse(amount) < limits.min) { tradeState = TradeIsCreatedFailure( + title: provider.title, error: S.current.error_text_minimal_limit('${provider.description}', '${limits.min}', currency.toString())); } else if (limits.max != null && double.parse(amount) > limits.max) { tradeState = TradeIsCreatedFailure( + title: provider.title, error: S.current.error_text_maximum_limit('${provider.description}', '${limits.max}', currency.toString())); } else { @@ -262,11 +264,14 @@ abstract class ExchangeViewModelBase with Store { await trades.add(trade); tradeState = TradeIsCreatedSuccessfully(trade: trade); } catch (e) { - tradeState = TradeIsCreatedFailure(error: e.toString()); + tradeState = TradeIsCreatedFailure( + title: provider.title, + error: e.toString()); } } } else { tradeState = TradeIsCreatedFailure( + title: provider.title, error: S.current .error_text_limits_loading_failed('${provider.description}')); } diff --git a/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart b/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart index 33e9dd1e5..d32f8eadd 100644 --- a/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart +++ b/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart @@ -13,6 +13,7 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store { {AccountListItem accountListItem}) : state = InitialExecutionState(), isEdit = accountListItem != null, + label = accountListItem?.label??'', _accountListItem = accountListItem; final bool isEdit; diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index fb6cd195e..0a5e07266 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -12,7 +12,9 @@ class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase abstract class NodeCreateOrEditViewModelBase with Store { NodeCreateOrEditViewModelBase(this._nodeSource, this._wallet) - : state = InitialExecutionState(); + : state = InitialExecutionState(), + connectionState = InitialExecutionState(), + useSSL = false; @observable ExecutionState state; @@ -29,6 +31,12 @@ abstract class NodeCreateOrEditViewModelBase with Store { @observable String password; + @observable + ExecutionState connectionState; + + @observable + bool useSSL; + @computed bool get isReady => (address?.isNotEmpty ?? false) && (port?.isNotEmpty ?? false); @@ -54,6 +62,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { port = ''; login = ''; password = ''; + useSSL = false; } @action @@ -61,11 +70,25 @@ abstract class NodeCreateOrEditViewModelBase with Store { try { state = IsExecutingState(); final node = - Node(uri: uri, type: _wallet.type, login: login, password: password); + Node(uri: uri, type: _wallet.type, login: login, password: password, + useSSL: useSSL); await _nodeSource.add(node); state = ExecutedSuccessfullyState(); } catch (e) { state = FailureState(e.toString()); } } + + @action + Future connect() async { + try { + connectionState = IsExecutingState(); + final node = + Node(uri: uri, type: _wallet.type, login: login, password: password); + final isAlive = await node.requestNode(); + connectionState = ExecutedSuccessfullyState(payload: isAlive); + } catch (e) { + connectionState = FailureState(e.toString()); + } + } } diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index 3bb1d09ba..9cf800705 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -13,16 +13,18 @@ part 'node_list_view_model.g.dart'; class NodeListViewModel = NodeListViewModelBase with _$NodeListViewModel; abstract class NodeListViewModelBase with Store { - NodeListViewModelBase(this._nodeSource, this._wallet, this.settingsStore) + NodeListViewModelBase(this._nodeSource, this.wallet, this.settingsStore) : nodes = ObservableList() { _nodeSource.bindToList(nodes, - filter: (Node val) => val?.type == _wallet.type, initialFire: true); + filter: (Node val) => val?.type == wallet.type, initialFire: true); } + @computed + Node get currentNode => settingsStore.nodes[wallet.type]; + final ObservableList nodes; final SettingsStore settingsStore; - - final WalletBase _wallet; + final WalletBase wallet; final Box _nodeSource; Future reset() async { @@ -30,14 +32,12 @@ abstract class NodeListViewModelBase with Store { Node node; - switch (_wallet.type) { + switch (wallet.type) { case WalletType.bitcoin: node = getBitcoinDefaultElectrumServer(nodes: _nodeSource); break; case WalletType.monero: - node = getMoneroDefaultNode( - nodes: _nodeSource, - ); + node = getMoneroDefaultNode(nodes: _nodeSource); break; default: break; @@ -50,5 +50,5 @@ abstract class NodeListViewModelBase with Store { Future delete(Node node) async => node.delete(); Future setAsCurrent(Node node) async => - settingsStore.currentNode = node; + settingsStore.nodes[wallet.type] = node; } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 483d40131..f3d46ccb3 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:hive/hive.dart'; import 'package:intl/intl.dart'; @@ -77,19 +78,9 @@ abstract class SendViewModelBase with Store { PendingTransaction pendingTransaction; @computed - String get balance { - String balance = '0.0'; - - if (_wallet is MoneroWallet) { - balance = _wallet.balance.formattedUnlockedBalance as String ?? ''; - } - - if (_wallet is BitcoinWallet) { - balance = _wallet.balance.confirmedFormatted as String ?? ''; - } - - return balance; - } + String get balance => + _wallet.balance.formattedBalance(BalanceDisplayMode.availableBalance) + as String ?? '0.0'; @computed bool get isReadyForSend => _wallet.syncStatus is SyncedSyncStatus; @@ -97,6 +88,7 @@ abstract class SendViewModelBase with Store { @computed ObservableList