From a79a6dca32eb9d184a9c748238534ffe7fb4fb2b Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer Date: Tue, 24 May 2022 14:22:23 -0500 Subject: [PATCH 01/22] Update readme Add Yats, templates --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b11cb1c10..b68d624d4 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,11 @@ * Select your own custom nodes/servers * Address book * Backup to external location or iCloud -* OpenAlias and Unstoppable Domains +* Send to OpenAlias, Unstoppable Domains, and Yats * Set custom fee levels * Store local transaction notes * Extremely simple user experience +* Convenient exchange and sending templates for recurring payments ### Monero Specific Features From 4e4c19d3e30b8b71fe2d408da9566e6130048340 Mon Sep 17 00:00:00 2001 From: Serhii-Borodenko Date: Wed, 1 Jun 2022 14:12:09 +0300 Subject: [PATCH 02/22] added validation of hvx,hvi,hvs Haven addresses --- lib/core/address_validator.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 8e87c9930..fec250a08 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -47,7 +47,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.xrp: return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$'; case CryptoCurrency.xhv: - case CryptoCurrency.xhv: + return '^hvx|hvi|hvs[0-9a-zA-Z]'; case CryptoCurrency.xag: case CryptoCurrency.xau: case CryptoCurrency.xaud: @@ -102,7 +102,6 @@ class AddressValidator extends TextValidator { case CryptoCurrency.xrp: return null; case CryptoCurrency.xhv: - case CryptoCurrency.xhv: case CryptoCurrency.xag: case CryptoCurrency.xau: case CryptoCurrency.xaud: From cb45ff6ecc32b7795effc3e0749b561771985392 Mon Sep 17 00:00:00 2001 From: Serhii-Borodenko Date: Wed, 8 Jun 2022 11:17:03 +0300 Subject: [PATCH 03/22] added node request for Haven --- cw_core/lib/node.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 2560a72d7..7de2e84b6 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -88,6 +88,8 @@ class Node extends HiveObject with Keyable { return requestElectrumServer(); case WalletType.litecoin: return requestElectrumServer(); + case WalletType.haven: + return requestMoneroNode(); default: return false; } From 997e960a0b924a0b5f9dbe7b5077bfdf9d570c77 Mon Sep 17 00:00:00 2001 From: Serhii-Borodenko Date: Fri, 10 Jun 2022 17:15:53 +0300 Subject: [PATCH 04/22] remove restore from date for haven restore form --- lib/src/screens/restore/wallet_restore_from_keys_form.dart | 6 +++++- lib/src/screens/restore/wallet_restore_page.dart | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index 3f8d122d1..d6f637422 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/services.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -10,10 +12,11 @@ import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/entities/generate_name.dart'; class WalletRestoreFromKeysFrom extends StatefulWidget { - WalletRestoreFromKeysFrom({Key key, this.onHeightOrDateEntered}) + WalletRestoreFromKeysFrom({Key key, this.onHeightOrDateEntered, this.walletRestoreViewModel}) : super(key: key); final Function(bool) onHeightOrDateEntered; + final WalletRestoreViewModel walletRestoreViewModel; @override WalletRestoreFromKeysFromState createState() => @@ -113,6 +116,7 @@ class WalletRestoreFromKeysFromState extends State { maxLines: null)), BlockchainHeightWidget( key: blockchainHeightKey, + hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven, onHeightChange: (_) => null, onHeightOrDateEntered: widget.onHeightOrDateEntered) ]), diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 65b6c03ec..708d3cc81 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -83,6 +83,7 @@ class WalletRestorePage extends BasePage { case WalletRestoreMode.keys: _pages.add(WalletRestoreFromKeysFrom( key: walletRestoreFromKeysFormKey, + walletRestoreViewModel: walletRestoreViewModel, onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value)); break; From e6fe4676caf8d4c1a7e723d1733c4bad5041fb75 Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 13 Jun 2022 12:32:15 +0300 Subject: [PATCH 05/22] fix haven transaction info (#373) * fix haven transaction info * fix format --- lib/haven/cw_haven.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/haven/cw_haven.dart b/lib/haven/cw_haven.dart index 8e9325bb9..c2e4ba68b 100644 --- a/lib/haven/cw_haven.dart +++ b/lib/haven/cw_haven.dart @@ -297,9 +297,9 @@ class CWHaven extends Haven { } CryptoCurrency assetOfTransaction(TransactionInfo tx) { - final tx = transaction as HavenTransactionInfo; - final asset = CryptoCurrency.fromString(tx.assetType); - return asset; + final transaction = tx as HavenTransactionInfo; + final asset = CryptoCurrency.fromString(transaction.assetType); + return asset; } List getAssetRate() From fc58972bf90ace1aceaa782c433a574f82fc2189 Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 13 Jun 2022 14:41:46 +0300 Subject: [PATCH 06/22] Added ability sending to FIO address (#370) * Added ability sending to FIO address * remove apiKey property * fix conditions for FIO address checking --- lib/entities/fio_address_provider.dart | 57 +++++++++++++++++++ lib/entities/parse_address_from_domain.dart | 9 +++ lib/entities/parsed_address.dart | 11 +++- .../widgets/extract_address_from_parsed.dart | 5 ++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 lib/entities/fio_address_provider.dart diff --git a/lib/entities/fio_address_provider.dart b/lib/entities/fio_address_provider.dart new file mode 100644 index 000000000..435be3f8f --- /dev/null +++ b/lib/entities/fio_address_provider.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +class FioAddressProvider { + static const apiAuthority = 'fio.blockpane.com'; + static const availCheck = '/v1/chain/avail_check'; + static const getAddress = '/v1/chain/get_pub_address'; + + static Future checkAvail(String fioAddress) async { + bool isFioRegistered = false; + final headers = {'Content-Type': 'application/json'}; + final body = {"fio_name": fioAddress}; + + final uri = Uri.https(apiAuthority, availCheck); + final response = + await http.post(uri, headers: headers, body: json.encode(body)); + + if (response.statusCode != 200) { + return isFioRegistered; + } + + final responseJSON = json.decode(response.body) as Map; + isFioRegistered = responseJSON['is_registered'] as int == 1; + + return isFioRegistered; + } + + static Future getPubAddress(String fioAddress, String token) async { + final headers = {'Content-Type': 'application/json'}; + final body = { + "fio_address": fioAddress, + "chain_code": token.toUpperCase(), + "token_code": token.toUpperCase(), + }; + + final uri = Uri.https(apiAuthority, getAddress); + final response = + await http.post(uri, headers: headers, body: json.encode(body)); + + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['error'] as String; + final message = responseJSON['message'] as String; + throw Exception('${error}\n$message'); + } + + if (response.statusCode != 200) { + return null; + } + + final responseJSON = json.decode(response.body) as Map; + final String pubAddress = responseJSON['public_address'] as String; + + return pubAddress; + } +} diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 76459d0e0..b074eb7f7 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/emoji_string_extension.dart'; import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/entities/fio_address_provider.dart'; class AddressResolver { @@ -26,6 +27,14 @@ class AddressResolver { Future resolve(String text, String ticker) async { try { + if (text.contains('@') && !text.contains('.')) { + final bool isFioRegistered = await FioAddressProvider.checkAvail(text); + if (isFioRegistered) { + final address = await FioAddressProvider.getPubAddress(text, ticker); + return ParsedAddress.fetchFioAddress(address: address, name: text); + } + + } if (text.hasOnlyEmojis) { final addresses = await yatService.fetchYatAddress(text, ticker); return ParsedAddress.fetchEmojiAddress(addresses: addresses, name: text); diff --git a/lib/entities/parsed_address.dart b/lib/entities/parsed_address.dart index 2905ce89f..3ddef1910 100644 --- a/lib/entities/parsed_address.dart +++ b/lib/entities/parsed_address.dart @@ -2,7 +2,7 @@ import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/yat_record.dart'; import 'package:flutter/material.dart'; -enum ParseFrom { unstoppableDomains, openAlias, yatRecord, notParsed } +enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed } class ParsedAddress { ParsedAddress({ @@ -58,4 +58,13 @@ class ParsedAddress { parseFrom: ParseFrom.openAlias, ); } + + factory ParsedAddress.fetchFioAddress({@required String address, @required String name}){ + + return ParsedAddress( + addresses: [address], + name: name, + parseFrom: ParseFrom.fio, + ); + } } diff --git a/lib/src/screens/send/widgets/extract_address_from_parsed.dart b/lib/src/screens/send/widgets/extract_address_from_parsed.dart index 75d434369..9d20c39d3 100644 --- a/lib/src/screens/send/widgets/extract_address_from_parsed.dart +++ b/lib/src/screens/send/widgets/extract_address_from_parsed.dart @@ -23,6 +23,11 @@ Future extractAddressFromParsed( content = S.of(context).openalias_alert_content(parsedAddress.name); address = parsedAddress.addresses.first; break; + case ParseFrom.fio: + title = S.of(context).address_detected; + content = S.of(context).openalias_alert_content(parsedAddress.name); + address = parsedAddress.addresses.first; + break; case ParseFrom.yatRecord: if (parsedAddress.name.isEmpty) { title = S.of(context).yat_error; From 5a089b5893bd06fe1043ab0058852eeb7e073a47 Mon Sep 17 00:00:00 2001 From: OmarHatem28 Date: Sat, 18 Jun 2022 17:10:07 +0200 Subject: [PATCH 07/22] Add Haven to welcome screen text --- res/values/strings_de.arb | 2 +- res/values/strings_en.arb | 2 +- res/values/strings_es.arb | 2 +- res/values/strings_fr.arb | 2 +- res/values/strings_hi.arb | 2 +- res/values/strings_hr.arb | 2 +- res/values/strings_it.arb | 2 +- res/values/strings_ja.arb | 2 +- res/values/strings_ko.arb | 2 +- res/values/strings_nl.arb | 2 +- res/values/strings_pl.arb | 2 +- res/values/strings_pt.arb | 2 +- res/values/strings_ru.arb | 2 +- res/values/strings_uk.arb | 2 +- res/values/strings_zh.arb | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index a501cd72e..9d1cd281c 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -1,7 +1,7 @@ { "welcome" : "Willkommen bei", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Eine großartige Wallet für Monero, Bitcoin und Litecoin", + "first_wallet_text" : "Eine großartige Wallet für Monero, Bitcoin, Litecoin, und Haven", "please_make_selection" : "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.", "create_new" : "Neue Wallet erstellen", "restore_wallet" : "Wallet wiederherstellen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index dfe3b84f3..daa3d9d66 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -1,7 +1,7 @@ { "welcome" : "Welcome to", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Awesome wallet for Monero, Bitcoin and Litecoin", + "first_wallet_text" : "Awesome wallet for Monero, Bitcoin, Litecoin, and Haven", "please_make_selection" : "Please make a selection below to create or recover your wallet.", "create_new" : "Create New Wallet", "restore_wallet" : "Restore Wallet", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index a033025f3..d9cfd2c4a 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -1,7 +1,7 @@ { "welcome" : "Bienvenido", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Impresionante billetera para Monero, Bitcoin y Litecoin", + "first_wallet_text" : "Impresionante billetera para Monero, Bitcoin, Litecoin, y Haven", "please_make_selection" : "Seleccione a continuación para crear o recuperar su billetera.", "create_new" : "Crear nueva billetera", "restore_wallet" : "Restaurar billetera", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 9a3ef3cb3..6e728d7a1 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -1,7 +1,7 @@ { "welcome" : "Bienvenue sur", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Super portefeuille pour Monero, Bitcoin et Litecoin", + "first_wallet_text" : "Super portefeuille pour Monero, Bitcoin, Litecoin, et Haven", "please_make_selection" : "Merci de faire un choix ci-dessous pour créer ou restaurer votre portefeuille.", "create_new" : "Créer un Nouveau Portefeuille", "restore_wallet" : "Restaurer un Portefeuille", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 4efe457c3..34b90b9aa 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -1,7 +1,7 @@ { "welcome" : "स्वागत हे सेवा मेरे", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Monero, Bitcoin और Litecoin के लिए बहुत बढ़िया बटुआ", + "first_wallet_text" : "Monero, Bitcoin, Litecoin, और Haven के लिए बहुत बढ़िया बटुआ", "please_make_selection" : "कृपया नीचे चयन करें अपना बटुआ बनाएं या पुनर्प्राप्त करें.", "create_new" : "नया बटुआ बनाएँ", "restore_wallet" : "वॉलेट को पुनर्स्थापित करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 95004d689..39fc60b1f 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -1,7 +1,7 @@ { "welcome" : "Dobrodošli na", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Odličan novčanik za Monero, Bitcoin i Litecoin", + "first_wallet_text" : "Odličan novčanik za Monero, Bitcoin, Litecoin, i Haven", "please_make_selection" : "Molimo odaberite opcije niže za izradu novog novčanika ili za oporavak postojećeg.", "create_new" : "Izradi novi novčanik", "restore_wallet" : "Oporavi novčanik", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index a65078b3e..3b208aa33 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -1,7 +1,7 @@ { "welcome" : "Benvenuto", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Portafoglio fantastico per Monero, Bitcoin e Litecoin", + "first_wallet_text" : "Portafoglio fantastico per Monero, Bitcoin, Litecoin, e Haven", "please_make_selection" : "Gentilmente seleziona se vuoi generare o recuperare il tuo portafoglio.", "create_new" : "Genera nuovo Portafoglio", "restore_wallet" : "Recupera Portafoglio", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 5cb3039c1..5639e68b7 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -1,7 +1,7 @@ { "welcome" : "ようこそ に", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Monero、Bitcoin、Litecoin用の素晴らしいウォレット", + "first_wallet_text" : "Monero、Bitcoin、Litecoin、Haven用の素晴らしいウォレット", "please_make_selection" : "以下を選択してください ウォレットを作成または回復する.", "create_new" : "新しいウォレットを作成", "restore_wallet" : "ウォレットを復元", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index db099be63..50da9147e 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -1,7 +1,7 @@ { "welcome" : "환영 에", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Monero, Bitcoin 및 Litecoin을 위한 멋진 지갑", + "first_wallet_text" : "Monero, Bitcoin, Litecoin 및 Haven을 위한 멋진 지갑", "please_make_selection" : "아래에서 선택하십시오 지갑 만들기 또는 복구.", "create_new" : "새 월렛 만들기", "restore_wallet" : "월렛 복원", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 207979462..6f2343b53 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -1,7 +1,7 @@ { "welcome" : "Welkom bij", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Geweldige portemonnee voor Monero, Bitcoin en Litecoin", + "first_wallet_text" : "Geweldige portemonnee voor Monero, Bitcoin, Litecoin, en Haven", "please_make_selection" : "Maak hieronder uw keuze tot maak of herstel je portemonnee.", "create_new" : "Maak een nieuwe portemonnee", "restore_wallet" : "Portemonnee herstellen", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 3da2cc94d..93199a569 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -1,7 +1,7 @@ { "welcome" : "Witamy w", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Świetny portfel na Monero, Bitcoin i Litecoin", + "first_wallet_text" : "Świetny portfel na Monero, Bitcoin, Litecoin, i Haven", "please_make_selection" : "Wybierz poniżej, aby cutwórz lub odzyskaj swój portfel.", "create_new" : "Utwórz nowy portfel", "restore_wallet" : "Przywróć portfel", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index d32e47bd0..66dd01df4 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -1,7 +1,7 @@ { "welcome" : "Bem-vindo ao", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Carteira incrível para Monero, Bitcoin e Litecoin", + "first_wallet_text" : "Carteira incrível para Monero, Bitcoin, Litecoin, e Haven", "please_make_selection" : "Escolha se quer criar uma carteira nova ou restaurar uma antiga.", "create_new" : "Criar nova carteira", "restore_wallet" : "Restaurar carteira", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 78c67943a..3cc6b7518 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -1,7 +1,7 @@ { "welcome" : "Приветствуем в", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "В самом удобном кошельке для Monero, Bitcoin и Litecoin", + "first_wallet_text" : "В самом удобном кошельке для Monero, Bitcoin, Litecoin, и Haven", "please_make_selection" : "Выберите способ создания кошелька: создать новый или восстановить ваш существующий.", "create_new" : "Создать новый кошелёк", "restore_wallet" : "Восстановить кошелёк", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index c2a96ab49..cba4fca95 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -1,7 +1,7 @@ { "welcome" : "Вітаємо в", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "В самому зручному гаманці для Monero, Bitcoin та Litecoin", + "first_wallet_text" : "В самому зручному гаманці для Monero, Bitcoin, Litecoin, та Haven", "please_make_selection" : "Оберіть спосіб створення гаманця: створити новий чи відновити ваш існуючий.", "create_new" : "Створити новий гаманець", "restore_wallet" : "Відновити гаманець", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 43132041a..79885392b 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -1,7 +1,7 @@ { "welcome" : "欢迎使用", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "门罗币、比特币和莱特币的超棒钱包", + "first_wallet_text" : "门罗币、比特币、莱特币和避风港的超棒钱包", "please_make_selection" : "请在下面进行选择 创建或恢复您的钱包.", "create_new" : "创建新钱包", "restore_wallet" : "恢复钱包", From 50740333713b59a0b581687e013e286488b2ee17 Mon Sep 17 00:00:00 2001 From: OmarHatem28 Date: Sun, 19 Jun 2022 00:51:43 +0200 Subject: [PATCH 08/22] Add paste option to address text field in new contact screen --- lib/src/screens/contact/contact_page.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/screens/contact/contact_page.dart b/lib/src/screens/contact/contact_page.dart index 3a140b707..6ac236352 100644 --- a/lib/src/screens/contact/contact_page.dart +++ b/lib/src/screens/contact/contact_page.dart @@ -94,7 +94,10 @@ class ContactPage extends BasePage { child: Observer( builder: (_) => AddressTextField( controller: _addressController, - options: [AddressTextFieldOption.qrCode], + options: [ + AddressTextFieldOption.paste, + AddressTextFieldOption.qrCode, + ], buttonColor: Theme.of(context).accentTextTheme.display2.color, iconColor: PaletteDark.gray, borderColor: Theme.of(context).primaryTextTheme.title.backgroundColor, From 06f21e80ec1d4019e04f7db186c5220007b06247 Mon Sep 17 00:00:00 2001 From: OmarHatem28 Date: Mon, 20 Jun 2022 16:18:25 +0200 Subject: [PATCH 09/22] Save Haven current node --- lib/store/settings_store.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 373ba0aca..d1b6d69ed 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -314,6 +314,10 @@ abstract class SettingsStoreBase with Store { await _sharedPreferences.setInt( PreferencesKey.currentNodeIdKey, node.key as int); break; + case WalletType.haven: + await _sharedPreferences.setInt( + PreferencesKey.currentHavenNodeIdKey, node.key as int); + break; default: break; } From 9568c5f9326b3453cb97fb0b5a6caf9abd382617 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Thu, 23 Jun 2022 11:25:00 +0200 Subject: [PATCH 10/22] QR code fullscreen (#390) * Add fullscreen qr page flow * Add qr fullscreen text localization * Add package to control screen brightness when navigating to qr fullscreen and revert when leaving screen * ios fix trial 1 - Change brightness control package * Use package imports instead of relevant path Add Map types --- android/app/src/main/AndroidManifestBase.xml | 1 + lib/di.dart | 7 +- lib/router.dart | 22 +- lib/routes.dart | 1 + .../screens/receive/fullscreen_qr_page.dart | 85 +++++++ .../screens/receive/widgets/qr_widget.dart | 215 ++++++++++-------- pubspec_base.yaml | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 4 +- res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_zh.arb | 1 + 22 files changed, 246 insertions(+), 104 deletions(-) create mode 100644 lib/src/screens/receive/fullscreen_qr_page.dart diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 5d0c41846..f43b0369b 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -5,6 +5,7 @@ + YatService()); getIt.registerFactory(() => AddressResolver(yatService: getIt.get())); - + + getIt.registerFactoryParam( + (String qrData, bool isLight) => FullscreenQRPage(qrData: qrData, isLight: isLight,)); + _isSetupFinished = true; } diff --git a/lib/router.dart b/lib/router.dart index acfb18684..01443a138 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,6 +1,5 @@ import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/buy/order.dart'; -import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/src/screens/backup/backup_page.dart'; import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart'; import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart'; @@ -13,10 +12,7 @@ import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cake_wallet/view_model/buy/buy_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; -import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -26,7 +22,6 @@ import 'package:cake_wallet/utils/language_list.dart'; import 'package:cake_wallet/view_model/wallet_new_vm.dart'; import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart'; import 'package:cake_wallet/view_model/wallet_restoration_from_keys_vm.dart'; -import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -66,16 +61,15 @@ import 'package:cake_wallet/src/screens/exchange/exchange_template_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:flutter/services.dart'; -import 'package:hive/hive.dart'; -import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; +import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart'; RouteSettings currentRouteSettings; Route createRoute(RouteSettings settings) { currentRouteSettings = settings; - + switch (settings.name) { case Routes.welcome: return MaterialPageRoute(builder: (_) => createWelcomePage()); @@ -84,7 +78,7 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute( builder: (_) => getIt.get( param1: (PinCodeState context, dynamic _) { - if (availableWalletTypes.length == 1) { + if (availableWalletTypes.length == 1) { Navigator.of(context.context).pushNamed(Routes.newWallet, arguments: availableWalletTypes.first); } else { Navigator.of(context.context).pushNamed(Routes.newWalletType); @@ -402,6 +396,16 @@ Route createRoute(RouteSettings settings) { getIt.get( param1: args)); + case Routes.fullscreenQR: + final args = settings.arguments as Map; + + return MaterialPageRoute( + builder: (_) => + getIt.get( + param1: args['qrData'] as String, + param2: args['isLight'] as bool, + )); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index 23e236023..eea2b7488 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -60,4 +60,5 @@ class Routes { static const moneroRestoreWalletFromWelcome = '/monero_restore_wallet'; static const moneroNewWalletFromWelcome = '/monero_new_wallet'; static const addressPage = '/address_page'; + static const fullscreenQR = '/fullscreen_qr'; } \ No newline at end of file diff --git a/lib/src/screens/receive/fullscreen_qr_page.dart b/lib/src/screens/receive/fullscreen_qr_page.dart new file mode 100644 index 000000000..7ef87c749 --- /dev/null +++ b/lib/src/screens/receive/fullscreen_qr_page.dart @@ -0,0 +1,85 @@ +import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class FullscreenQRPage extends BasePage { + FullscreenQRPage({@required this.qrData, @required this.isLight}); + + final bool isLight; + final String qrData; + + @override + Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white; + + @override + Color get backgroundDarkColor => Colors.transparent; + + @override + bool get resizeToAvoidBottomInset => false; + + @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 Function(BuildContext, Widget) get rootWrapper => (BuildContext context, Widget scaffold) => Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).accentColor, + Theme.of(context).scaffoldBackgroundColor, + Theme.of(context).primaryColor, + ], + begin: Alignment.topRight, + end: Alignment.bottomLeft, + ), + ), + child: scaffold); + + @override + Widget body(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width * 0.05), + child: Hero( + tag: Key(qrData), + child: Center( + child: AspectRatio( + aspectRatio: 1.0, + child: Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + border: Border.all(width: 3, color: Theme.of(context).accentTextTheme.display3.backgroundColor)), + child: QrImage( + data: qrData, + backgroundColor: isLight ? Colors.transparent : Colors.black, + foregroundColor: Theme.of(context).accentTextTheme.display3.backgroundColor, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index 1bfbd6230..96691aa45 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -1,4 +1,6 @@ +import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:device_display_brightness/device_display_brightness.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; @@ -38,101 +40,134 @@ class QRWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row(children: [ - Spacer(flex: 3), - Observer( - builder: (_) => Flexible( - flex: 5, - child: Center( - child: AspectRatio( - aspectRatio: 1.0, - child: Container( - padding: EdgeInsets.all(5), - decoration: BoxDecoration( - border: Border.all( - width: 3, - color: Theme.of(context).accentTextTheme. - display3.backgroundColor - ) - ), - child: QrImage( - data: addressListViewModel.uri.toString(), - backgroundColor: isLight ? Colors.transparent : Colors.black, - foregroundColor: Theme.of(context).accentTextTheme. - display3.backgroundColor, - ), - ))))), - Spacer(flex: 3) - ]), - if (isAmountFieldShow) + Column( + children: [ Padding( - padding: EdgeInsets.only(top: 10), - child: Row( - children: [ - Expanded( - child: Form( - key: _formKey, - child: BaseTextFormField( - focusNode: amountTextFieldFocusNode, - controller: amountController, - keyboardType: TextInputType.numberWithOptions( - decimal: true), - inputFormatters: [ - BlacklistingTextInputFormatter( - RegExp('[\\-|\\ ]')) - ], - textAlign: TextAlign.center, - hintText: S.of(context).receive_amount, - textColor: Theme.of(context).accentTextTheme. - display3.backgroundColor, - borderColor: Theme.of(context) - .textTheme - .headline - .decorationColor, - validator: AmountValidator( - type: addressListViewModel.type, - isAutovalidate: true), - autovalidate: true, - placeholderTextStyle: TextStyle( - color: Theme.of(context).hoverColor, - fontSize: 18, - fontWeight: FontWeight.w500)))) - ], - ), + padding: const EdgeInsets.only(bottom: 12), + child: Text( + S.of(context).qr_fullscreen, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme.display3.backgroundColor), ), + ), + Row( + children: [ + Spacer(flex: 3), + Observer( + builder: (_) => Flexible( + flex: 5, + child: GestureDetector( + onTap: () async { + // Get the current brightness: + final double brightness = await DeviceDisplayBrightness.getBrightness(); + + // ignore: unawaited_futures + DeviceDisplayBrightness.setBrightness(1.0); + await Navigator.pushNamed( + context, + Routes.fullscreenQR, + arguments: { + 'qrData': addressListViewModel.uri.toString(), + 'isLight': isLight, + }, + ); + // ignore: unawaited_futures + DeviceDisplayBrightness.setBrightness(brightness); + }, + child: Hero( + tag: Key(addressListViewModel.uri.toString()), + child: Center( + child: AspectRatio( + aspectRatio: 1.0, + child: Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + border: Border.all( + width: 3, + color: Theme.of(context).accentTextTheme.display3.backgroundColor, + ), + ), + child: QrImage( + data: addressListViewModel.uri.toString(), + backgroundColor: isLight ? Colors.transparent : Colors.black, + foregroundColor: Theme.of(context).accentTextTheme.display3.backgroundColor, + ), + ), + ), + ), + ), + ), + ), + ), + Spacer(flex: 3) + ], + ), + ], + ), + if (isAmountFieldShow) + Padding( + padding: EdgeInsets.only(top: 10), + child: Row( + children: [ + Expanded( + child: Form( + key: _formKey, + child: BaseTextFormField( + focusNode: amountTextFieldFocusNode, + controller: amountController, + keyboardType: TextInputType.numberWithOptions(decimal: true), + inputFormatters: [BlacklistingTextInputFormatter(RegExp('[\\-|\\ ]'))], + textAlign: TextAlign.center, + hintText: S.of(context).receive_amount, + textColor: Theme.of(context).accentTextTheme.display3.backgroundColor, + borderColor: Theme.of(context).textTheme.headline.decorationColor, + validator: AmountValidator(type: addressListViewModel.type, isAutovalidate: true), + autovalidate: true, + placeholderTextStyle: TextStyle( + color: Theme.of(context).hoverColor, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ), Padding( padding: EdgeInsets.only(top: 8, bottom: 8), child: Builder( - builder: (context) => Observer( - builder: (context) => GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData( - text: addressListViewModel.address.address)); - showBar( - context, S.of(context).copied_to_clipboard); - }, - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - addressListViewModel.address.address, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - color: Theme.of(context).accentTextTheme. - display3.backgroundColor), - ), - ), - Padding( - padding: EdgeInsets.only(left: 12), - child: copyImage, - ) - ], - ), - ))), + builder: (context) => Observer( + builder: (context) => GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: addressListViewModel.address.address)); + showBar(context, S.of(context).copied_to_clipboard); + }, + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + addressListViewModel.address.address, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme.display3.backgroundColor), + ), + ), + Padding( + padding: EdgeInsets.only(left: 12), + child: copyImage, + ) + ], + ), + ), + ), + ), ) ], ); diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 62f129246..8d1c098ae 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -54,6 +54,7 @@ dependencies: file_picker: ^3.0.0-nullsafety.2 unorm_dart: ^0.2.0 permission_handler: ^5.0.1+1 + device_display_brightness: ^0.0.6 dev_dependencies: flutter_test: diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 9d1cd281c..fa34eeced 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -151,6 +151,7 @@ "subaddresses" : "Unteradressen", "addresses" : "Adressen", "scan_qr_code" : "Scannen Sie den QR-Code, um die Adresse zu erhalten", + "qr_fullscreen" : "Tippen Sie hier, um den QR-Code im Vollbildmodus zu öffnen", "rename" : "Umbenennen", "choose_account" : "Konto auswählen", "create_new_account" : "Neues Konto erstellen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index daa3d9d66..25c449929 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -151,6 +151,7 @@ "subaddresses" : "Subaddresses", "addresses" : "Addresses", "scan_qr_code" : "Scan the QR code to get the address", + "qr_fullscreen" : "Tap to open full screen QR code", "rename" : "Rename", "choose_account" : "Choose account", "create_new_account" : "Create new account", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index d9cfd2c4a..e520ff727 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -151,6 +151,7 @@ "subaddresses" : "Subdirecciones", "addresses" : "Direcciones", "scan_qr_code" : "Escanee el código QR para obtener la dirección", + "qr_fullscreen" : "Toque para abrir el código QR en pantalla completa", "rename" : "Rebautizar", "choose_account" : "Elegir cuenta", "create_new_account" : "Crear una nueva cuenta", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 6e728d7a1..af41bb225 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -149,6 +149,7 @@ "subaddresses" : "Sous-adresses", "addresses" : "Adresses", "scan_qr_code" : "Scannez le QR code pour obtenir l'adresse", + "qr_fullscreen" : "Appuyez pour ouvrir le code QR en plein écran", "rename" : "Renommer", "choose_account" : "Choisir le compte", "create_new_account" : "Créer un nouveau compte", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 34b90b9aa..9c61e180f 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -151,6 +151,7 @@ "subaddresses" : "उप पते", "addresses" : "पतों", "scan_qr_code" : "पता प्राप्त करने के लिए QR कोड स्कैन करें", + "qr_fullscreen" : "फ़ुल स्क्रीन क्यूआर कोड खोलने के लिए टैप करें", "rename" : "नाम बदलें", "choose_account" : "खाता चुनें", "create_new_account" : "नया खाता बनाएँ", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 39fc60b1f..16142ccd3 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -151,6 +151,7 @@ "subaddresses" : "Podadrese", "addresses" : "Adrese", "scan_qr_code" : "Skeniraj QR kod za dobivanje adrese", + "qr_fullscreen" : "Dodirnite za otvaranje QR koda preko cijelog zaslona", "rename" : "Preimenuj", "choose_account" : "Odaberi račun", "create_new_account" : "Izradi novi račun", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 3b208aa33..10c22001a 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -151,6 +151,7 @@ "subaddresses" : "Sottoindirizzi", "addresses" : "Indirizzi", "scan_qr_code" : "Scansiona il codice QR per ottenere l'indirizzo", + "qr_fullscreen" : "Tocca per aprire il codice QR a schermo intero", "rename" : "Rinomina", "choose_account" : "Scegli account", "create_new_account" : "Crea nuovo account", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 5639e68b7..3e2c3e886 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -151,6 +151,7 @@ "subaddresses" : "サブアドレス", "addresses" : "住所", "scan_qr_code" : "QRコードをスキャンして住所を取得します", + "qr_fullscreen" : "タップして全画面QRコードを開く", "rename" : "リネーム", "choose_account" : "アカウントを選択", "create_new_account" : "新しいアカウントを作成する", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 50da9147e..ad3e3ac5b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -151,6 +151,7 @@ "subaddresses" : "하위 주소", "addresses" : "구애", "scan_qr_code" : "QR 코드를 스캔하여 주소를 얻습니다.", + "qr_fullscreen" : "전체 화면 QR 코드를 열려면 탭하세요.", "rename" : "이름 바꾸기", "choose_account" : "계정을 선택하십시오", "create_new_account" : "새 계정을 만들", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 6f2343b53..834da2c02 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -152,6 +152,7 @@ "rename" : "Hernoemen", "addresses" : "Adressen", "scan_qr_code" : "Scan de QR-code om het adres te krijgen", + "qr_fullscreen" : "Tik om de QR-code op volledig scherm te openen", "choose_account" : "Kies account", "create_new_account" : "Creëer een nieuw account", "accounts_subaddresses" : "Accounts en subadressen", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 93199a569..6bcccb1e1 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -9,9 +9,6 @@ "monero_com": "Monero.com by Cake Wallet", "monero_com_wallet_text": "Awesome wallet for Monero", - "haven_app": "Haven by Cake Wallet", - "haven_app_wallet_text": "Awesome wallet for Haven", - "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", @@ -154,6 +151,7 @@ "subaddresses" : "Podadresy", "addresses" : "Adresy", "scan_qr_code" : "Zeskanuj kod QR, aby uzyskać adres", + "qr_fullscreen" : "Dotknij, aby otworzyć pełnoekranowy kod QR", "rename" : "Przemianować", "choose_account" : "Wybierz konto", "create_new_account" : "Stwórz nowe konto", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 66dd01df4..8737de6c0 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -151,6 +151,7 @@ "subaddresses" : "Sub-endereços", "addresses" : "Endereços", "scan_qr_code" : "Digitalize o código QR para obter o endereço", + "qr_fullscreen" : "Toque para abrir o código QR em tela cheia", "rename" : "Renomear", "choose_account" : "Escolha uma conta", "create_new_account" : "Criar nova conta", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 3cc6b7518..7df0463fb 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -151,6 +151,7 @@ "subaddresses" : "Субадреса", "addresses" : "Адреса", "scan_qr_code" : "Отсканируйте QR-код для получения адреса", + "qr_fullscreen" : "Нажмите, чтобы открыть полноэкранный QR-код", "rename" : "Переименовать", "choose_account" : "Выберите аккаунт", "create_new_account" : "Создать новый аккаунт", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index cba4fca95..2ef8cbb6b 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -151,6 +151,7 @@ "subaddresses" : "Субадреси", "addresses" : "Адреси", "scan_qr_code" : "Скануйте QR-код для одержання адреси", + "qr_fullscreen" : "Торкніться, щоб відкрити QR-код на весь екран", "rename" : "Перейменувати", "choose_account" : "Оберіть акаунт", "create_new_account" : "Створити новий акаунт", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 79885392b..f8632ee98 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -151,6 +151,7 @@ "subaddresses" : "子地址", "addresses" : "地址", "scan_qr_code" : "扫描二维码获取地址", + "qr_fullscreen" : "点击打开全屏二维码", "rename" : "重命名", "choose_account" : "选择账户", "create_new_account" : "建立新账户", From 8bcf70b8f845734fc9c9869270d70e082f123a4d Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Thu, 23 Jun 2022 15:36:50 +0200 Subject: [PATCH 11/22] Hide keys for BTC & LTC (#392) * Remove WIF and Keys from BTC and LTC wallets in the Wallet seed/keys screen * Move Title assigning logic to view model --- .../screens/wallet_keys/wallet_keys_page.dart | 11 ++---- lib/view_model/wallet_keys_view_model.dart | 39 +++++++------------ 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index 48b326d47..2070a773f 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -1,5 +1,4 @@ import 'package:cake_wallet/utils/show_bar.dart'; -import 'package:flushbar/flushbar.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; @@ -13,7 +12,7 @@ class WalletKeysPage extends BasePage { WalletKeysPage(this.walletKeysViewModel); @override - String get title => S.current.wallet_keys; + String get title => walletKeysViewModel.title; final WalletKeysViewModel walletKeysViewModel; @@ -27,10 +26,7 @@ class WalletKeysPage extends BasePage { separatorBuilder: (context, index) => Container( height: 1, padding: EdgeInsets.only(left: 24), - color: Theme.of(context) - .accentTextTheme - .title - .backgroundColor, + color: Theme.of(context).accentTextTheme.title.backgroundColor, child: Container( height: 1, color: Theme.of(context).dividerColor, @@ -43,8 +39,7 @@ class WalletKeysPage extends BasePage { return GestureDetector( onTap: () { Clipboard.setData(ClipboardData(text: item.value)); - showBar(context, - S.of(context).copied_key_to_clipboard(item.title)); + showBar(context, S.of(context).copied_key_to_clipboard(item.title)); }, child: StandartListRow( title: item.title + ':', diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 34f4ea2b6..a0794e1f3 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -2,7 +2,6 @@ import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cw_core/wallet_base.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; @@ -13,19 +12,18 @@ class WalletKeysViewModel = WalletKeysViewModelBase with _$WalletKeysViewModel; abstract class WalletKeysViewModelBase with Store { WalletKeysViewModelBase(WalletBase wallet) - : items = ObservableList() { + : title = wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin + ? S.current.wallet_seed + : S.current.wallet_keys, + items = ObservableList() { if (wallet.type == WalletType.monero) { final keys = monero.getKeys(wallet); items.addAll([ - StandartListItem( - title: S.current.spend_key_public, value: keys['publicSpendKey']), - StandartListItem( - title: S.current.spend_key_private, value: keys['privateSpendKey']), - StandartListItem( - title: S.current.view_key_public, value: keys['publicViewKey']), - StandartListItem( - title: S.current.view_key_private, value: keys['privateViewKey']), + StandartListItem(title: S.current.spend_key_public, value: keys['publicSpendKey']), + StandartListItem(title: S.current.spend_key_private, value: keys['privateSpendKey']), + StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']), + StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']), StandartListItem(title: S.current.wallet_seed, value: wallet.seed), ]); } @@ -34,29 +32,22 @@ abstract class WalletKeysViewModelBase with Store { final keys = haven.getKeys(wallet); items.addAll([ - StandartListItem( - title: S.current.spend_key_public, value: keys['publicSpendKey']), - StandartListItem( - title: S.current.spend_key_private, value: keys['privateSpendKey']), - StandartListItem( - title: S.current.view_key_public, value: keys['publicViewKey']), - StandartListItem( - title: S.current.view_key_private, value: keys['privateViewKey']), + StandartListItem(title: S.current.spend_key_public, value: keys['publicSpendKey']), + StandartListItem(title: S.current.spend_key_private, value: keys['privateSpendKey']), + StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']), + StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']), StandartListItem(title: S.current.wallet_seed, value: wallet.seed), ]); } if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) { - final keys = bitcoin.getWalletKeys(wallet); - items.addAll([ - StandartListItem(title: 'WIF', value: keys['wif']), - StandartListItem(title: S.current.public_key, value: keys['publicKey']), - StandartListItem(title: S.current.private_key, value: keys['privateKey']), - StandartListItem(title: S.current.wallet_seed, value: wallet.seed) + StandartListItem(title: S.current.wallet_seed, value: wallet.seed), ]); } } final ObservableList items; + + final String title; } From 9b9e211c081e3b13dccafc1bbc9214f84c076da2 Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 28 Jun 2022 18:46:32 +0300 Subject: [PATCH 12/22] cancel yat address request for haven wallet (#396) --- lib/di.dart | 3 ++- lib/entities/parse_address_from_domain.dart | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index f1c6f562f..763a82834 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -639,7 +639,8 @@ Future setup( getIt.registerFactory(() => YatService()); - getIt.registerFactory(() => AddressResolver(yatService: getIt.get())); + getIt.registerFactory(() => AddressResolver(yatService: getIt.get(), + walletType: getIt.get().wallet.type)); getIt.registerFactoryParam( (String qrData, bool isLight) => FullscreenQRPage(qrData: qrData, isLight: isLight,)); diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index b074eb7f7..4778be0cd 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -3,14 +3,16 @@ import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/emoji_string_extension.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:cake_wallet/entities/fio_address_provider.dart'; class AddressResolver { - AddressResolver({@required this.yatService}); + AddressResolver({@required this.yatService, this.walletType}); final YatService yatService; + final WalletType walletType; static const unstoppableDomains = [ 'crypto', @@ -36,8 +38,10 @@ class AddressResolver { } if (text.hasOnlyEmojis) { - final addresses = await yatService.fetchYatAddress(text, ticker); - return ParsedAddress.fetchEmojiAddress(addresses: addresses, name: text); + if (walletType != WalletType.haven) { + final addresses = await yatService.fetchYatAddress(text, ticker); + return ParsedAddress.fetchEmojiAddress(addresses: addresses, name: text); + } } final formattedName = OpenaliasRecord.formatDomainName(text); final domainParts = formattedName.split('.'); From dc623f3293fba818c937f79af5b8d63bcb58b689 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Wed, 29 Jun 2022 14:38:44 +0200 Subject: [PATCH 13/22] Rework settings pickers (#398) Updated pickers and settings screen --- assets/images/flags/aus.png | Bin 0 -> 1390 bytes assets/images/flags/bgr.png | Bin 0 -> 735 bytes assets/images/flags/bra.png | Bin 0 -> 1269 bytes assets/images/flags/cad.png | Bin 0 -> 1005 bytes assets/images/flags/che.png | Bin 0 -> 855 bytes assets/images/flags/chn.png | Bin 0 -> 860 bytes assets/images/flags/czk.png | Bin 0 -> 915 bytes assets/images/flags/deu.png | Bin 0 -> 703 bytes assets/images/flags/dnk.png | Bin 0 -> 801 bytes assets/images/flags/esp.png | Bin 0 -> 1090 bytes assets/images/flags/eur.png | Bin 0 -> 1005 bytes assets/images/flags/fra.png | Bin 0 -> 700 bytes assets/images/flags/gbr.png | Bin 0 -> 1341 bytes assets/images/flags/hkg.png | Bin 0 -> 1023 bytes assets/images/flags/hrv.png | Bin 0 -> 1088 bytes assets/images/flags/hun.png | Bin 0 -> 728 bytes assets/images/flags/idn.png | Bin 0 -> 684 bytes assets/images/flags/ind.png | Bin 0 -> 845 bytes assets/images/flags/isl.png | Bin 0 -> 867 bytes assets/images/flags/isr.png | Bin 0 -> 937 bytes assets/images/flags/ita.png | Bin 0 -> 705 bytes assets/images/flags/jpn.png | Bin 0 -> 896 bytes assets/images/flags/kor.png | Bin 0 -> 997 bytes assets/images/flags/mex.png | Bin 0 -> 1013 bytes assets/images/flags/mys.png | Bin 0 -> 1082 bytes assets/images/flags/nld.png | Bin 0 -> 762 bytes assets/images/flags/nor.png | Bin 0 -> 898 bytes assets/images/flags/nzl.png | Bin 0 -> 1373 bytes assets/images/flags/phl.png | Bin 0 -> 1179 bytes assets/images/flags/pol.png | Bin 0 -> 707 bytes assets/images/flags/prt.png | Bin 0 -> 1169 bytes assets/images/flags/rou.png | Bin 0 -> 705 bytes assets/images/flags/rus.png | Bin 0 -> 702 bytes assets/images/flags/saf.png | Bin 0 -> 1319 bytes assets/images/flags/sgp.png | Bin 0 -> 902 bytes assets/images/flags/swe.png | Bin 0 -> 752 bytes assets/images/flags/tha.png | Bin 0 -> 774 bytes assets/images/flags/ukr.png | Bin 0 -> 695 bytes assets/images/flags/usa.png | Bin 0 -> 1220 bytes assets/images/flags/ven.png | Bin 0 -> 1009 bytes assets/images/search_icon.png | Bin 0 -> 469 bytes lib/di.dart | 3 - lib/entities/fiat_currency.dart | 71 ++-- lib/entities/language_service.dart | 19 + lib/router.dart | 5 - lib/routes.dart | 1 - lib/src/screens/settings/change_language.dart | 63 --- lib/src/screens/settings/settings.dart | 10 + .../settings/widgets/language_row.dart | 30 -- .../widgets/settings_choices_cell.dart | 71 ++++ .../widgets/settings_picker_cell.dart | 49 ++- lib/src/widgets/alert_background.dart | 22 +- lib/src/widgets/picker.dart | 398 +++++++++++------- lib/src/widgets/standard_list.dart | 14 +- lib/src/widgets/standart_switch.dart | 2 +- lib/themes/bright_theme.dart | 8 + lib/themes/dark_theme.dart | 8 + lib/themes/light_theme.dart | 8 + lib/themes/theme_list.dart | 2 +- .../settings/choices_list_item.dart | 25 ++ lib/view_model/settings/picker_list_item.dart | 19 +- .../settings/settings_view_model.dart | 74 +++- pubspec_base.yaml | 1 + res/values/strings_de.arb | 4 +- res/values/strings_en.arb | 4 +- res/values/strings_es.arb | 4 +- res/values/strings_fr.arb | 6 +- res/values/strings_hi.arb | 4 +- res/values/strings_hr.arb | 4 +- res/values/strings_it.arb | 4 +- res/values/strings_ja.arb | 4 +- res/values/strings_ko.arb | 4 +- res/values/strings_nl.arb | 4 +- res/values/strings_pl.arb | 4 +- res/values/strings_pt.arb | 4 +- res/values/strings_ru.arb | 4 +- res/values/strings_uk.arb | 4 +- res/values/strings_zh.arb | 4 +- 78 files changed, 604 insertions(+), 361 deletions(-) create mode 100644 assets/images/flags/aus.png create mode 100644 assets/images/flags/bgr.png create mode 100644 assets/images/flags/bra.png create mode 100644 assets/images/flags/cad.png create mode 100644 assets/images/flags/che.png create mode 100644 assets/images/flags/chn.png create mode 100644 assets/images/flags/czk.png create mode 100644 assets/images/flags/deu.png create mode 100644 assets/images/flags/dnk.png create mode 100644 assets/images/flags/esp.png create mode 100644 assets/images/flags/eur.png create mode 100644 assets/images/flags/fra.png create mode 100644 assets/images/flags/gbr.png create mode 100644 assets/images/flags/hkg.png create mode 100644 assets/images/flags/hrv.png create mode 100644 assets/images/flags/hun.png create mode 100644 assets/images/flags/idn.png create mode 100644 assets/images/flags/ind.png create mode 100644 assets/images/flags/isl.png create mode 100644 assets/images/flags/isr.png create mode 100644 assets/images/flags/ita.png create mode 100644 assets/images/flags/jpn.png create mode 100644 assets/images/flags/kor.png create mode 100644 assets/images/flags/mex.png create mode 100644 assets/images/flags/mys.png create mode 100644 assets/images/flags/nld.png create mode 100644 assets/images/flags/nor.png create mode 100644 assets/images/flags/nzl.png create mode 100644 assets/images/flags/phl.png create mode 100644 assets/images/flags/pol.png create mode 100644 assets/images/flags/prt.png create mode 100644 assets/images/flags/rou.png create mode 100644 assets/images/flags/rus.png create mode 100644 assets/images/flags/saf.png create mode 100644 assets/images/flags/sgp.png create mode 100644 assets/images/flags/swe.png create mode 100644 assets/images/flags/tha.png create mode 100644 assets/images/flags/ukr.png create mode 100644 assets/images/flags/usa.png create mode 100644 assets/images/flags/ven.png create mode 100644 assets/images/search_icon.png delete mode 100644 lib/src/screens/settings/change_language.dart delete mode 100644 lib/src/screens/settings/widgets/language_row.dart create mode 100644 lib/src/screens/settings/widgets/settings_choices_cell.dart create mode 100644 lib/view_model/settings/choices_list_item.dart diff --git a/assets/images/flags/aus.png b/assets/images/flags/aus.png new file mode 100644 index 0000000000000000000000000000000000000000..c8837731c58a5746ff36872629dfabac35b11026 GIT binary patch literal 1390 zcmV-!1(EuRP)~*6YZFN-iXtSlF)57H3;jrLvI28Vz+EHCYLxV#7P(9UY`{nxR=qQYh zjm144kJ8)QOMS$(M2Vm!5{cL{GBPaDXjG78S#Ua?M4G*jbeqklEG#U*y=$mi#zB_d!~6HahmsZg2me$%ZawxK96i+q z)S|oP7_@g?QlIVKS^)1t0BWyZW%th(R>Me0fiy9*$30$wQtt2Xk6BS6*Nf;y@ugai z`-Dq|>hCYZ7odO{VJ+?u{0;3#8&w_t3MmJVoi!o&xKtcnk@2TGp zM#BEWJhg}Ik7mO+TQgLG!!I^Nr(}bH!9P-BaO&g>AT5P;f5i;M2}2NcLmaggJJcg7%KER0 z%OUjBML0Ba4XqW3J@Yh+(KAD{rbJxM%^(FskPIK4^q`Fcn&0Tw*)1nM$%FU{Hs+2#nFyz^ z{R8QMJUo3L_FX-FpjmF1f)7tB(fRp%d(n;F+ESRyveSpZlmGs7Aen@N5qxo@63xrY zgTMUA8??N>7Y^>LVgq$o&(CYlD(eAqp51O|9u-q*K(SZ#L=lse0B)R4$;0V@hOD(f z&f^{>qH8X5DE692K12z~i zk>Xru9>onid$DEsbI$x&>@mk zwDk$w5Z7^8#^m7OV0Z<=ZFwPEk|iZ2tS^#u&ux)pBtJiYWqNu#N}M3ET{eSyU|>8n w;P;6G3DNk>?2y55cp*ZP0|~M-J9yyuAKVAP`)@@c&Hw-a07*qoM6N<$g3Ef7xBvhE literal 0 HcmV?d00001 diff --git a/assets/images/flags/bgr.png b/assets/images/flags/bgr.png new file mode 100644 index 0000000000000000000000000000000000000000..a89509f1fe0a556e5c983509983685d07615e426 GIT binary patch literal 735 zcmV<50wDc~P)2&y%HoqLIRfknat<6 zUYX6lB&9#eZ)WZBypLJWcx@q`A@kZ~GI1b@NWN#2(;U-EHPxm07&Pp6aIZnu>AVWQjZDj$tTgS7qac9WG#h4eV)p>Hr45d5GYeWqWn zuU0FuUa#Zvcudpj6h2~;C>WKK& z|9QA$>2dAy@MVod77loNB6RfSIW-Sw=kuBB_4-@w%lOFJn$WhG@47Bsuh+2C>4XW< zIL&%Ql{*72us%XJ@5pu RD02V+002ovPDHLkV1kFePZt0H literal 0 HcmV?d00001 diff --git a/assets/images/flags/bra.png b/assets/images/flags/bra.png new file mode 100644 index 0000000000000000000000000000000000000000..ecac6f5a3e8a3c3a191f64fa2fe3a1e1814ff0cc GIT binary patch literal 1269 zcmVLBkly*~N#{RIDRg7$f2FwO~83Tpk+dwz^F1`;`*q-g)2iCDY3Bx|@ zsT-zXnPH}Mqj9*}G+LRf#UwRpVwyj<-|y%N*PB1a=z~A_IOm@GopXQpJKy=vcMj1( zD3xSlVnRi!>$*}+r-GBkT3*X_}^Uz1Y^)R^YO* zw6v606h)Yrn7w6VW8=&pUn~FcV;7wctoeUF^B$M^Kp;@ixB}CwY(@5umeWEK=;GP4 z@GKVapvqSEDGQwd`kl{(oCJ)VR5~Fzv^-6{^O)wnVIkA+KcueaEFBkEgeXfy;l$J+ zMmshs;=4zqotve$;fv2`zV87IU)Z8XvAw-bGJ0sT5v!c&XtVETi%nm8$Wy&TGq*3( zO_skNou|YLEA@Y{P9vW_plOx?SB%D1x@m3KdZh8~c6%v&YSbXcJx_!MG3Ap#T@zx& zoxeMUM4#Ge{L8yEcrHehkq#mCB9%%NTUuH~{m4Fwg>vueeL)l?Zw32kX8v<>zu_cD zYjc@CegM{%KNsh@iS!*S4}a99qywL38>X+71yi64gVPCn|OnN1fRhPtNSp%tpW14;9gCw4M4_v;GX z{B}c-o|^ouYX9gV7A*@;2nE%EKmKItvY0M22CqFMqzn$8@=BQdWtA;@tE?Sk^)6na zJ8?VJf}HIV(f0QCnjP@xWB+!OXTf{G3IkXFEKNNi3B$_L(t{t~F1^2>dW}8~T@qs@ z^0k8wj8|sSdI6FkE5i&H3Br#gi!#BB39z5F(dfYMLIzdX}zq?*)9248+gUSK+sh9Pj~|;7ptBRV8zwd)dDZ8 zbvi9zE_{Y7ur+bG(+?c(xYV|`wlv=NRph68sox$@ua>hK4`=WznM|0+FzXs2br#`b;Pw5)SlCdyzinAv4W zT`AB-+1c4C#zKi_smmm@E|+UJ8ja?_iT01nl~B4cFkc$T=lu%_)%>o~C4=emLZvDf f640x3@gVUEYCWjJ76m-_00000NkvXXu0mjfz1VG< literal 0 HcmV?d00001 diff --git a/assets/images/flags/cad.png b/assets/images/flags/cad.png new file mode 100644 index 0000000000000000000000000000000000000000..106cea5b9cc398a4b2287a4f5397c90f144dda31 GIT binary patch literal 1005 zcmVk^WF50!?e&&XZ$5M_c3Q~zn**MzKD*ZG)r@Hb1HJvG?n9Q znk3b3-{M`>ma7h1OPBy+I4LtTGfJUQP^PA)XnlQMJVSYZe_z?!+A1h-FpdD@Yer0)MV}p{(q%k`?Yc4GFhs%+($(#Qw$@8413;|KBsyC#)zJf}_(Lob&* zH^57Fe%s7`u;<53!K;B(N2MvPVy|Cb>c|?=Sh#^tM{}b0nC$_ z+~qWcw?AthHWvln-FW=H{kulKGuH(mg&&-9E%8 zwD1{D=b?vK^T^?j^FSt(3GlkFA{6XfKa`IP5BuQ^afKm6+1ZEF){eBr`j!Hny7wUZiaPX;Jh@D_4U`GhlEHC#q0%uI60mD_ b@gVsN{LmIAoja<=00000NkvXXu0mjf#EIR1 literal 0 HcmV?d00001 diff --git a/assets/images/flags/che.png b/assets/images/flags/che.png new file mode 100644 index 0000000000000000000000000000000000000000..427db0fbcedfb9f5799a40fd9c7ca644c5b698f1 GIT binary patch literal 855 zcmV-d1E~CoP)ICtFS*9-rJcslV9Gv`FKMVhB2ySvsnQtr8LIbj3^>#|DV1h zAVtL1C#C>0T#TcmBct7J8~gkFbai#5+MqWY4Wm>l$;cn0-Q8U^#`^lY)b_S*8%s+| zQn#ZY>JASNCB{KL>Vo5HJ(tT-KA-oFkB{Za$qB~rf0Vx5I)-5=7ZbBLE0s#}ZS(tQuDC}r z0JHx3?Dm5_R-NK-OGAU*VatBRLD^;qQ;GcZY?lb{%* z)9H9xk2TS?wKdXo_gS~?Fm}}B_{4qZmUJkk4mV{+6sFx+U~VmG%fUO`$RW4 zH;G!;6A$qF?3BKoh2sq|x#)c~nM{T~8b`o+yubS!{@f?hO+w#s_02lAz=ayX)T^8M z!NCE|i=Qu_`}!;kCyHjXDGP;y(p8Lt^&&1M_NV!8OF`gd(kYQY^++=)X5g(UeAaUU z<`s)Y*CY9jVghjC9L{BLf~*WBcPy=HwQBLXFAxgBs~_}n;So&E5Lfy#5gj9H8-%t+ zf73K&x7&4gc6N|2fvD#np-j}{$^pB(&mC+|U6n-PfqQJyg+vL~4{f_zUtV5T^5Wvc z?lH8jF0h5%-riPYLE>B6GRb}>lj)VqSqMIuF_NkKu8)TyY_rODr*OqV`A6;0Bopg~CiRRoB}0*NLG z#*RPm*WGRAEt$*SVFNB~-ILBcH#x2tlwoR)gD zgKuMFLn9CT@Y#9AJ~}$0Oiz(pwE_{<$CXIgd0A ze>yY^*qrX{5hNF04r9LF50Bbk=ZvzCUkzy5z#>HHi35{ZY>+Fx?~+$G3y$)y2UOpg zr{C-gPZnwjT&$u9&ivSi-5>PzoD4HggmX@%e-*CW> zOjsMfwX#EtkZeyFdiju9z_5|OyN6;{+mj7%!g! zr3^MFqbOg%!e+A>u%zdd5`YVzo&p|vkkzrv-Pzfhx45|I@@3268=;vBK@g~wm6cc$ z)bXv;w7tB%B)eYPp{sDe@NB#c(zOhmARoB$pwmgVMJBb&-hgj*Cg&HsyTrG^6M#&K$r~a!y m@z$ls%4mj(7M6^0000P)xL4tec=q1VIa} zirU&@32CWX14&boCQXyP>-jD{kC#Up(=h7l4*u@$t`Ut!mFFL$;o)ID zM^8_WlK5OMXLNRUDtV7_=o=dwQsVvEiDxd!_X2F zv$wXlw*zazD?ga?Ao}i2g}Gg(c7d0Fb7ErR0=vaXBvR73!P1-i`}?Ml1RlPK(8Z}I z^ewtW?EtGd$g5`|4e@Ns3(hIa8@%;5MdLST>Hh3%+RGeJtKsHk)-vGf!bcjvHB0Zm z#;Dc6M+oZ>MG{vV0Clh3eb!QS6Nv=L?%@!{S6<6tQFrdmXKFUOy1E=rH7>Pb# zn%4FswH=~;eSMUmljH1Rh8}Fbqf41T z)M!AwKp;@AXg#it44uF~b|!4i(+tt;y!XS^HX1 zK7iR98yh9gf-MPZmx=;g(H5WKN}QG`OjOz7j;${qkNfzrWg->ar62s`(!+f?LtaTS z;hn=9>xRT)yk%J`o6Q!7hK6un0#Vzko|vf3oduRV&jWINbk!D(Cq|{5g++7sAH`v^J(Q&yFoC^c*rGY#jT}YVT pca<&~JeL-8!I zg8{8pE7b@4d_IfaZYOg;jQah)nxollO6?y6fv8j}QukvV+D4<1#5`z6n>nwx*XuQH zHk))ZnaJ5}hB;D`Cz>ePQtsNcF*?cQ}gJA8%w2-gxy~Ud03t zj&9{W<&tGEIXPrG@Df62M6rp$=kS{kYfb#q;?bMAPiRXQC-LMjY;t2LxQYDh=U@Z|vejL(KMvudi)R z%amR$7Qv07eYL2z-U{XPOGdX&pW$Sw@>mj?RzaUtR4`-(0Z la+enly||EoU(v;b#&0DM!YFi}<8=T4002ovPDHLkV1jXJM412p literal 0 HcmV?d00001 diff --git a/assets/images/flags/dnk.png b/assets/images/flags/dnk.png new file mode 100644 index 0000000000000000000000000000000000000000..69dd1b2b8d6117df195668916b901d61126123d6 GIT binary patch literal 801 zcmV++1K#|JP)-4sQrU=nABEA<% zK=4mcJP+?~GqbqIdT4S46ZZXJnc3ah@#pN`>>q$buw$j!Y#Jmv=j@ctkz%O!e;8{h z(g@iG!~}>87FMs<*=n_7)oK-by`JbJeX&@u(P+d&e+(*>itv%mX1TPtZJS+OTyWV> z9JUh&*yXa9l~Rz9@WmWERMRL%jGEfb7l5WD$z+0vQhE{# z7LzVK{_-)h6El-KsNZ{W2?flpXT|epfhvf~1)C_{hrflDPKolbaBI?&B^xLeIM?4J zJD{moBk2BIfwx@2xglpeAeztT;nSlhksXBh`XBE;oOQ1!%12sWJRI=6$%jJ@*5f!X;Je1JRq()RsJzX1x-St?KiEQ%sE>_147Y;=Y`nS(Nd+Bm(PTrCirT)n-AFx|ObXfQbnGoc z+R8$Wgo% literal 0 HcmV?d00001 diff --git a/assets/images/flags/esp.png b/assets/images/flags/esp.png new file mode 100644 index 0000000000000000000000000000000000000000..0193a6a44bf2ffce70281ce6137a2301239dcb15 GIT binary patch literal 1090 zcmV-I1ikx-P)M%mtAVg^iDovr?(V(&;qj=H}c!(l<6X*z)o+ zZ~0?LrBd!3y}i9$+E=Sp77PZt?5A z7GunHF$8-xo6Q=3K3ID!8qUB65UgMPb}@BcsMhrKw5r# z6PA%g-YjA*cLrIuiW=icpA!=~MRXeFa@h%mLLWx-%HOYGJ~gn-GZ>1;@p7$*t?x^? zG=`2N8jaFIk{!#JuO9ro)i@Yx*Pp?UX`oeBw8pG4jk}NDu^z20;#WRNN=yhJbMb)Z~cqS z)!X94c!Hfm6g>$WorYZP=4OhEjR9o#9kBhp2Ipb~T9=K$*>wz8O&IqLY>nK(&_Efv z&ws$qHbXZ$2XdZa7<)Z>7-C=Os5EqR}}<&Jso90kT2nv zPnX_RLN>lVxzy~Vph@Byjsx-nFG=d7qocvU9_2RSBm8_icw%vJu_jc@jZtDvmlF64 z^kpJ$1esKU(hT?vVk0?HcReZ+Fej7A*bcaF?s>#T{*K7!8L;t{C-Dk4n7h1-=3O2zVm#Ab2vd2V?L`(u@XLfdp&O__eQmK>Jw4Up@i=X4Y=|-N$H&Lo-rk<&`eQUcJ}%Z6 z92~Tye6?ED0)c=f$1x9mlarGc)h-$6 zudmP7Xf!m_G_`?&0Vpkg5WL^-w@RfF{R{3nlJ8c|Fbs?NRyZ7PGHtG}ubY~t2@?}* z?<*7vH`ad5eCN+Gx*ph141Bo5)XZcuO@kSjUS})nJdiWV^3nao)kwAf*P^|o0v>eM!9l}9^c)h z+~El2n4kOj%pQvk7ig9h&+JT)7Zi&{3I>BB$V9H<)WCWgfvoxRgsup%D*luT;`BDe zq{3;>^z3@LbZKU}Yx#v_ni3N`q=K`vvzFuy@(8#(|KoPo@(Zg|lrM!z1))#~`CRZq zQEXUFx+-f>z8F!G&lQHL7YNtOz- z^7DLJ;nY$Aj3+G$*DD2*M7OS&iT>&0Yw`kF)KXEqGWP-m)*H62%m8rj?884UMudOfj49__iE_DY>!!7}_TOZDtv&xqJwlXu zfojhQ9=K<`3-ZZS#aS;3XLRD4K{e_bUai*&97rv`jtE{vJzv0DySuwwNm~bWZD1Yn z9j?jNL}6l`yWBA~j*gBDKKFIhr?}wBY2Zc8+5yhX8TJ*53GnVU+_FwcS^=w8fnbHg_HSg0HTEqVdAm6ctc3XMbC^BKiLQenGR@Y_-Oava&%I zbaZr7tObg1Da%UMhlYk~`Fy?sov3|X?gab6!2M_-zuONIx_hkCBZKSlLZ=HJBp}!6 b;X&dzW2Yyj8Uy;000000NkvXXu0mjfD{|Wg literal 0 HcmV?d00001 diff --git a/assets/images/flags/fra.png b/assets/images/flags/fra.png new file mode 100644 index 0000000000000000000000000000000000000000..91dce8ff249363df0de2e3ca30d522f1fd706630 GIT binary patch literal 700 zcmV;t0z>_YP)#{zZ z&0s#C$Nhd^4TnQoBQ}W=LC5#~Tt1(7f*=rK7>ZJY`*@)?h`zK)`t@O`u+`Gg59UjFERi9<$eAm z%5XFqMJ_7jdWl|?kCJ>PJmKy+xQh{WB#+{ma=2 zx?nNLd3e7k9<};K;ux1BYimIPH9>onS=)xg;h;>9YSuOeCG=%7J*u~!(hRT+cs1RA z>oEa~7mEc((&`y30Wy5X94^AGC$j1$xkKr0HX9d{ElEeg4E;#IcRbAGjLsDY6X`Y5 zwkFt?<~xp~&gXO3YPBd|BBF6};Um!)2Lp7sj}tshUA3f!3t!OWOp=;8eziTbUMv>1 z9FND|1;Mu5$d+WY+0<*1q(0jsNx#u(Tvn@9K%C&3E@vSr7^I5^+`kqiWcpcFks(#Q ikZ`9UL3UY%2gVO+dD`psrUXG?E(WAGEG;dSd^{JObGElsCHL-u6K8&hp>HOj5IOKlJq&)d zA12GUtKPdq)6ntumj&6empuqaI$ng18Y@HxhT!I9@6~r}9nWA@YJ7Y=&Bg}0UW`Vg zB2rhaoH_^L=6%o=TY$OYHyK1Rg#f#cG{YaqTVT({Z((`wVOYEDrLL~R-ZRiJBt4Ng zz%gYoS3V{iC^m2|KrX~WO#E3?Hl9A#4b@-tK{0^~0VD=uA)!e~xsfZ-Ag1x;dZGfl zQE68q0M|blfW)=GWMIJ5)!qU}5%}a!^YGn|({ObuSTLY+3@-P0RrS_}9k8W+H(W3~ zp|yXM*?r0o1e?uvYm#wAn&p)HQK1mv!wB5b>47shQXom2doUkhY3Bj7lZH=Ej;Vee zAysjdVkSD&Xxa8c=S}U02$(#P{Qe?#(XUw`Wu9A}Akmxk{O9!AQoB{xlh8jn2H~}M zR{byMkC&B}mab!bPN_gACML2#Z%1;0{$HuDueV`O#|e~mJq=h4x>+B(Q@LY%Pg~?7 zoArFTyPrjb`&@WCF+1rLh(~8 z_A(2Gr=8&N{|SGeKATlMpXg-dFOAo$;n9yuq-?Y%7$!3daD_B{c{67I1I~q0Q=v= zeZX2Q@A^-kqHW#~+TGpM00$4f0jA-vVdeBWR!=LQ@6a)*Mc^wszCR#q27Jbo?>u@9!65 zghC-x+7}81rK6+6l<)B!`l8XOiE+@6KKr;*-`Uxr-Q8VdY;4S2T3W&whMgz~dP=2| zx2>(sQ!bYkUDp-A-w)E_2cdhtUNezM(BI(vM(Y1-r)iqWy3yU;U13?-+S<|;MG-D0 zX77o`VwYZ}_V)O(ms$f~A6&UPz*1jdU$1Da!1XG75&6+7B!OOzo`<7Y!GkJ$Ij20- zVz6^ksl|aw2x$>T5>pf48y%<1v-70-1LPL*c${SQu!v%olS_mqA2Z44)OlxwjBJ|R zA`l2zoLq(8={4Wz1m%`ri+=5Dgxn&R%NbJF$OSG9Tqm_FAiP)n@LrJX>>8os#|`px z3V=A?<=0jyIsc66MKBmdJ^w=#3Cl&7>IH}))cW@$(ql)0Q2F?XD%^sPrpU;oNryf; zCH_!+?g=#t5qg=h^&-EvDm(^~?L$#I<>%b;B9)o=kLgKagZ}iwOFDhKN^amPM?n&V z;Mt#_KT&vkR`eN}jL?w+$MR?1O@ItWWv3IG;$VwjRMX)SMVDJN|CQh=6BfBlv#Ko zB>hQsB4IWo)bJiA6LJ!SzumpTC2NDSZx+c7+XteXutB!N^aWpNWZieo^tHYLpjRwi%*3&ae1NA1s4*qt99`p@e3{wPU)Pkr<(u(002ovPDHLkV1f^;;NAcL literal 0 HcmV?d00001 diff --git a/assets/images/flags/hrv.png b/assets/images/flags/hrv.png new file mode 100644 index 0000000000000000000000000000000000000000..9c87c5d0ed8fc429c3918b628d71cf64f49a63fa GIT binary patch literal 1088 zcmV-G1i$-e?b|>3?m~kYv~QHl1~|q-rNA^n;gqJNtI>?R#%#=M9h#rI7UW^r+n0 zwymsWQ$SR&{Xd>nJ+11qb%{A(G9*eQ5>XZw7L=Bj77Pv!I`>$gnVC^WM@Mbni=nBh z$r+=jrp6Zb$z)Oq27|V^&(CpRdwaXh<8VLsdB+ucJRZl;(2&*9(P8)X_3;>%mnaj| zQmIr~MMZ^{PN$VjCZkkWS2Jnn4_PlOE3>Dkr|~!VpAq{1+64jun{=zTwl+&`c3@y2 zqbN#l3}UafwY9MeOcHxN2)WTD=T(vf*pbc;xn3nNa(;xdPC%g!Q}i^7VwSH}af5P7 zLn)x^C>6Y%0w@w@?N+?seE_L<;|Mj>(^FQRaF6^cke<5U5$-^ zVF1sc1BVXfI!QN3H*Q_e^-Dl+ZRPd+4^d87T67@|5s52T$ivD5uJ(mHpo07GJuIo-o z#1KUlxXi#h11(LK&>fFb0cMSij8Jk}a+}6Afi*@oT$V!5WbM4-j@rP)#6*C;`zoh` zck9RcO6lP(&N#0)WUTwg@Y{MJZ25Uj)9gedkueM-6N|-|sO5f{h^R%KX>xapXBw5R zPRl|AFOf-tTNZEsvTZ`>v@LVm{{H^t5<%DsVOwq+8XBCjxaDhMOC(eE_4P}pX{MPI zmdoWra1RXphX&$$`9MPT-z#*;;5)ofvBH4_+Z8%^5cmy@k8EL^E@CYJ0000A< zmUZT{@XD-~jL{jT^yAo08uTd(Ml(9b?4kDY)Dl?9G=xeoF3hSUB zefGLWACJd0nM}e?r=xnk9@YqLq9EvL z_#C4@*UtBS#d=sSmoF?Y!{IOxLTDEgv-cK@Mf%h4^iM(Rft-N)^|tE0VkPMJ`xl=T zxL&dsrJ$4}BmwcjyXWyPUhpMNH=ikw9D~hClH;!2@D#WR zv)j>Ss#GfE8sNNiI;}l=bL&5R`A9AS&f{Ajc{I96-+w%kYcMgciL9Lk0%`z6qsZJf zY`0rwdsMS_F(@e(i^BG(-g=}NA-}5}R-|vHZy^eedM8oKpiHU~X zd1iN?c*22GS1r+a;Uk;uA<^vqtL@(C9F}Q07z_?ahOspbwve@2O|J!sZ)3|OPt|Jm zIG@kY;6&GSISEn0AYL>u-`9eKjE^N18Dhl?Np}houuCdD82km`kEoA_`E;NF0000< KMNUMnLSTXzGE1=l literal 0 HcmV?d00001 diff --git a/assets/images/flags/idn.png b/assets/images/flags/idn.png new file mode 100644 index 0000000000000000000000000000000000000000..52c9659213385314b3c321c31eaace4332e5df9e GIT binary patch literal 684 zcmV;d0#p5oP)M#&a+NxJj`~#OB@fBPNE_?~!$M^9i+zPtXm57|B;14LRr9Zjz9XZ zYiof6HK0AptZl>nepjYPHESCWCD@rvkLu8)%n+7=mZtn(PX$!Rq=d(w}mV;CN)NL*LtgP=qBuv<)&$#Pyz9Ek_x*S{9LB5F z3PjUvXCk60bw=dwkOl-)x@rxfiC<*mLqp8|x2@0ZMx&widc6)_1h!>?E%ab8&~riK znQe(=*zfmWr_(6{C-E+q%aC0d6fX_<`F$bb6yKFyG88T^9P!~o0(NB=4~)NR4JK$w S6mZ)B0000wS=L3{z_4T!y2HRrS8W# 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 literal 0 HcmV?d00001 diff --git a/assets/images/flags/mys.png b/assets/images/flags/mys.png new file mode 100644 index 0000000000000000000000000000000000000000..022476291461fe068760cc472e5ea14c49b156cc GIT binary patch literal 1082 zcmV-A1jYM_P)IL#z~BFM1M{Qt;zq4|)}~e?Yt|y{ebu#Y+(jy?N>0 zN}<7mwqg(M21><3+LVN(X_6h^n|8Llo2?cdp+6Yjo7p$BzkTy&_RRozp#+s&E~k=Y zj421%1QgY6|A)S+OR7FwhZqNu!J-(3p_I#IC6mcOKA-2$NZ;JtRF;>QneW4(zrUZ4 z(b?I_gng}6Q=-u*6VIt1wG9pqG8%{4sm(pE&=(dKU~zHL9v&WM6B83OhV3Rw1hsm- z9*RUFTBFfWn$4yXkH?8Le5}8Y{2i}?<4fX+UdH^P`A6fx>_h(b8~Y|MNznm z0oiM-tE=Iene*SUdJ6s#bMO`)*JTZRQ&UU9@6MX+lrL>+tdACoZ49y5bN_oh*a(BhF>15{43f zKoDYv0WuJM{aV8i1P@U+#-7b^G8Z2VIb?`5;mN%(uvLkGwHCt?=fwT^dipd-K{m@5 zriUm6F-ao$@a7VXipbxluX^oQMa$J#F zA6ZaB?M5h@%^qV%4C!>5_Q4PqMHj3oDUp`-NJ8So3Fk<}6K86qV}`6eQWm0?W#e(g zsVj68>Fw=}+O}O_M+B`_t1vJy5Ji4Gq4yl_Q0nXJ>pK4TRSE@>`VmMMIjbM;!&#|R zVu?gzcklC!;kR`|*iwH@(^$1yZKhHwIxo>F)OPYwBx+-01OEhd>`=p_E09n&%{XJ=+9oVk)*n?MUwUI?(S{NvKqt*_KwRza4rm( z@6tfj_bwz<|1%bF$>6)ZP~m_J39@4W7Y_oz07Cgry5D4;BLDyZ07*qoM6N<$g1;y5 A+yDRo literal 0 HcmV?d00001 diff --git a/assets/images/flags/nld.png b/assets/images/flags/nld.png new file mode 100644 index 0000000000000000000000000000000000000000..62dbc205834a2822186eb8423d4af92be5f4e6a2 GIT binary patch literal 762 zcmV-Abrr&AUCW1?28@fyWqQEC0d;UKfwtkUC{hrULmp|B46 z(Pyr!<{{NfZR#)9IADySsBQmy7g$U*_|9kmetRoJytCYPAx7 zLVS*v|5eZPJVkO?DwP7NgTY|nODWmJg4nzB`8@sc`|7I{@|Cav#QNL)%NMV6xtvdy z4L$0U%jHs@&u8#{`r8@)FQgRD7K?>TE)!5w!Fk!_nM}qNmVsQt(Xt~f2Lc;)L==G{ zECYRdSQWfvL==%OECZ!H%}IIYz}qjMh4k!jRptBQ)BAU-TrP9_hUk-OMc4#Q65vm- z9oA;>9t(%HF__G_iSnq8!35)(>4^n85LjnX;F_K|m{>4Ufi*xLrBfL5;=NuE%zkX> z^kT#F^J?^ml;|4KBBq8?W5^?GMYuqT>uAV=mJg%&=N${sa} z*?-mDYnd)%T=)C^!;zqMb%8GGN~OYUp+;Nl63G+!=5aQgUBF3Puggh@9t`4-2D*KH skZ|H-NskP%#|uY1@*n}dq=yHMpY=|<^gs1VNdN!<07*qoM6N<$f*b&2ZvX%Q literal 0 HcmV?d00001 diff --git a/assets/images/flags/nor.png b/assets/images/flags/nor.png new file mode 100644 index 0000000000000000000000000000000000000000..bd226c0a6a50b3bd61123d838f48336157bcd3d9 GIT binary patch literal 898 zcmV-|1AY97P)4?W~>5PR0*souOOh(ZLxe?X5~zivIKV6V0(wY0@v z`URy_%_)!%OwEVc-SNF4GuZ#5dSL)l_+px2x}!^6WwTKpmDfk1%g^Lgk5w~y4l+8KtyQMY5Un1#~X*w`=`VQ47pqmRz&0RI4nNB={X3k^@`}&>3qFna`FiTgTdyE z5{~C@cXg)eDRlo{%eLCvcToSl0)ql255IbqQMDqO>+8@Dto{AIV>BUrUhRqH&f2%2 z4K;y?s-3ytP%IX?GoymFA2%h@Xp}iKD!QJI8Q?SE)MQItWE2lz_QAn{Wdr{SJ^^vT zGn|E8Ph@%1;SQyd&1MZ;_ca;{&aEHm?S_Z*a7N>bx{P#p47aTl(w6*nUFYR;*-Ru7 zbY3E&wpv1wsEw6%Om__(DtL4ik_rZ{qDh$~6=!|Jwk&nrmI=AGwpOVkq^&GuOLBaC zT*M+t&(ao2)<#E1t4Bvib>ak#<8mvg3j_D1fqZUUNNDc1R+kK}%L@(KTu6{ztBVJT Yf8aCZ8PR=&y8r+H07*qoM6N<$f>9Qp`v3p{ literal 0 HcmV?d00001 diff --git a/assets/images/flags/nzl.png b/assets/images/flags/nzl.png new file mode 100644 index 0000000000000000000000000000000000000000..11c6ade9cd644d01e7dbaf97538302ba811d55cc GIT binary patch literal 1373 zcmV-j1)}vhl)C*WYmu$(rrif-(^|VlaShf=x{a)AVq9XHkGuChcyE(P{C{i*Ton(l$U!|=fMj8@qHDUrxhLTiYUoZK5KB=;@62rs8 zqK@^sxjAWaa#Bh7F%%UQi9T|2aun4*5C}+FSy_r&&+WLZrlv;Wez=^=V*6Efhr@xf zv9V}NON-Lm+sl1KV~H|BV=x#@%gD$uhC(4J91cq+lZi=-53-(?mZr?k&SE7bwo&!} zYA4IGLV7eeH#b5uGBh+4mLy5I7{uP_a=Fq^1arp`juie=TZ2#CM)dqL9oPBk58puf z(IW^>{{c_uaWuVmF0Q_Q`_nl1co@a~SA

RULNeUn-ye}lVb>eT zi-uz*FBZ01BY5oU4``=~)@FUeiMUVjYe6u&rxE8H_Tuc739JEyGi%kF3hXmY)ZY zHvo@M-|NhYLQv>pW>FwhXvkRy+CKKi8ou0E2IYG@BHnqCw>0m@Z?&%quT_lp!T;+- zT-^oxBz-iXwLfzFB5u28l9Jp{Kk1WXocLR&A6rg#oAsKR;&hQFS4bn8N%+X&o5%l% zgV|d#l4ed?|9Ra8xP13;^qT$z-E6#MCIo0T32Q^ovm7Z#`^d_EA(9#=IksZ_WM`b= z&Mg~YzCJ2+qj^1Yns?!Pd6PEuEbmR<5%rO>QuIySjcXr0^(}SkLNX){_ts5jxaLDz z%q}b}M6+s}aon zs#{bpzR-lupaoss4$3)ky3`ie`I#>VafW1F^>b*gkBIg2rK>-pfeBaLCu~`tg|^+r zXmF0e*jS14mqwQnog%x#WJh}(kz76sf~dOPz4J{1!HZ1r4pzf}{U!!<2)nDF$0_g- z2Qa=6g~wWgfx(+e`xzZe9wu?i(<_^Hd2i(9qwBH*Ye8N0Q`nd3!~5S&;ZH**9u%?5 zlNO^hGc#y+^D^qiTA_LvYappY9c_bJke8Q-M-A*eo5FPC4>T>3BDA3mU1m<(r&j07741Y zn%I_QK|z7&izVk(TOt{>TCIyyQ&S=4gr)1U9<&F8#76@)zw{tsNUYO)WJq|tFd)f; f1l#F7JgE2=4E*4{CRBGF00000NkvXXu0mjfXibP` literal 0 HcmV?d00001 diff --git a/assets/images/flags/phl.png b/assets/images/flags/phl.png new file mode 100644 index 0000000000000000000000000000000000000000..b453f3933bef2d4dff687e33c06ef62e44e38e70 GIT binary patch literal 1179 zcmV;M1Z4Y(P)L{Co-rlzLUJFc&- zt%CB6OYNV{W<@w0mij&SC)faL-7!1nQ)m7{Te;-}HSHD0YAgS)Q zw6r*6oyp0`f)GN57{uOArBb1xuW!tJJUG)i^zA$j9&(}Y(ACGNQWzZ_b(A(J^w#$F zw$=S`Q|DLbBe?a=b2!=^$H78$((KtRLw>S!Gil1k-P@42-$vqrM*KSb0tVlI8i@xZ zI4CGiC0J!|LhZ1+t^;BZ!8uwoGk1?PHA@M54fpwra95()J?zX zblOEVqVmosa5uk$+QEYDMbiIXg(S=H>bF*e<8ZLZZ@-Ri1JoozL=+%_EO0jWfI$CK-0OOPC{m2{nU>n#`QR3&^`Au?}y(d>S`jd95akWiPC+uS;)46>F80@W0RB z7g&T!hrCc`pFdTz#ZnFY&!i*D8dX!z#|)L`J^BNdJ{_*B4tE1>O)e$Q&_wwF=8lh# zJL$>a78lOG(^2m!t^^lyks}J788S6?X z6aF#$zFufw?r+<+%w#f!L?XfGB_`^c?1_lF)R?Dm=cq%Lj;_jbp@DbECBv4Bx4q<> z(e=j0Mr9`^CbByO?W+s>vhC>TP-C&>XYETQb8T&HJM;7NdFF)Dak&!Ag@OOlKtGo* tBrN~CN|y}2%L@y;Tu88ArHcoR{{Tu0a@fY&eG~uy002ovPDHLkV1n1YKLh{( literal 0 HcmV?d00001 diff --git a/assets/images/flags/pol.png b/assets/images/flags/pol.png new file mode 100644 index 0000000000000000000000000000000000000000..30d5a937142eed15a38ddce4a631634da569e945 GIT binary patch literal 707 zcmV;!0zCbRP)+Uax> zmdSd(j-`|zKA62b8jb24@*`P-G)<4w>9h)N2&9LP^Z6`auNR{GCM*F*@$Gavxx8c& zek3ej71mm<<_gQeOTy8*BP<6J8!{(~L=l#OryfxSFPRfXrVGo!B~NoAtEZmpYCg+W z6YX}pxvq%n1#2_Z9*YHQV=|d}gMv@G^Y@Qm!Y24XeKO0FQp|;G6SDFo@}0>j1=av{ zR7X_p^}4>KL{XR*F-i0MdNxbu^BjKt4`*6q7$>!KJmP-8A7@0xIvhIFHoSOfD@E4E+fKeOMK56vc&b%3ae)Hzd58!zi z-OBRvvMJK{ed9cvF2!uO@6p$6lV*o)i&y|5!(up&W1O6v7>Pszxm+%26aDDu$k^T8 z^*jC;W@cuBF(xJ^d~IJYmyJjy;_G(lCw24l^S+ED^-|Y9uGTj;Hn6$5*;rg$^w-wb zWQ;~TQ6U(rR4OB*qobi}wQAIAHDi2yTu28$DEi3Ah<|u^h!>%=kJew-&ay0@dSh~O zvQAlFUtg~oh7q_JWFOkw+Y3Lw`FM+;m(eFyGr-;7F;tLs;UmYnLA932Wa<_balJ_| zn*WF7s{%cEnX?z-EY{`Q)D4WO5H1+nIhnZNkVPoc(iIZK=mTmLSXuyV{%1D=L8D`l0x>;TEP0Oux<`hg!|66O-LNzhP0+5zI!RUQL_BwL*N zp8C`+;Okoo(KPVe1D2l{^{UzSl*OKPP*|Z7$G|8Siw&(?{d80@rlI$mx>EqWr6g25 zjuc7RLM)$#&3c!9$S;v=3IoBmZOP|>9(Ys-Y%e(1NWeQAB*Wq)fiJ$nogY5{?%xmC zN(d+tv|S7}*##ce+g&fn0(zJhg7oIMEdTrsy89 zwHTgk|Ao7^85g-Nc%BQJ1ieQXN}AHM9UP^2a$y>^BH7vO@rydymw@kY(|fXCJWyCj zxKK}0aNmBHB!6ZSO_M+4RPvKc&whx3@Xpn%(Qr6?%F|S}uc`M+jL|=s-zcM>d7NsS zi8BrL)tA&s?pSfk^YkkJ)IAK0SS;4^s1WUIM_T2P^AV#Ylk@Pt<>AUF9#)cy(@l1( zE|TPq&zl>-Frd%4!RZNoV&&InPECEx58uPnEe{!HDkZUbXDGS81$}_mBuD1v=6c5H zd3Y7)QNfMt*WcLQ-ae&T3u07Q_j@RL9gv+#xC!*73UbZB1p^mSYp)MH$_rRyXJ%QJICm>ZHKm% z{-IFFFO^C)rIg&4glMCcM@h87$|~JmVTUrWu7Q-ofhC|zBBiv~pV^kQJ~lQM$ZR%S zJ|(oRE@CTkdU`q-OQd{jTav6yO--G;u3HsOIJ+))L+iny^U*+mpFK#Joo(G789E*> jOq}x|A$Hv!9yI;~FbO%In{hR}00000NkvXXu0mjf9GWNq literal 0 HcmV?d00001 diff --git a/assets/images/flags/rou.png b/assets/images/flags/rou.png new file mode 100644 index 0000000000000000000000000000000000000000..49b36b438d254a3a8a5c957df494a81db4b1f9cc GIT binary patch literal 705 zcmV;y0zUnTP)xK~#7F)mXc( z8Zi*Hy-N@hh?hi>f{UoqrJ$vt$tUn7`2qeSe~^Zspo@^8i3Dj7B3D#MJQNTQv8*%4 zVpe9ePo>@B5ncuv98tNL~hmK_I0xE*8Yzo6qMTe*F3Ji#R`t2N8V#CSHS|t-`~$#QOKw z&mTYGXL`Nf#U}->SL8+Y8j^1f^57Hgo=3ZQ!L#B4I#Zr-3^pev90y)P$cQKov2YB_ zz4g1@E@a(lqJ=`?)}wr2U4|jb2i9f0zv~HCfs6R(dMcO8 z!ZpBo>``Ah3^8$@Zw(dZZb` zG0@d?`>m%0ESyXxh@_u|BM60lrX21O7OXPc-2`sjHD_wD6Hk_K;|H|J8QS zdN!Livfu9?js&*l23yE#wQAOa#53Cx$y23LInHLYGdPKBx}1clU=S}F@bk4Gq2gmn nMTS`MLWw&C3D_kS9vFWCo`R#l57l*#00000NkvXXu0mjfNK_)&QxL`m$<=Z7P| zG_+m%eUh1pJvJZXiJ39cdx)YoolZSSrIdJPQ_wuy{vUHaOL{rmjF<+~=eq`(f1Y_w^dhW>XpaFbqYdQc-3c^UyaMjTF{FKl&@(~16u{2ZhIuU!xX zignp)wNjSpa=A=|5N~TRdw)0_R@mixljy3;Y*Xf!g9YN94!U4|xV0@h`GyyFQ+fs6R#cxt!X67+cd|3)$^<^;(d4HnvPM>U28y{eB;V6Fti1B4if^DtBpMo*x$yUVcm!Tr%V? kFFbm3Aptv8aPeU98x%(>K{(uA8UO$Q07*qoM6N<$g7b4YPXGV_ literal 0 HcmV?d00001 diff --git a/assets/images/flags/saf.png b/assets/images/flags/saf.png new file mode 100644 index 0000000000000000000000000000000000000000..3b9cbded835862dc7257ce7bfb0d046fbe6ea1d3 GIT binary patch literal 1319 zcmV+?1=#wDP)>+g5q)Jt~@o3wtDoAc|vzkB-W@0{PS z=Nw?a6r;=M^O?BSG)>9pV{n=D{)fD4(zT`>-z=CA!_cL;-EL)jds}I3ZN=2olz7H= zI2=|M78bOeABLu;CecS_Wu+$lcXoCZo6V-l=lmYu>*(mvxF5dH_w@ZrduC<^v$M0A z?(S}Fbaa&a$ml^C(40slELN-4oJ=N_R4S#EmzOiN_(HZV7K^sIxru|6`;N39)K67a zjqFTSRaKhW^yK7ZN>LOMVi3JK7z~z-KkN97(2vW`T$Q!(w4O!Z*;kNS{R`2rKF8MZ z4g4P>>LssQagxlG-|tVG$>3o!$K!Ezt@+r1-Z>Kg8~%k~@Zr=qa8}o&{l%AHKX)FP z=r)q`zoVF<2s%E9#NMHi&4fasj7dIVSWjKp@TEX_4W7|=apI~MAOAUvN3ZnbvC%J3 z_QH7-Q_Li7lm5Z*6G1FKGY7XLio<|^jBR9rt*4(xvBXKZ50odd_c|rYRt$GeFA8W^ z={@8F?!5UXyaXMzs;GPKCY)D%Q0wbaNNsFvKvoYiM;jU%7})aV_kp*DZbz}SmV)Q0 zG<+w+aM_ahIa-QH%6wl(KZO=BG;thB+pS% zRH=qhKvmns#Ke}~(ewNx=(~6fu38l%tCi?qtU(}NlDFgC3UQIDMGc}~e}?$<6po^f zk3KnOx7*`XpHl~dvRt)h0xlE43^}suAmG}(oy(GTxuXk?(lQ)H?d|Oql=VAV(3X}K z3l-f`BTzlCk6UOT%+Lj1!ka`0j^rwnF49X@!;v z|3SxT&hAPxOtL~smM8g*q7)n?N11}YS1ZudWhXcjdjF`yV9<$TOOAFH)ai6$etsTC zfR`_nqvwJhpqkmgSSF$<*B3jFOU){SiG3aA@V>0Q;|vA+UTGM-c~) zk|`U}`x0rlepZM~9|=+Y3_N)K9J%!?`UePdWeZ1D+vCWti5WDzcy^z%ih9YdKU@ay zbS?am{KMyNIz@mli`GB30`Ik3)Gnitk_&hdQ8wDz+Hhx}S^#!FBw*(og$HCsA`xwF zZcf;$q~=9Q=XBG7fc&pB+}$exd1yPkiUTO+a5xlwM8(kaHp3sW8NWcu!K-{ul*ru# zdf)yQgEuBoY*A^-&~`-FBBHbb%Z!hYr*Y=VWx{UM1IkRsJym4#;QNyTw&>EdW!l4~ zDVn^w)Anud7<_$wU8Vnh6Hf)+;K}#c=6gnc>NjURuXxDV&g~=DS1;+y@0-nLEf$NV zTrL;yml*Wltq6f;=w_0_o#2H4ZCypn7j+ybml(Hv>F@9P#-z>5GHz*svqSr^tgpP- zm)pk1M$s3y{8{=EWTK{~CLRa`lFWp??Xr=w2L`!^2J-XXfrKgdnb9Fb&f$d#c@8Al d&*K{d9$Klrzc(wol002ovPDHLkV1hg3gyjGL literal 0 HcmV?d00001 diff --git a/assets/images/flags/sgp.png b/assets/images/flags/sgp.png new file mode 100644 index 0000000000000000000000000000000000000000..5782ea1448b20532100edbaeb2afcab47b8cee9c GIT binary patch literal 902 zcmV;119|+3P)kkaJP6fR zPmO}1)KU=w`O&2LakD$VZ_KQdHL1Nc><7bpGy7)p<(s#&Gegu3(W|VitXQy8O3^Q- zN3qQK|MayCYlUJ1VhSL`MJz5ZifXkg=H}*TYimnAgI_L}#m>%-4E-^ho}N}?jEsy( z9p7j)L^7F_`Z@ZcZDCm!EAzLgKNv|FOQM~2am$h0D z3?&a9JS6#dC8LAYM@re_MkbR9ezfP*BOg)X7ta3)Vs#UjE-GKC$@M{?R;&4R7*U{l z=N{ST&M0E?XO-M{A5?qm#Vf8KznZ}C@Nl=HI$=#9AAelFsnkm@-wwz@X{-c;2G-Fa zVZ#6^RA)=@*HQjw;jinX~Zi3;##G8I)*k7EW!47@aTf9v@G=I8Tyk57Ml z6cPBozsF}dkEb4FW!K@3ty3rz96oGWNCk822ftr>n1?gumEKH*#|X!o(6Q)m+qSIN z>+PwjDV&!;)DON)Ow{K_m*sA8hXx;Al||u!n;cSyMKRkuv32dIQYmFOHZ~efhK|(* zv9J>p6KX72)O9SAY>kbLHFtM+U2vk#ak&?Q3xn{bfv$HhB&_hWUY87^%L|M8Tu30U c*TsXzZ^Tmp`=k5dkN^Mx07*qoM6N<$g3Df|CjbBd literal 0 HcmV?d00001 diff --git a/assets/images/flags/swe.png b/assets/images/flags/swe.png new file mode 100644 index 0000000000000000000000000000000000000000..ef73086f6d3a0054edddd56f218f9f49a9908350 GIT binary patch literal 752 zcmV1OE&1Mruqmg_^`ew6Xv)N3<{uneG4e6s)DhXxpI1bBZvqHV6an#pp zwFLR0e(KZxDt$a2!(=iEJDrZ`^?KwZ)QJ*7%XQsUCX=x|&ttytvwS{Jq~#Bio=T;} zdcB69AwEXw*V@^(El>~3<#K>B7!HR%V@!q^kiE57EYe?|zP(4zw{QhMzq*4LHmx7Y5*r-tGIYyCROY;AK-Qw(7IEw*2an+4$$^yfnbK+n) z=p!T<5v4^8hGDf@fyy2cQTockFcb=fh?CJ%zu)ger5jDOSS&^nrSv2hEGAtD-@Jz3 zMf>LdkH#nA`RG}s%ZRE4Ycd{QdZJWdBGhc#s-se=0RMP*wS#}Z*8@CSy@PZ*&CZCL z2nrtN_bA~T{UNC{t~yy83p!8}WJDEf)3Ducg&tAK+QdT%?M!+^<HR0$gVJ-koRovX zz&Rq6t!iXTvR11}UnJ>S*&<1|TCE=E^SMWy;8ZRrA-XV#UmB?A(}e_&ze~Dgh+STA ixNspsc1afx3O@jfc(L7|H`M9?0000CjASwi(((^N zkHunQKA+Ry5T2v-|FyGiTd?kBvsss=+v#*1!!V?aiP>9|$>i+ic=we*FGvr3`}ipJ zn5ENdwOpGOxZY$h^8T=NAqiY?^em2|iw{%v@}9CtGXy!=!GR|?SELm_+{l9o)hCKg z{P6iD>4e$!Yf7b3@{swV$0;Yxz$AT6C!xRl{DpMFdcF1%iGxm06Z#W#f-20t00l07t=dw3JR-6=fEbZxZYICt= zB0mkLe$deuwP>(7!@fc?0X@7%*ft1di}{viiOpu?6pKaFOCaj`7d{j9xbwj7-eX^I z=_(}}FT7)u4J2By|7g2WI;Uk)w%hIPj-hN-gDqsCP>^du;#=7=$$dVb-;KxP131yK zT#kbOU=V&ZP~XP~2{Swv^~exF;pOKXW@-_a1yCCRNvk@`DYy=;m_`sRz)M>1t zovXQb*$0}c?x}8%)ipg`MRX7KsP%e14^nHbp4mKTo^AieT+fnT$~GZp05aTEr_)jU z{a&?NEgFx<<{9+ucB|&|xla8tYBrl@jdHoH#r||Usa!6ntC-|6^5ZI6bc}1eh_*llhK>ahW>~29MRuv z7X*Q3J+4$L5zA;a8ih(J<6>g={&Kne@U!{-7k6LC4gBu>{qd1yI2a6~fEBpjV=p2; zTpua zIK-kmO|(=h-Fs95)@2x?60j~KusizO7jg-ozkYgsJmDyC5wDJ?YPCwP0nQVT2IMf{ zJU-tOk2?J#$sJd2*3JSKYQT6@Si6SZZl`UJ8rCjeO7LZ}J!-ZdX$Cn4KAP^o^_+mk zv)L@tn*NX@h?xuLa1n1k$m+Y~j%Bb~tpZNAJcNRM^@IK_JnZBQaV3|D^crbf8)A$3 zzVGYf@fbE54dhE88Yc%n6ODQB%I?1KgcGN(Mxyb;XEr%PqS@m&+oS0DeBQ|6aCka1 z#8w(?A?x+JSql<2|FJt;`Q d?or`E@DFUqq)bc02A2Q;002ovPDHLkV1ibEI&J^} literal 0 HcmV?d00001 diff --git a/assets/images/flags/usa.png b/assets/images/flags/usa.png new file mode 100644 index 0000000000000000000000000000000000000000..a8c44ce75b7960e2d1cd9cd820ebba0ad23c2334 GIT binary patch literal 1220 zcmV;#1UvhQP)tvxTMOVXY3|4~ z-$zqVHt01gPcGv3dtv`|wzZX&mAc6pNUyVsx}l$qPEAFGK!1OesLlNZ5sd7(9w~e3;71bIO3tW)EoQjR{JvJ{Gbd2;byngjU%H zpfjt#J{E!?4}~u!-ll8wQF0T$@0JXng49M77#Ru4kb(p(2teK2w{Oye2akkcsbmT9 z`S}~Pyu2Zufq(ald(`)(MQ#I&u#G4tF^2N&%%Vu-ijaX?b8|O@WP-^i*`hP?_=Nao zW+rHBD=SLB|LQdgg$!~N$3J{afk2?S$dnVS+{0hDm{`(50;rJ^l^nqV~YHgopXCVkdMW}>D0}{&*H(EXn{hd3vXldzZ@qsKl6i%mHANo?M z)bRWL-HAdeD5h3F$f;q6q8NH1$?7dXTo)gbd3Z*?&>*>qU?s1$EhbD@-gLzQVt4Pp zLyL>w%ZdkKnF_oUV3A=SOJ|rQEWXj9cR&4rUUzxuDHdyG<^PobzIZK}K93pw3-b{} z4T4zpAmUUMbJXdB!syWTKXWu`x;ykA=e7f^sKtNHwYH%Lxg#bA3xEI;<|TZxv5z$U zSyoZS0ik{mQRM|}Pshi{wD|Q$)KlQ0SBIMx(V?Ls`t7|Bsh0rpyk2j+qJVYq2lWyl z9?#L1qOD`tn~#2^UczTQ^y-W&rzcgg^~vYdYfuMxQ52bIG)jeyH1!frPfzVkCL?)K zlUaLSVx&^3zo~~1i^baZQ6YQ}80?~%90-0!oomG~*f_cGLSFuRk@HvN+VUe8vi`cTt^RZ0q`ue(c z&QP%`Ar>|q4oh3G_*St@vNAk8e7?80R|O}!xGr}?>%pM&(LjA)JV@xBW!)YbIvy`{ idd`Cc;<`ONDEtG<61ko@ElBC1{gTOhJM(t>?VC5RZw8UV3We(u$0f|rSb7`SY2J^W2EPDIVqV;GRKF(z`y{X zqobpP3Hws1BzZg@CdTPK>YJRLWHb-;Q=ffap|7v6Lp&ZgXJ=>G{QNx4VcLljL8V+S zyWDQKQmIs=YPBl)d_E%0e~@&S%f+(UEF1;rJ3>FMovNw~bu$nM7$}WcELN2yiK#S* z?B#Si?OL9Dzk$^I^*n&4X#j=}tpc*%y#6RWI5jmzA7Btsa=naRl!@flt_r`B!w}E( zz}V#vaH%^3tpF}{KdzpFt7y=(ECw*96q3)D0}%R>f$mRh@G|BGy`n;^AUF|Z=U~5D zfT#J}@Hq1TqOl;Xr4(o-XcLmOh*A)fycY1o+Sdg5D$6j`DS>v{1+Bu)&JKv|VXZ6e za*{6Z54bzh31xQzE}qihN^b#L1;5{KrO%oGe6cjbr4dj-FOEV&D?noDEVK%RLc#QU zyaS*-vhs)jj+(0_2aHDVfk34&17w)n}5DRJ7EF5R!uA_ z>$U|I)C3}GRp#vmUDui2qnx!UO4~cWy)Q6bmfv7PE7xgMUvF){Rg&1p<`Lb<;u!RX%8W6 zMI&31eSLj=E|T;tY>{NStE+1-l}c5J6C50u>%qD(a9$dS=YtCg**RA4lEHC#A;TdT f5@c8J;z8gqB@7!k&4^c200000NkvXXu0mjfmiNpA literal 0 HcmV?d00001 diff --git a/assets/images/search_icon.png b/assets/images/search_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cf4ec0772226b405d143973ea158dc5b47cd739e GIT binary patch literal 469 zcmV;`0V@89P)kgkpwzN)sVf>i<9&|b3D&XfJqdNi zsckB$A>w$HSsFi9GeoK?!*MO}G+{N~dK>kH2W1UdsZ-h)#(;uPv)7{h1?C;j00000 LNkvXXu0mjfRFK2L literal 0 HcmV?d00001 diff --git a/lib/di.dart b/lib/di.dart index 763a82834..7391e59ca 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -38,7 +38,6 @@ 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'; import 'package:cake_wallet/src/screens/settings/settings.dart'; import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; import 'package:cake_wallet/src/screens/support/support_page.dart'; @@ -507,8 +506,6 @@ Future setup( getIt.registerFactory(() => FaqPage(getIt.get())); - getIt.registerFactory(() => LanguageListPage(getIt.get())); - getIt.registerFactoryParam( (type, _) => WalletRestoreViewModel(getIt.get(), getIt.get(param1: type), _walletInfoSource, diff --git a/lib/entities/fiat_currency.dart b/lib/entities/fiat_currency.dart index c5fa3a602..1e5c61e1e 100644 --- a/lib/entities/fiat_currency.dart +++ b/lib/entities/fiat_currency.dart @@ -1,43 +1,46 @@ import 'package:cw_core/enumerable_item.dart'; class FiatCurrency extends EnumerableItem with Serializable { - const FiatCurrency({String symbol}) : super(title: symbol, raw: symbol); + const FiatCurrency({String symbol, this.countryCode, this.fullName}) : super(title: symbol, raw: symbol); + + final String countryCode; + final String fullName; static List get all => _all.values.toList(); - static const aud = FiatCurrency(symbol: 'AUD'); - static const bgn = FiatCurrency(symbol: 'BGN'); - static const brl = FiatCurrency(symbol: 'BRL'); - static const cad = FiatCurrency(symbol: 'CAD'); - static const chf = FiatCurrency(symbol: 'CHF'); - static const cny = FiatCurrency(symbol: 'CNY'); - static const czk = FiatCurrency(symbol: 'CZK'); - static const eur = FiatCurrency(symbol: 'EUR'); - static const dkk = FiatCurrency(symbol: 'DKK'); - static const gbp = FiatCurrency(symbol: 'GBP'); - static const hkd = FiatCurrency(symbol: 'HKD'); - static const hrk = FiatCurrency(symbol: 'HRK'); - static const huf = FiatCurrency(symbol: 'HUF'); - static const idr = FiatCurrency(symbol: 'IDR'); - static const ils = FiatCurrency(symbol: 'ILS'); - static const inr = FiatCurrency(symbol: 'INR'); - static const isk = FiatCurrency(symbol: 'ISK'); - static const jpy = FiatCurrency(symbol: 'JPY'); - static const krw = FiatCurrency(symbol: 'KRW'); - static const mxn = FiatCurrency(symbol: 'MXN'); - static const myr = FiatCurrency(symbol: 'MYR'); - static const nok = FiatCurrency(symbol: 'NOK'); - static const nzd = FiatCurrency(symbol: 'NZD'); - static const php = FiatCurrency(symbol: 'PHP'); - static const pln = FiatCurrency(symbol: 'PLN'); - static const ron = FiatCurrency(symbol: 'RON'); - static const rub = FiatCurrency(symbol: 'RUB'); - static const sek = FiatCurrency(symbol: 'SEK'); - static const sgd = FiatCurrency(symbol: 'SGD'); - static const thb = FiatCurrency(symbol: 'THB'); - static const usd = FiatCurrency(symbol: 'USD'); - static const zar = FiatCurrency(symbol: 'ZAR'); - static const vef = FiatCurrency(symbol: 'VEF'); + static const aud = FiatCurrency(symbol: 'AUD', countryCode: "aus", fullName: "Australian Dollar"); + static const bgn = FiatCurrency(symbol: 'BGN', countryCode: "bgr", fullName: "Bulgarian Lev"); + static const brl = FiatCurrency(symbol: 'BRL', countryCode: "bra", fullName: "Brazilian Real"); + static const cad = FiatCurrency(symbol: 'CAD', countryCode: "cad", fullName: "Canadian Dollar"); + static const chf = FiatCurrency(symbol: 'CHF', countryCode: "che", fullName: "Swiss Franc"); + static const cny = FiatCurrency(symbol: 'CNY', countryCode: "chn", fullName: "Chinese Yuan"); + static const czk = FiatCurrency(symbol: 'CZK', countryCode: "czk", fullName: "Czech Koruna"); + static const eur = FiatCurrency(symbol: 'EUR', countryCode: "eur", fullName: "Euro"); + static const dkk = FiatCurrency(symbol: 'DKK', countryCode: "dnk", fullName: "Danish Krone"); + static const gbp = FiatCurrency(symbol: 'GBP', countryCode: "gbr", fullName: "Pound sterling"); + static const hkd = FiatCurrency(symbol: 'HKD', countryCode: "hkg", fullName: "Hong Kong Dollar"); + static const hrk = FiatCurrency(symbol: 'HRK', countryCode: "hrv", fullName: "Croatian Kuna"); + static const huf = FiatCurrency(symbol: 'HUF', countryCode: "hun", fullName: "Hungarian Forint"); + static const idr = FiatCurrency(symbol: 'IDR', countryCode: "idn", fullName: "Indonesian Rupiah"); + static const ils = FiatCurrency(symbol: 'ILS', countryCode: "isr", fullName: "Israeli New Shekel"); + static const inr = FiatCurrency(symbol: 'INR', countryCode: "ind", fullName: "Indian Rupee"); + static const isk = FiatCurrency(symbol: 'ISK', countryCode: "isl", fullName: "Icelandic Króna"); + static const jpy = FiatCurrency(symbol: 'JPY', countryCode: "jpn", fullName: "Japanese Yen equals"); + static const krw = FiatCurrency(symbol: 'KRW', countryCode: "kor", fullName: "South Korean won"); + static const mxn = FiatCurrency(symbol: 'MXN', countryCode: "mex", fullName: "Mexican Peso"); + static const myr = FiatCurrency(symbol: 'MYR', countryCode: "mys", fullName: "Malaysian Ringgit"); + static const nok = FiatCurrency(symbol: 'NOK', countryCode: "nor", fullName: "Norwegian Krone"); + static const nzd = FiatCurrency(symbol: 'NZD', countryCode: "nzl", fullName: "New Zealand Dollar"); + static const php = FiatCurrency(symbol: 'PHP', countryCode: "phl", fullName: "Philippine peso"); + static const pln = FiatCurrency(symbol: 'PLN', countryCode: "pol", fullName: "Poland złoty"); + static const ron = FiatCurrency(symbol: 'RON', countryCode: "rou", fullName: "Romanian Leu"); + static const rub = FiatCurrency(symbol: 'RUB', countryCode: "rus", fullName: "Russian Ruble"); + static const sek = FiatCurrency(symbol: 'SEK', countryCode: "swe", fullName: "Swedish Krona"); + static const sgd = FiatCurrency(symbol: 'SGD', countryCode: "sgp", fullName: "Singapore Dollar"); + static const thb = FiatCurrency(symbol: 'THB', countryCode: "tha", fullName: "Thai Baht"); + static const usd = FiatCurrency(symbol: 'USD', countryCode: "usa", fullName: "United States Dollar"); + static const zar = FiatCurrency(symbol: 'ZAR', countryCode: "saf", fullName: "South African Rand"); + static const vef = FiatCurrency(symbol: 'VEF', countryCode: "ven", fullName: "Venezuelan Bolívar"); static final _all = { FiatCurrency.aud.raw: FiatCurrency.aud, diff --git a/lib/entities/language_service.dart b/lib/entities/language_service.dart index fc3349dee..367223e0d 100644 --- a/lib/entities/language_service.dart +++ b/lib/entities/language_service.dart @@ -20,6 +20,25 @@ class LanguageService { 'hr': 'Hrvatski (Croatian)', 'it': 'Italiano (Italian)' }; + + static const Map localeCountryCode = { + 'en': 'usa', + 'de': 'deu', + 'es': 'esp', + 'fr': 'fra', + 'hi': 'ind', + 'ja': 'jpn', + 'ko': 'kor', + 'nl': 'nld', + 'pl': 'pol', + 'pt': 'prt', + 'ru': 'rus', + 'uk': 'ukr', + 'zh': 'chn', + 'hr': 'hrv', + 'it': 'ita' + }; + static final list = {}; static void loadLocaleList() { diff --git a/lib/router.dart b/lib/router.dart index 01443a138..a3c7573ea 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -47,7 +47,6 @@ import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_c import 'package:cake_wallet/src/screens/contact/contact_list_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_page.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; -import 'package:cake_wallet/src/screens/settings/change_language.dart'; import 'package:cake_wallet/src/screens/restore/restore_wallet_from_seed_details.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; import 'package:cake_wallet/src/screens/settings/settings.dart'; @@ -359,10 +358,6 @@ Route createRoute(RouteSettings settings) { case Routes.faq: return MaterialPageRoute(builder: (_) => getIt.get()); - case Routes.changeLanguage: - return MaterialPageRoute( - builder: (_) => getIt.get()); - case Routes.preSeed: return MaterialPageRoute( builder: (_) => diff --git a/lib/routes.dart b/lib/routes.dart index eea2b7488..1200d558a 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -41,7 +41,6 @@ class Routes { static const unlock = '/auth_not_closable'; static const rescan = '/rescan'; static const faq = '/faq'; - static const changeLanguage = '/change_language'; static const newWalletType = '/new_wallet_type'; static const sendTemplate = '/send_template'; static const exchangeTemplate = '/exchange_template'; diff --git a/lib/src/screens/settings/change_language.dart b/lib/src/screens/settings/change_language.dart deleted file mode 100644 index d4c7c36d8..000000000 --- a/lib/src/screens/settings/change_language.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:cake_wallet/src/screens/settings/widgets/language_row.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; -import 'package:cake_wallet/store/settings_store.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'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/entities/language_service.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; - -class LanguageListPage extends BasePage { - LanguageListPage(this.settingsStore); - - final SettingsStore settingsStore; - - @override - String get title => S.current.settings_change_language; - - @override - Widget body(BuildContext context) { - return Container( - padding: EdgeInsets.only(top: 10.0), - child: SectionStandardList( - sectionCount: 1, - context: context, - itemCounter: (int sectionIndex) => LanguageService.list.values.length, - itemBuilder: (_, sectionIndex, index) { - return Observer(builder: (BuildContext context) { - final item = LanguageService.list.values.elementAt(index); - final code = LanguageService.list.keys.elementAt(index); - final isCurrent = code == settingsStore.languageCode ?? false; - - return LanguageRow( - title: item, - isSelected: isCurrent, - handler: (context) async { - if (!isCurrent) { - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).change_language, - alertContent: - S.of(context).change_language_to(item), - rightButtonText: S.of(context).change, - leftButtonText: S.of(context).cancel, - actionRightButton: () { - settingsStore.languageCode = code; - Navigator.of(context).pop(); - }, - actionLeftButton: () => - Navigator.of(context).pop()); - }); - } - }, - ); - }); - }, - )); - } -} diff --git a/lib/src/screens/settings/settings.dart b/lib/src/screens/settings/settings.dart index feda1cf61..168b8d297 100644 --- a/lib/src/screens/settings/settings.dart +++ b/lib/src/screens/settings/settings.dart @@ -1,4 +1,6 @@ +import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart'; +import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; import 'package:cake_wallet/view_model/settings/version_list_item.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; @@ -46,6 +48,10 @@ class SettingsPage extends BasePage { selectedItem: item.selectedItem(), items: item.items, onItemSelected: (dynamic value) => item.onItemSelected(value), + images: item.images, + searchHintText: item.searchHintText, + isGridView: item.isGridView, + matchingCriteria: (dynamic value, String searchText) => item.matchingCriteria(value, searchText), ); }); } @@ -80,6 +86,10 @@ class SettingsPage extends BasePage { }); } + if (item is ChoicesListItem) { + return SettingsChoicesCell(item); + } + return Container(); }); } diff --git a/lib/src/screens/settings/widgets/language_row.dart b/lib/src/screens/settings/widgets/language_row.dart deleted file mode 100644 index 0f807a843..000000000 --- a/lib/src/screens/settings/widgets/language_row.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:cake_wallet/palette.dart'; -import 'package:flutter/material.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; - -class LanguageRow extends StandardListRow { - LanguageRow({@required String title, @required this.isSelected, @required Function(BuildContext context) handler}) : - super(title: title, isSelected: isSelected, onTap: handler); - - @override - final bool isSelected; - - @override - Widget buildCenter(BuildContext context, {@required bool hasLeftOffset}) { - return Expanded( - child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [ - if (hasLeftOffset) SizedBox(width: 10), - Text(title, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: titleColor(context))) - ])); - } - - @override - Widget buildTrailing(BuildContext context) => - isSelected - ? Icon(Icons.done, color: Palette.blueCraiola) - : Offstage(); -} \ No newline at end of file diff --git a/lib/src/screens/settings/widgets/settings_choices_cell.dart b/lib/src/screens/settings/widgets/settings_choices_cell.dart new file mode 100644 index 000000000..abbd33231 --- /dev/null +++ b/lib/src/screens/settings/widgets/settings_choices_cell.dart @@ -0,0 +1,71 @@ +import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; +import 'package:flutter/material.dart'; + +class SettingsChoicesCell extends StatelessWidget { + const SettingsChoicesCell(this.choicesListItem, {Key key}) : super(key: key); + + final ChoicesListItem choicesListItem; + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).backgroundColor, + padding: EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + choicesListItem.title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).primaryTextTheme.title.color, + ), + ), + ], + ), + const SizedBox(height: 24), + Center( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Theme.of(context).accentTextTheme.display2.color, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: choicesListItem.items.map((dynamic e) { + final isSelected = choicesListItem.selectedItem == e; + return GestureDetector( + onTap: () { + choicesListItem.onItemSelected?.call(e); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 32, vertical: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: isSelected ? Theme.of(context).accentTextTheme.body2.color : null, + ), + child: Text( + choicesListItem.displayItem?.call(e) ?? e.toString(), + style: TextStyle( + color: isSelected ? Colors.white : Theme.of(context).primaryTextTheme.caption.color, + fontWeight: isSelected ? FontWeight.w700 : FontWeight.normal, + ), + ), + ), + ); + }).toList(), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/settings/widgets/settings_picker_cell.dart b/lib/src/screens/settings/widgets/settings_picker_cell.dart index 691b73835..6116a6ea5 100644 --- a/lib/src/screens/settings/widgets/settings_picker_cell.dart +++ b/lib/src/screens/settings/widgets/settings_picker_cell.dart @@ -2,7 +2,6 @@ import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; -import 'package:cake_wallet/generated/i18n.dart'; class SettingsPickerCell extends StandardListRow { SettingsPickerCell( @@ -10,29 +9,43 @@ class SettingsPickerCell extends StandardListRow { @required this.displayItem, this.selectedItem, this.items, + this.images, + this.searchHintText, + this.isGridView = false, + this.matchingCriteria, this.onItemSelected}) : super( - title: title, - isSelected: false, - onTap: (BuildContext context) async { - final selectedAtIndex = items.indexOf(selectedItem); + title: title, + isSelected: false, + onTap: (BuildContext context) async { + final selectedAtIndex = items.indexOf(selectedItem); - await showPopUp( - context: context, - builder: (_) => Picker( - items: items, - displayItem: displayItem, - selectedAtIndex: selectedAtIndex, - title: S.current.please_select, - mainAxisAlignment: MainAxisAlignment.center, - onItemSelected: (ItemType item) => - onItemSelected?.call(item))); - }); + await showPopUp( + context: context, + builder: (_) => Picker( + items: items, + displayItem: displayItem, + selectedAtIndex: selectedAtIndex, + mainAxisAlignment: MainAxisAlignment.start, + onItemSelected: (ItemType item) => onItemSelected?.call(item), + images: images, + isSeparated: false, + hintText: searchHintText, + isGridView: isGridView, + matchingCriteria: matchingCriteria, + ), + ); + }, + ); final ItemType selectedItem; final List items; final void Function(ItemType item) onItemSelected; final String Function(ItemType item) displayItem; + final List images; + final String searchHintText; + final bool isGridView; + final bool Function(ItemType, String) matchingCriteria; @override Widget buildTrailing(BuildContext context) { @@ -40,9 +53,7 @@ class SettingsPickerCell extends StandardListRow { displayItem?.call(selectedItem) ?? selectedItem.toString(), textAlign: TextAlign.right, style: TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.w500, - color: Theme.of(context).primaryTextTheme.overline.color), + fontSize: 14.0, fontWeight: FontWeight.w500, color: Theme.of(context).primaryTextTheme.overline.color), ); } } diff --git a/lib/src/widgets/alert_background.dart b/lib/src/widgets/alert_background.dart index db73e7c0d..e8368e27c 100644 --- a/lib/src/widgets/alert_background.dart +++ b/lib/src/widgets/alert_background.dart @@ -10,15 +10,19 @@ class AlertBackground extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - height: double.infinity, - width: double.infinity, - color: Colors.transparent, - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), - child: Container( - decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)), - child: child, + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: Container( + height: double.infinity, + width: double.infinity, + color: Colors.transparent, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), + child: Container( + decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)), + child: child, + ), ), ), ); diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index 3748bf28f..b5cad676b 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -2,7 +2,6 @@ import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart'; -import 'package:cake_wallet/src/widgets/cake_scrollbar.dart'; import 'package:cake_wallet/src/widgets/alert_close_button.dart'; import 'package:cake_wallet/palette.dart'; @@ -10,13 +9,19 @@ class Picker extends StatefulWidget { Picker({ @required this.selectedAtIndex, @required this.items, - @required this.title, @required this.onItemSelected, + this.title, this.displayItem, this.images, this.description, this.mainAxisAlignment = MainAxisAlignment.start, - }); + this.isGridView = false, + this.isSeparated = true, + this.hintText, + this.matchingCriteria, + }) : assert(hintText == null || + matchingCriteria != + null); // make sure that if the search field is enabled then there is a searching criteria provided final int selectedAtIndex; final List items; @@ -26,6 +31,10 @@ class Picker extends StatefulWidget { final Function(Item) onItemSelected; final MainAxisAlignment mainAxisAlignment; final String Function(Item) displayItem; + final bool isGridView; + final bool isSeparated; + final String hintText; + final bool Function(Item, String) matchingCriteria; @override PickerState createState() => PickerState(items, images, onItemSelected); @@ -35,8 +44,10 @@ class PickerState extends State { PickerState(this.items, this.images, this.onItemSelected); final Function(Item) onItemSelected; - final List items; - final List images; + List items; + List images; + + final TextEditingController searchController = TextEditingController(); final closeButton = Image.asset( 'assets/images/close.png', @@ -44,161 +55,248 @@ class PickerState extends State { ); ScrollController controller = ScrollController(); - final double backgroundHeight = 193; - final double thumbHeight = 72; - double fromTop = 0; + @override + void initState() { + super.initState(); + + searchController.addListener(() { + items = []; + images = []; + for (int i=0;i 3 : false; - return AlertBackground( - child: Stack( - alignment: Alignment.center, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: EdgeInsets.only(left: 24, right: 24), - child: Text( - widget.title, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none, - color: Colors.white), - ), - ), - Padding( - padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: GestureDetector( - onTap: () => null, - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(14)), - child: Container( - height: 233, + child: Stack( + alignment: Alignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.title?.isNotEmpty ?? false) + Container( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Text( + widget.title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.white, + ), + ), + ), + Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: GestureDetector( + onTap: () => null, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: Container( color: Theme.of(context).accentTextTheme.title.color, - child: Stack( - alignment: Alignment.center, - children: [ - ListView.separated( - padding: EdgeInsets.all(0), - controller: controller, - separatorBuilder: (context, index) => Divider( - color: Theme.of(context) - .accentTextTheme - .title - .backgroundColor, - height: 1, - ), - itemCount: items == null ? 0 : items.length, - itemBuilder: (context, index) { - final item = items[index]; - final image = - images != null ? images[index] : null; - final isItemSelected = - index == widget.selectedAtIndex; - - final color = isItemSelected - ? Theme.of(context).textTheme.body2.color - : Theme.of(context) - .accentTextTheme - .title - .color; - final textColor = isItemSelected - ? Palette.blueCraiola - : Theme.of(context) - .primaryTextTheme - .title - .color; - - return GestureDetector( - onTap: () { - if (onItemSelected == null) { - return; - } - Navigator.of(context).pop(); - onItemSelected(item); - }, - child: Container( - height: 77, - padding: EdgeInsets.only(left: 24, right: 24), - color: color, - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: widget.mainAxisAlignment, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - image ?? Offstage(), - Padding( - padding: EdgeInsets.only( - left: image != null ? 12 : 0), - child: Text( - widget.displayItem?.call(item) ?? - item.toString(), - style: TextStyle( - fontSize: 18, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: textColor, - decoration: TextDecoration.none, - ), - ), - ) - ], + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.65, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.hintText != null) + Padding( + padding: const EdgeInsets.all(16), + child: TextFormField( + controller: searchController, + style: TextStyle(color: Theme.of(context).primaryTextTheme.title.color), + decoration: InputDecoration( + hintText: widget.hintText, + prefixIcon: Image.asset("assets/images/search_icon.png"), + filled: true, + fillColor: Theme.of(context).accentTextTheme.display2.color, + alignLabelWithHint: false, + contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: const BorderSide( + color: Colors.transparent, + )), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: const BorderSide( + color: Colors.transparent, + )), ), ), - ); - }, - ), - ((widget.description != null) && - (widget.description.isNotEmpty)) - ? Positioned( - bottom: 24, - left: 24, - right: 24, - child: Text( - widget.description, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - fontFamily: 'Lato', - decoration: TextDecoration.none, - color: Theme.of(context) - .primaryTextTheme - .title - .color), - )) - : Offstage(), - isShowScrollThumb - ? CakeScrollbar( - backgroundHeight: backgroundHeight, - thumbHeight: thumbHeight, - fromTop: fromTop) - : Offstage(), - ], - )), + ), + Divider( + color: Theme.of(context).accentTextTheme.title.backgroundColor, + height: 1, + ), + if (widget.selectedAtIndex != -1) buildSelectedItem(), + Flexible( + child: Stack( + alignment: Alignment.center, + children: [ + (items?.length ?? 0) > 3 ? Scrollbar( + controller: controller, + child: itemsList(), + ) : itemsList(), + (widget.description?.isNotEmpty ?? false) + ? Positioned( + bottom: 24, + left: 24, + right: 24, + child: Text( + widget.description, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + fontFamily: 'Lato', + decoration: TextDecoration.none, + color: Theme.of(context).primaryTextTheme.title.color, + ), + ), + ) + : Offstage(), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ) + ], + ), + AlertCloseButton(image: closeButton) + ], + ), + ); + } + + Widget itemsList() { + return Container( + color: Theme.of(context).accentTextTheme.headline6.backgroundColor, + child: widget.isGridView + ? GridView.builder( + padding: EdgeInsets.zero, + controller: controller, + itemCount: items == null || items.isEmpty ? 0 : items.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 2, + childAspectRatio: 3, + ), + itemBuilder: (context, index) => buildItem(index), + ) + : ListView.separated( + padding: EdgeInsets.zero, + controller: controller, + shrinkWrap: true, + separatorBuilder: (context, index) => widget.isSeparated + ? Divider( + color: Theme.of(context).accentTextTheme.title.backgroundColor, + height: 1, + ) + : const SizedBox(), + itemCount: items == null || items.isEmpty ? 0 : items.length, + itemBuilder: (context, index) => buildItem(index), + ), + ); + } + + Widget buildItem(int index) { + /// don't show selected item in the list view + if (widget.items[widget.selectedAtIndex] == items[index] && !widget.isGridView) { + return const SizedBox(); + } + + final item = items[index]; + final image = images != null ? images[index] : null; + + return GestureDetector( + onTap: () { + if (onItemSelected == null) { + return; + } + Navigator.of(context).pop(); + onItemSelected(item); + }, + child: Container( + height: 55, + color: Theme.of(context).accentTextTheme.headline6.color, + padding: EdgeInsets.only(left: 24, right: 24), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: widget.mainAxisAlignment, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + image ?? Offstage(), + Expanded( + child: Padding( + padding: EdgeInsets.only(left: image != null ? 12 : 0), + child: Text( + widget.displayItem?.call(item) ?? item.toString(), + style: TextStyle( + fontSize: 14, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).primaryTextTheme.title.color, + decoration: TextDecoration.none, + ), ), ), - ) + ), ], ), - AlertCloseButton(image: closeButton) - ], - )); + ), + ); + } + + Widget buildSelectedItem() { + final item = widget.items[widget.selectedAtIndex]; + final image = images != null ? widget.images[widget.selectedAtIndex] : null; + + return Container( + height: 55, + color: Theme.of(context).accentTextTheme.headline6.color, + padding: EdgeInsets.only(left: 24, right: 24), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: widget.mainAxisAlignment, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + image ?? Offstage(), + Expanded( + child: Padding( + padding: EdgeInsets.only(left: image != null ? 12 : 0), + child: Text( + widget.displayItem?.call(item) ?? item.toString(), + style: TextStyle( + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w700, + color: Theme.of(context).primaryTextTheme.title.color, + decoration: TextDecoration.none, + ), + ), + ), + ), + Icon(Icons.check_circle, color: Theme.of(context).accentTextTheme.body2.color), + ], + ), + ); } } diff --git a/lib/src/widgets/standard_list.dart b/lib/src/widgets/standard_list.dart index c12f1b347..572f03791 100644 --- a/lib/src/widgets/standard_list.dart +++ b/lib/src/widgets/standard_list.dart @@ -37,11 +37,15 @@ class StandardListRow extends StatelessWidget { return Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [ if (hasLeftOffset) SizedBox(width: 10), - Text(title, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: titleColor(context))) + Expanded( + child: Text(title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: titleColor(context), + ), + ), + ) ])); } diff --git a/lib/src/widgets/standart_switch.dart b/lib/src/widgets/standart_switch.dart index 8760279a1..06d13d6aa 100644 --- a/lib/src/widgets/standart_switch.dart +++ b/lib/src/widgets/standart_switch.dart @@ -24,7 +24,7 @@ class StandartSwitchState extends State { height: 28, decoration: BoxDecoration( color: widget.value - ? Colors.green + ? Theme.of(context).accentTextTheme.body2.color : Theme.of(context).accentTextTheme.display4.color, borderRadius: BorderRadius.all(Radius.circular(14.0))), child: Container( diff --git a/lib/themes/bright_theme.dart b/lib/themes/bright_theme.dart index 5adb5d2f1..67f4e20bf 100644 --- a/lib/themes/bright_theme.dart +++ b/lib/themes/bright_theme.dart @@ -78,6 +78,14 @@ class BrightTheme extends ThemeBase { decorationColor: Colors.white, // menu background ) ), + scrollbarTheme: ScrollbarThemeData( + thumbColor: MaterialStateProperty.all(Palette.moderatePurpleBlue), + trackColor: MaterialStateProperty.all(Palette.periwinkleCraiola), + radius: Radius.circular(3), + thickness: MaterialStateProperty.all(6), + isAlwaysShown: true, + crossAxisMargin: 6, + ), primaryTextTheme: TextTheme( title: TextStyle( color: Palette.darkBlueCraiola, // title color diff --git a/lib/themes/dark_theme.dart b/lib/themes/dark_theme.dart index 646afc964..3a1361913 100644 --- a/lib/themes/dark_theme.dart +++ b/lib/themes/dark_theme.dart @@ -77,6 +77,14 @@ class DarkTheme extends ThemeBase { decorationColor: PaletteDark.deepPurpleBlue, // menu background ) ), + scrollbarTheme: ScrollbarThemeData( + thumbColor: MaterialStateProperty.all(PaletteDark.wildBlueGrey), + trackColor: MaterialStateProperty.all(PaletteDark.violetBlue), + radius: Radius.circular(3), + thickness: MaterialStateProperty.all(6), + isAlwaysShown: true, + crossAxisMargin: 6, + ), primaryTextTheme: TextTheme( title: TextStyle( color: Colors.white, // title color diff --git a/lib/themes/light_theme.dart b/lib/themes/light_theme.dart index 763872efe..a00b07f91 100644 --- a/lib/themes/light_theme.dart +++ b/lib/themes/light_theme.dart @@ -78,6 +78,14 @@ class LightTheme extends ThemeBase { decorationColor: Colors.white, // menu background ) ), + scrollbarTheme: ScrollbarThemeData( + thumbColor: MaterialStateProperty.all(Palette.moderatePurpleBlue), + trackColor: MaterialStateProperty.all(Palette.periwinkleCraiola), + radius: Radius.circular(3), + thickness: MaterialStateProperty.all(6), + isAlwaysShown: true, + crossAxisMargin: 6, + ), primaryTextTheme: TextTheme( title: TextStyle( color: Palette.darkBlueCraiola, // title color diff --git a/lib/themes/theme_list.dart b/lib/themes/theme_list.dart index b4cea1391..06bdde3f7 100644 --- a/lib/themes/theme_list.dart +++ b/lib/themes/theme_list.dart @@ -4,7 +4,7 @@ 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 all = [brightTheme, lightTheme, darkTheme]; static final lightTheme = LightTheme(raw: 0); static final brightTheme = BrightTheme(raw: 1); diff --git a/lib/view_model/settings/choices_list_item.dart b/lib/view_model/settings/choices_list_item.dart new file mode 100644 index 000000000..040968597 --- /dev/null +++ b/lib/view_model/settings/choices_list_item.dart @@ -0,0 +1,25 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/view_model/settings/settings_list_item.dart'; +import 'package:flutter/material.dart'; + +class ChoicesListItem extends SettingsListItem { + ChoicesListItem( + {@required String title, + @required this.selectedItem, + @required this.items, + this.displayItem, + void Function(ItemType item) onItemSelected}) + : _onItemSelected = onItemSelected, + super(title); + + final ItemType selectedItem; + final List items; + final String Function(ItemType item) displayItem; + final void Function(ItemType item) _onItemSelected; + + void onItemSelected(dynamic item) { + if (item is ItemType) { + _onItemSelected?.call(item); + } + } +} diff --git a/lib/view_model/settings/picker_list_item.dart b/lib/view_model/settings/picker_list_item.dart index 0785232bb..ae4bfd128 100644 --- a/lib/view_model/settings/picker_list_item.dart +++ b/lib/view_model/settings/picker_list_item.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:cake_wallet/view_model/settings/settings_list_item.dart'; +import 'package:flutter/material.dart'; class PickerListItem extends SettingsListItem { PickerListItem( @@ -7,18 +8,34 @@ class PickerListItem extends SettingsListItem { @required this.selectedItem, @required this.items, this.displayItem, - void Function(ItemType item) onItemSelected}) + this.images, + this.searchHintText, + this.isGridView = false, + void Function(ItemType item) onItemSelected, + bool Function(ItemType item, String searchText) matchingCriteria}) : _onItemSelected = onItemSelected, + _matchingCriteria = matchingCriteria, super(title); final ItemType Function() selectedItem; final List items; final String Function(ItemType item) displayItem; final void Function(ItemType item) _onItemSelected; + final List images; + final String searchHintText; + final bool isGridView; + final bool Function(ItemType, String) _matchingCriteria; void onItemSelected(dynamic item) { if (item is ItemType) { _onItemSelected?.call(item); } } + + bool matchingCriteria(dynamic item, String searchText) { + if (item is ItemType) { + return _matchingCriteria?.call(item, searchText); + } + return true; + } } diff --git a/lib/view_model/settings/settings_view_model.dart b/lib/view_model/settings/settings_view_model.dart index 555b0d2a0..67cf35635 100644 --- a/lib/view_model/settings/settings_view_model.dart +++ b/lib/view_model/settings/settings_view_model.dart @@ -1,6 +1,7 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/entities/language_service.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/settings/link_list_item.dart'; +import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; import 'package:flutter/cupertino.dart'; import 'package:mobx/mobx.dart'; import 'package:package_info/package_info.dart'; @@ -75,7 +76,7 @@ abstract class SettingsViewModelBase with Store { //var connectYatUrl = YatLink.baseUrl + YatLink.signInSuffix; //final connectYatUrlParameters = // _yatStore.defineQueryParameters(); - + //if (connectYatUrlParameters.isNotEmpty) { // connectYatUrl += YatLink.queryParameter + connectYatUrlParameters; //} @@ -83,7 +84,7 @@ abstract class SettingsViewModelBase with Store { //var manageYatUrl = YatLink.baseUrl + YatLink.managePath; //final manageYatUrlParameters = // _yatStore.defineQueryParameters(); - + //if (manageYatUrlParameters.isNotEmpty) { // manageYatUrl += YatLink.queryParameter + manageYatUrlParameters; //} @@ -91,27 +92,41 @@ abstract class SettingsViewModelBase with Store { //var createNewYatUrl = YatLink.startFlowUrl; //final createNewYatUrlParameters = // _yatStore.defineQueryParameters(); - + //if (createNewYatUrlParameters.isNotEmpty) { // createNewYatUrl += '?sub1=' + createNewYatUrlParameters; //} - + sections = [ [ - PickerListItem( - title: S.current.settings_display_balance_as, - items: BalanceDisplayMode.all, - selectedItem: () => balanceDisplayMode, - onItemSelected: (BalanceDisplayMode mode) => - _settingsStore.balanceDisplayMode = mode), + SwitcherListItem( + title: S.current.settings_display_balance, + value: () => balanceDisplayMode == BalanceDisplayMode.displayableBalance, + onValueChange: (_, bool value) { + if (value) { + _settingsStore.balanceDisplayMode = BalanceDisplayMode.displayableBalance; + } else { + _settingsStore.balanceDisplayMode = BalanceDisplayMode.hiddenBalance; + } + }, + ), if (!isHaven) PickerListItem( title: S.current.settings_currency, + searchHintText: S.current.search_currency, items: FiatCurrency.all, selectedItem: () => fiatCurrency, onItemSelected: (FiatCurrency currency) => - setFiatCurrency(currency)), + setFiatCurrency(currency), + images: FiatCurrency.all.map( + (e) => Image.asset("assets/images/flags/${e.countryCode}.png")) + .toList(), + isGridView: true, + matchingCriteria: (FiatCurrency currency, String searchText) { + return currency.title.toLowerCase().contains(searchText) || currency.fullName.toLowerCase().contains(searchText); + }, + ), PickerListItem( title: S.current.settings_fee_priority, items: priorityForWalletType(wallet.type), @@ -150,10 +165,23 @@ abstract class SettingsViewModelBase with Store { } }); }), - RegularListItem( - title: S.current.settings_change_language, - handler: (BuildContext context) => - Navigator.of(context).pushNamed(Routes.changeLanguage), + PickerListItem( + title: S.current.settings_change_language, + searchHintText: S.current.search_language, + items: LanguageService.list.keys.toList(), + displayItem: (dynamic code) { + return LanguageService.list[code]; + }, + selectedItem: () => getIt.get().languageCode, + onItemSelected: (String code) { + getIt.get().languageCode = code; + }, + images: LanguageService.list.keys.map( + (e) => Image.asset("assets/images/flags/${LanguageService.localeCountryCode[e]}.png")) + .toList(), + matchingCriteria: (String code, String searchText) { + return LanguageService.list[code].toLowerCase().contains(searchText); + }, ), SwitcherListItem( title: S.current.settings_allow_biometrical_authentication, @@ -180,12 +208,12 @@ abstract class SettingsViewModelBase with Store { setAllowBiometricalAuthentication(value); } }), - PickerListItem( - title: S.current.color_theme, - items: ThemeList.all, - selectedItem: () => theme, - onItemSelected: (ThemeBase theme) => - _settingsStore.currentTheme = theme) + ChoicesListItem( + title: S.current.color_theme, + items: ThemeList.all, + selectedItem: theme, + onItemSelected: (ThemeBase theme) => _settingsStore.currentTheme = theme, + ), ], //[ //if (_yatStore.emoji.isNotEmpty) ...[ diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 8d1c098ae..83d461546 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -76,6 +76,7 @@ flutter: assets: - assets/images/ + - assets/images/flags/ - assets/node_list.yml - assets/haven_node_list.yml - assets/bitcoin_electrum_server_list.yml diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index fa34eeced..304e91a1b 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -228,7 +228,7 @@ "settings_nodes" : "Knoten", "settings_current_node" : "Aktueller Knoten", "settings_wallets" : "Wallets", - "settings_display_balance_as" : "Kontostand anzeigen in", + "settings_display_balance" : "Kontostand anzeigen", "settings_currency" : "Währung", "settings_fee_priority" : "Gebührenpriorität", "settings_save_recipient_address" : "Empfängeradresse speichern", @@ -530,6 +530,8 @@ "third_intro_content" : "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!", "learn_more" : "Erfahren Sie mehr", "search": "Suche", + "search_language": "Sprache suchen", + "search_currency": "Währung suchen", "new_template" : "neue Vorlage", "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 25c449929..2f9b0cf78 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -228,7 +228,7 @@ "settings_nodes" : "Nodes", "settings_current_node" : "Current node", "settings_wallets" : "Wallets", - "settings_display_balance_as" : "Display balance as", + "settings_display_balance" : "Display balance", "settings_currency" : "Currency", "settings_fee_priority" : "Fee priority", "settings_save_recipient_address" : "Save recipient address", @@ -530,6 +530,8 @@ "third_intro_content" : "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!", "learn_more" : "Learn More", "search": "Search", + "search_language": "Search language", + "search_currency": "Search currency", "new_template" : "New Template", "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index e520ff727..76284f46d 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -228,7 +228,7 @@ "settings_nodes" : "Nodos", "settings_current_node" : "Nodo actual", "settings_wallets" : "Carteras", - "settings_display_balance_as" : "Mostrar saldo como", + "settings_display_balance" : "Mostrar saldo", "settings_currency" : "Moneda", "settings_fee_priority" : "Prioridad de tasa", "settings_save_recipient_address" : "Guardar dirección del destinatario", @@ -530,6 +530,8 @@ "third_intro_content" : "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!", "learn_more" : "Aprende más", "search": "Búsqueda", + "search_language": "Idioma de búsqueda", + "search_currency": "Moneda de búsqueda", "new_template" : "Nueva plantilla", "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index af41bb225..32abb9ac7 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -226,7 +226,7 @@ "settings_nodes" : "Nœuds", "settings_current_node" : "Nœud actuel", "settings_wallets" : "Portefeuilles", - "settings_display_balance_as" : "Afficher le solde en", + "settings_display_balance" : "Afficher le solde", "settings_currency" : "Devise", "settings_fee_priority" : "Priorité des frais", "settings_save_recipient_address" : "Sauvegarder l'adresse du bénéficiaire", @@ -527,7 +527,9 @@ "third_intro_title" : "Yat joue bien avec les autres", "third_intro_content" : "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !", "learn_more" : "En savoir plus", - + "search": "Chercher", + "search_language": "Langue de recherche", + "search_currency": "Devise de recherche", "new_template" : "Nouveau Modèle", "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 9c61e180f..c8b738277 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -228,7 +228,7 @@ "settings_nodes" : "नोड्स", "settings_current_node" : "वर्तमान नोड", "settings_wallets" : "पर्स", - "settings_display_balance_as" : "के रूप में संतुलन प्रदर्शित करें", + "settings_display_balance" : "प्रदर्शन संतुलन", "settings_currency" : "मुद्रा", "settings_fee_priority" : "शुल्क प्राथमिकता", "settings_save_recipient_address" : "प्राप्तकर्ता का पता सहेजें", @@ -530,6 +530,8 @@ "third_intro_content" : "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!", "learn_more" : "और अधिक जानें", "search": "खोज", + "search_language": "भाषा खोजें", + "search_currency": "मुद्रा खोजें", "new_template" : "नया टेम्पलेट", "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 16142ccd3..48a0ef4e2 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -228,7 +228,7 @@ "settings_nodes" : "Nodovi", "settings_current_node" : "Trenutni node", "settings_wallets" : "Novčanik", - "settings_display_balance_as" : "Prikaži stanje računa kao", + "settings_display_balance" : "Prikaži stanje računa", "settings_currency" : "Valuta", "settings_fee_priority" : "Prioritet naknade", "settings_save_recipient_address" : "Spremi primateljevu adresu", @@ -530,6 +530,8 @@ "third_intro_content" : "Yats žive i izvan Cake Wallet -a. Bilo koja adresa novčanika na svijetu može se zamijeniti Yat!", "learn_more" : "Saznajte više", "search": "Traži", + "search_language": "Jezik pretraživanja", + "search_currency": "Traži valutu", "new_template" : "novi predložak", "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 10c22001a..cb21d43dd 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -228,7 +228,7 @@ "settings_nodes" : "Nodi", "settings_current_node" : "Nodo attuale", "settings_wallets" : "Portafogli", - "settings_display_balance_as" : "Mostra saldo come", + "settings_display_balance" : "Mostra saldo", "settings_currency" : "Moneta", "settings_fee_priority" : "Priorità commissione", "settings_save_recipient_address" : "Salva indirizzo di destinazione", @@ -530,6 +530,8 @@ "third_intro_content" : "Anche Yats vive fuori da Cake Wallet. Qualsiasi indirizzo di portafoglio sulla terra può essere sostituito con un Yat!", "learn_more" : "Impara di più", "search": "Ricerca", + "search_language": "Cerca lingua", + "search_currency": "Cerca valuta", "new_template" : "Nuovo modello", "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 3e2c3e886..6f162b8dc 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -228,7 +228,7 @@ "settings_nodes" : "ノード", "settings_current_node" : "現在のノード", "settings_wallets" : "財布", - "settings_display_balance_as" : "残高を表示", + "settings_display_balance" : "ディスプレイバランス", "settings_currency" : "通貨", "settings_fee_priority" : "料金優先", "settings_save_recipient_address" : "受信者のアドレスを保存", @@ -530,6 +530,8 @@ "third_intro_content" : "YatsはCakeWalletの外にも住んでいます。 地球上のどのウォレットアドレスもYatに置き換えることができます!", "learn_more" : "もっと詳しく知る", "search": "検索", + "search_language": "検索言語", + "search_currency": "検索通貨", "new_template" : "新しいテンプレート", "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index ad3e3ac5b..124edbd7c 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -228,7 +228,7 @@ "settings_nodes" : "노드", "settings_current_node" : "현재 노드", "settings_wallets" : "지갑", - "settings_display_balance_as" : "잔액 표시", + "settings_display_balance" : "디스플레이 잔액", "settings_currency" : "통화", "settings_fee_priority" : "수수료 우선", "settings_save_recipient_address" : "수신자 주소 저장", @@ -530,6 +530,8 @@ "third_intro_content" : "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!", "learn_more" : "더 알아보기", "search": "찾다", + "search_language": "검색 언어", + "search_currency": "통화 검색", "new_template" : "새 템플릿", "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다." } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 834da2c02..9ac238793 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -228,7 +228,7 @@ "settings_nodes" : "knooppunten", "settings_current_node" : "Huidige knooppunt", "settings_wallets" : "Portemonnee", - "settings_display_balance_as" : "Toon saldo als", + "settings_display_balance" : "Saldo weergeven", "settings_currency" : "Valuta", "settings_fee_priority" : "Tariefprioriteit", "settings_save_recipient_address" : "Adres ontvanger opslaan", @@ -530,6 +530,8 @@ "third_intro_content" : "Yats wonen ook buiten Cake Wallet. Elk portemonnee-adres op aarde kan worden vervangen door een Yat!", "learn_more" : "Kom meer te weten", "search": "Zoekopdracht", + "search_language": "Zoektaal", + "search_currency": "Zoek valuta", "new_template" : "Nieuwe sjabloon", "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 6bcccb1e1..d6db3d206 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -228,7 +228,7 @@ "settings_nodes" : "Węzły", "settings_current_node" : "Bieżący węzeł", "settings_wallets" : "Portfele", - "settings_display_balance_as" : "Wyświetl saldo jako", + "settings_display_balance" : "Wyświetl saldo", "settings_currency" : "Waluta", "settings_fee_priority" : "Priorytet opłaty", "settings_save_recipient_address" : "Zapisz adres odbiorcy", @@ -530,6 +530,8 @@ "third_intro_content" : "Yats mieszkają również poza Cake Wallet. Każdy adres portfela na ziemi można zastąpić Yat!", "learn_more" : "Ucz się więcej", "search": "Szukaj", + "search_language": "Wyszukaj język", + "search_currency": "Wyszukaj walutę", "new_template" : "Nowy szablon", "electrum_address_disclaimer": "Za każdym razem, gdy korzystasz z jednego z nich, generujemy nowe adresy, ale poprzednie adresy nadal działają" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 8737de6c0..f44ffaf6f 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -228,7 +228,7 @@ "settings_nodes" : "Nós", "settings_current_node" : "Nó atual", "settings_wallets" : "Carteiras", - "settings_display_balance_as" : "Saldo a exibir", + "settings_display_balance" : "Exibir saldo", "settings_currency" : "Moeda", "settings_fee_priority" : "Prioridade da taxa", "settings_save_recipient_address" : "Salvar endereço do destinatário", @@ -530,6 +530,8 @@ "third_intro_content" : "Yats também mora fora da Cake Wallet. Qualquer endereço de carteira na Terra pode ser substituído por um Yat!", "learn_more" : "Saber mais", "search": "Procurar", + "search_language": "Idioma de pesquisa", + "search_currency": "Pesquisar moeda", "new_template" : "Novo modelo", "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 7df0463fb..bbb3482b2 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -228,7 +228,7 @@ "settings_nodes" : "Ноды", "settings_current_node" : "Текущая нода", "settings_wallets" : "Кошельки", - "settings_display_balance_as" : "Отображать баланс как", + "settings_display_balance" : "Отображать баланс", "settings_currency" : "Валюта", "settings_fee_priority" : "Приоритет транзакции", "settings_save_recipient_address" : "Сохранять адрес получателя", @@ -530,6 +530,8 @@ "third_intro_content" : "Yat находятся за пределами Cake Wallet. Любой адрес кошелька на земле можно заменить на Yat!", "learn_more" : "Узнать больше", "search": "Поиск", + "search_language": "Язык поиска", + "search_currency": "Валюта поиска", "new_template" : "Новый шаблон", "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать." } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 2ef8cbb6b..ec13da291 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -227,7 +227,7 @@ "settings_nodes" : "Вузли", "settings_current_node" : "Поточний вузол", "settings_wallets" : "Гаманці", - "settings_display_balance_as" : "Відображати баланс як", + "settings_display_balance" : "Відображати баланс", "settings_currency" : "Валюта", "settings_fee_priority" : "Пріоритет транзакції", "settings_save_recipient_address" : "Зберігати адресу отримувача", @@ -529,6 +529,8 @@ "third_intro_content" : "Yat знаходиться за межами Cake Wallet. Будь-яку адресу гаманця на землі можна замінити на Yat!", "learn_more" : "Дізнатися більше", "search": "Пошук", + "search_language": "Мова пошуку", + "search_currency": "Шукати валюту", "new_template" : "Новий шаблон", "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index f8632ee98..d1cb625ee 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -228,7 +228,7 @@ "settings_nodes" : "节点数", "settings_current_node" : "当前节点", "settings_wallets" : "钱包", - "settings_display_balance_as" : "将余额显示为", + "settings_display_balance" : "显示余额为", "settings_currency" : "货币", "settings_fee_priority" : "交易优先级", "settings_save_recipient_address" : "保存收件人地址", @@ -528,6 +528,8 @@ "third_intro_content" : "Yats 也住在 Cake Wallet 之外。 地球上任何一個錢包地址都可以用一個Yat來代替!", "learn_more" : "了解更多", "search": "搜索", + "search_language": "搜索语言", + "search_currency": "搜索货币", "new_template" : "新模板", "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效" } From 02533d68a078fa1d3b11e819ff7dd93048353ff4 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 29 Jun 2022 20:21:21 +0300 Subject: [PATCH 14/22] prevent using same wallet name as existing (#391) * Add wallet name validation --- lib/core/validator.dart | 5 --- lib/core/wallet_name_validator.dart | 11 +++++ lib/entities/generate_name.dart | 8 +++- .../screens/new_wallet/new_wallet_page.dart | 23 +++++++--- .../restore_wallet_from_keys_page.dart | 2 +- .../restore_wallet_from_seed_details.dart | 2 +- .../wallet_restore_from_keys_form.dart | 2 +- .../wallet_restore_from_seed_form.dart | 10 +++-- .../screens/restore/wallet_restore_page.dart | 44 ++++++++++++++++++- lib/view_model/wallet_creation_vm.dart | 6 +++ res/values/strings_de.arb | 5 ++- res/values/strings_en.arb | 5 ++- res/values/strings_es.arb | 5 ++- res/values/strings_fr.arb | 5 ++- res/values/strings_hi.arb | 5 ++- res/values/strings_hr.arb | 5 ++- res/values/strings_it.arb | 5 ++- res/values/strings_ja.arb | 5 ++- res/values/strings_ko.arb | 5 ++- res/values/strings_nl.arb | 5 ++- res/values/strings_pl.arb | 5 ++- res/values/strings_pt.arb | 5 ++- res/values/strings_ru.arb | 5 ++- res/values/strings_uk.arb | 5 ++- res/values/strings_zh.arb | 5 ++- 25 files changed, 137 insertions(+), 51 deletions(-) create mode 100644 lib/core/wallet_name_validator.dart diff --git a/lib/core/validator.dart b/lib/core/validator.dart index fa867c1d6..055439347 100644 --- a/lib/core/validator.dart +++ b/lib/core/validator.dart @@ -40,8 +40,3 @@ class TextValidator extends Validator { bool match(String value) => RegExp(pattern).hasMatch(value); } - -class WalletNameValidator extends TextValidator { - WalletNameValidator() - : super(minLength: 1, maxLength: 15, pattern: '^[a-zA-Z0-9_]\$'); -} \ No newline at end of file diff --git a/lib/core/wallet_name_validator.dart b/lib/core/wallet_name_validator.dart new file mode 100644 index 000000000..32832ef0c --- /dev/null +++ b/lib/core/wallet_name_validator.dart @@ -0,0 +1,11 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/core/validator.dart'; + +class WalletNameValidator extends TextValidator { + WalletNameValidator() + : super( + errorMessage: S.current.error_text_wallet_name, + pattern: '^[a-zA-Z0-9\-_ ]+\$', + minLength: 1, + maxLength: 33); +} diff --git a/lib/entities/generate_name.dart b/lib/entities/generate_name.dart index 4aa570769..9376542d4 100644 --- a/lib/entities/generate_name.dart +++ b/lib/entities/generate_name.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:math'; import 'package:flutter/services.dart'; @@ -14,8 +15,11 @@ Future generateName() async { await rootBundle.loadString('assets/text/Wallet_Adjectives.txt'); final nounStringRaw = await rootBundle.loadString('assets/text/Wallet_Nouns.txt'); - final adjectives = List.from(adjectiveStringRaw.split('\n')); - final nouns = List.from(nounStringRaw.split('\n')); + + final ls = LineSplitter(); + final adjectives = ls.convert(adjectiveStringRaw); + final nouns = ls.convert(nounStringRaw); + final chosenAdjective = adjectives[randomThing.nextInt(adjectives.length)]; final chosenNoun = nouns[randomThing.nextInt(nouns.length)]; final returnString = diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index a709dc4ce..9455a76cd 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -7,7 +7,7 @@ import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/core/validator.dart'; +import 'package:cake_wallet/core/wallet_name_validator.dart'; import 'package:cake_wallet/src/widgets/seed_language_selector.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; @@ -218,10 +218,21 @@ class _WalletNameFormState extends State { if (!_formKey.currentState.validate()) { return; } - - _walletNewVM.create( - options: _walletNewVM.hasLanguageSelector - ? _languageSelectorKey.currentState.selected - : null); + if (_walletNewVM.nameExists(_walletNewVM.name)) { + showPopUp( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: '', + alertContent: S.of(context).wallet_name_exists, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } else { + _walletNewVM.create( + options: _walletNewVM.hasLanguageSelector + ? _languageSelectorKey.currentState.selected + : null); + } } } diff --git a/lib/src/screens/restore/restore_wallet_from_keys_page.dart b/lib/src/screens/restore/restore_wallet_from_keys_page.dart index fa5efdff6..f66c2807b 100644 --- a/lib/src/screens/restore/restore_wallet_from_keys_page.dart +++ b/lib/src/screens/restore/restore_wallet_from_keys_page.dart @@ -1,4 +1,4 @@ -import 'package:cake_wallet/core/validator.dart'; +import 'package:cake_wallet/core/wallet_name_validator.dart'; import 'package:cake_wallet/palette.dart'; import 'package:flutter/services.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/src/screens/restore/restore_wallet_from_seed_details.dart b/lib/src/screens/restore/restore_wallet_from_seed_details.dart index 6a245884d..c2bade522 100644 --- a/lib/src/screens/restore/restore_wallet_from_seed_details.dart +++ b/lib/src/screens/restore/restore_wallet_from_seed_details.dart @@ -3,7 +3,7 @@ import 'package:mobx/mobx.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/core/validator.dart'; +import 'package:cake_wallet/core/wallet_name_validator.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index d6f637422..1058380b7 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -8,7 +8,7 @@ import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/core/validator.dart'; +import 'package:cake_wallet/core/wallet_name_validator.dart'; import 'package:cake_wallet/entities/generate_name.dart'; class WalletRestoreFromKeysFrom extends StatefulWidget { 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 a54d48ee7..07405871c 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -10,7 +10,7 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/core/validator.dart'; +import 'package:cake_wallet/core/wallet_name_validator.dart'; class WalletRestoreFromSeedForm extends StatefulWidget { WalletRestoreFromSeedForm( @@ -41,6 +41,7 @@ class WalletRestoreFromSeedFormState extends State { WalletRestoreFromSeedFormState(this.language) : seedWidgetStateKey = GlobalKey(), blockchainHeightKey = GlobalKey(), + formKey = GlobalKey(), languageController = TextEditingController(), nameTextEditingController = TextEditingController(); @@ -48,6 +49,7 @@ class WalletRestoreFromSeedFormState extends State { final GlobalKey blockchainHeightKey; final TextEditingController languageController; final TextEditingController nameTextEditingController; + final GlobalKey formKey; String language; @override @@ -61,7 +63,9 @@ class WalletRestoreFromSeedFormState extends State { return Container( padding: EdgeInsets.only(left: 24, right: 24), child: Column(children: [ - Stack( + Form( + key: formKey, + child: Stack( alignment: Alignment.centerRight, children: [ BaseTextFormField( @@ -97,7 +101,7 @@ class WalletRestoreFromSeedFormState extends State { ), ), ], - ), + )), Container(height: 20), SeedWidget( key: seedWidgetStateKey, diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 708d3cc81..1f8e9a584 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -193,8 +193,7 @@ class WalletRestorePage extends BasePage { child: Observer( builder: (context) { return LoadingPrimaryButton( - onPressed: () => - walletRestoreViewModel.create(options: _credentials()), + onPressed: _confirmForm, text: S.of(context).restore_recover, color: Theme.of(context).accentTextTheme.subtitle.decorationColor, @@ -268,4 +267,45 @@ class WalletRestorePage extends BasePage { return credentials; } + + void _confirmForm() { + final formContext = walletRestoreViewModel.mode == WalletRestoreMode.seed + ? walletRestoreFromSeedFormKey.currentContext + : walletRestoreFromKeysFormKey.currentContext; + + final formKey = walletRestoreViewModel.mode == WalletRestoreMode.seed + ? walletRestoreFromSeedFormKey.currentState.formKey + : walletRestoreFromKeysFormKey.currentState.formKey; + + final name = walletRestoreViewModel.mode == WalletRestoreMode.seed + ? walletRestoreFromSeedFormKey + .currentState.nameTextEditingController.value.text + : walletRestoreFromKeysFormKey + .currentState.nameTextEditingController.value.text; + + if (!formKey.currentState.validate()) { + return; + } + + if (walletRestoreViewModel.nameExists(name)) { + showNameExistsAlert(formContext); + return; + } + + walletRestoreViewModel.create(options: _credentials()); + } + + Future showNameExistsAlert(BuildContext context) { + return showPopUp( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: '', + alertContent: S.of(context).wallet_name_exists, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } } + + diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index a3de16c1b..8a4c76eb5 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -32,6 +32,12 @@ abstract class WalletCreationVMBase with Store { final Box _walletInfoSource; final AppStore _appStore; + bool nameExists(String name) { + final walletNameList = _walletInfoSource.values.map((e) => e.name.toLowerCase()).toList(); + + return walletNameList.contains(name.toLowerCase()); + } + Future create({dynamic options}) async { try { state = IsExecutingState(); diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 304e91a1b..6d88ae671 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "Der Wert des Betrags darf den verfügbaren Kontostand nicht überschreiten.\nDie Anzahl der Nachkommastellen muss kleiner oder gleich 2 sein", "error_text_subaddress_name" : "Der Name der Unteradresse darf nicht die Zeichen ` , ' \" enthalten\nund muss zwischen 1 und 20 Zeichen lang sein", "error_text_amount" : "Betrag darf nur Zahlen enthalten", - "error_text_wallet_name" : "Der Walletname darf nur Buchstaben und Zahlen enthalten\nund muss zwischen 1 und 15 Zeichen lang sein", + "error_text_wallet_name" : "Der Wallet-Name darf nur Buchstaben, Zahlen und _- Symbole enthalten\nund muss zwischen 1 und 33 Zeichen lang sein", "error_text_keys" : "Walletschlüssel können nur 64 hexadezimale Zeichen enthalten", "error_text_crypto_currency" : "Die Anzahl der Nachkommastellen\nmuss kleiner oder gleich 12 sein.", "error_text_minimal_limit" : "Handel für ${provider} wird nicht erstellt. Menge ist unter dem Minimum: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "Sprache suchen", "search_currency": "Währung suchen", "new_template" : "neue Vorlage", - "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin" + "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin", + "wallet_name_exists": "Wallet mit diesem Namen existiert bereits" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 2f9b0cf78..639c97c97 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "Value of amount can't exceed available balance.\nThe number of fraction digits must be less or equal to 2", "error_text_subaddress_name" : "Subaddress name can't contain ` , ' \" symbols\nand must be between 1 and 20 characters long", "error_text_amount" : "Amount can only contain numbers", - "error_text_wallet_name" : "Wallet name can only contain letters, numbers\nand must be between 1 and 15 characters long", + "error_text_wallet_name" : "Wallet name can only contain letters, numbers, _ - symbols \nand must be between 1 and 33 characters long", "error_text_keys" : "Wallet keys can only contain 64 chars in hex", "error_text_crypto_currency" : "The number of fraction digits\nmust be less or equal to 12", "error_text_minimal_limit" : "Trade for ${provider} is not created. Amount is less then minimal: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "Search language", "search_currency": "Search currency", "new_template" : "New Template", - "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work" + "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work", + "wallet_name_exists": "Wallet with that name has already existed" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 76284f46d..efe78c145 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "El valor de la cantidad no puede exceder el saldo disponible.\nEl número de dígitos de fracción debe ser menor o igual a 2", "error_text_subaddress_name" : "El nombre de la subdirección no puede contener símbolos `, '\" \ny debe tener entre 1 y 20 caracteres de longitud", "error_text_amount" : "La cantidad solo puede contener números", - "error_text_wallet_name" : "El nombre de la billetera solo puede contener letras, números \ny debe tener entre 1 y 15 caracteres de longitud", + "error_text_wallet_name" : "El nombre de la billetera solo puede contener letras, números , _ - símbolos\ny debe tener entre 1 y 33 caracteres de longitud", "error_text_keys" : "Las llaves de billetera solo pueden contener 64 caracteres en hexadecimal", "error_text_crypto_currency" : "El número de dígitos fraccionarios \ndebe ser menor o igual a 12", "error_text_minimal_limit" : "El comercio por ${provider} no se crea. La cantidad es menos que mínima: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "Idioma de búsqueda", "search_currency": "Moneda de búsqueda", "new_template" : "Nueva plantilla", - "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando" + "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando", + "wallet_name_exists": "Wallet con ese nombre ya ha existido" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 32abb9ac7..01b12f791 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -325,7 +325,7 @@ "error_text_fiat" : "La valeur du montant ne peut dépasser le solde disponible.\nLa partie décimale doit comporter au plus 2 chiffres", "error_text_subaddress_name" : "Le nom de sous-adresse ne peut contenir les symboles ` , ' \"\net sa longueur doit être comprise entre 1 et 20 caractères", "error_text_amount" : "Le montant ne peut comporter que des nombres", - "error_text_wallet_name" : "Le nom du portefeuille ne peut contenir que des lettres et des chiffres\net sa longueur doit être comprise entre 1 et 15 caractères", + "error_text_wallet_name" : "Le nom du portefeuille ne peut contenir que des lettres, des chiffres, des symboles _ -\net doit comporter entre 1 et 33 caractères", "error_text_keys" : "Les clefs du portefeuille ne peuvent être constituées que de 64 caractères hexadécimaux", "error_text_crypto_currency" : "La partie décimale\ndoit comporter au plus 12 chiffres", "error_text_minimal_limit" : "Échange pour ${provider} non créé. Le montant est inférieur au minimum : ${min} ${currency}", @@ -531,5 +531,6 @@ "search_language": "Langue de recherche", "search_currency": "Devise de recherche", "new_template" : "Nouveau Modèle", - "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner" + "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner", + "wallet_name_exists": "Le portefeuille portant ce nom existe déjà" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index c8b738277..711e4d92c 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "राशि का मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या कम या 2 के बराबर होनी चाहिए", "error_text_subaddress_name" : "सबड्रेस नाम नहीं हो सकता` , ' \" प्रतीकों\nऔर 1 और 20 वर्णों के बीच लंबा होना चाहिए", "error_text_amount" : "राशि में केवल संख्याएँ हो सकती हैं", - "error_text_wallet_name" : "वॉलेट नाम में केवल अक्षर, संख्याएं हो सकती हैं\nऔर 1 और 15 वर्णों के बीच लंबा होना चाहिए", + "error_text_wallet_name" : "वॉलेट नाम में केवल अक्षर, संख्याएं, _ - प्रतीक हो सकते हैं\nऔर 1 और 33 वर्णों के बीच लंबा होना चाहिए", "error_text_keys" : "वॉलेट कीज़ में हेक्स में केवल 64 वर्ण हो सकते हैं", "error_text_crypto_currency" : "अंश अंकों की संख्या\n12 से कम या इसके बराबर होना चाहिए", "error_text_minimal_limit" : "व्यापार ${provider} के लिए नहीं बनाया गया है। राशि कम है तो न्यूनतम: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "भाषा खोजें", "search_currency": "मुद्रा खोजें", "new_template" : "नया टेम्पलेट", - "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं" + "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं", + "wallet_name_exists": "उस नाम वाला वॉलेट पहले से मौजूद है" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 48a0ef4e2..209fa38b9 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "Vrijednost iznosa ne smije biti veća od raspoloživog iznosa.\nBroj decimala smije biti 2 ili manji.", "error_text_subaddress_name" : "Ime podadrese ne smije sadržavati znakove ` , ' \" \ni mora biti dužine između 1 i 20 znakova", "error_text_amount" : "Iznos smije sadržavati samo brojeve", - "error_text_wallet_name" : "Ime novčanika smije sadržavati samo slova i brojeve\nte mora biti dužine između 1 i 15 znakova", + "error_text_wallet_name" : "Naziv novčanika može sadržavati samo slova, brojeve, _ - simbole\ni mora imati između 1 i 33 znaka", "error_text_keys" : "Novčanik smije sadržavati samo 64 znakova hex vrijednosti", "error_text_crypto_currency" : "Broj decimala mora\nbiti 12 ili manji", "error_text_minimal_limit" : "Razmjena za ${provider} nije izrađena. Iznos je manji od minimalnog: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "Jezik pretraživanja", "search_currency": "Traži valutu", "new_template" : "novi predložak", - "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek" + "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek", + "wallet_name_exists": "Novčanik s tim nazivom već postoji" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index cb21d43dd..4b67fdc80 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "L'ammontare non può eccedere il saldo dispoinibile.\nIl numero di cifre decimali deve essere inferiore o uguale a 2", "error_text_subaddress_name" : "Il nome del sottoindirizzo non può contenere i simboli ` , ' \" \ne deve avere una lunghezza compresa tra 1 e 20 caratteri", "error_text_amount" : "L'ammontare può contenere solo numeri", - "error_text_wallet_name" : "Il nome del portafoglio può contenere solo lettere, numeri\ne deve avere una lunghezza compresa tra 1 e 15 caratteri", + "error_text_wallet_name" : "Il nome del portafoglio può contenere solo lettere, numeri, _ - simboli\ne deve avere una lunghezza compresa tra 1 e 33 caratteri", "error_text_keys" : "Le chiavi del portafoglio possono contenere solo 64 caratteri in hex", "error_text_crypto_currency" : "Il numero delle cifre decimali\ndeve essere inferiore o uguale a 12", "error_text_minimal_limit" : "Lo scambio per ${provider} non è stato creato. L'ammontare è inferiore al minimo: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "Cerca lingua", "search_currency": "Cerca valuta", "new_template" : "Nuovo modello", - "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare" + "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare", + "wallet_name_exists": "Il portafoglio con quel nome è già esistito" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 6f162b8dc..9a3f739e5 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "金額は利用可能な残高を超えることはできません.\n小数桁の数は2以下でなければなりません", "error_text_subaddress_name" : "サブアドレス名に含めることはできません` , ' \" シンボル\n1〜20文字の長さである必要があります", "error_text_amount" : "金額には数字のみを含めることができます", - "error_text_wallet_name" : "ウォレット名には文字のみを含めることができます\n1〜15文字である必要があります", + "error_text_wallet_name" : "ウォレット名には、文字、数字、_-記号のみを含めることができます\n長さは1〜33文字である必要があります", "error_text_keys" : "ウォレットキーには、16進数で64文字しか含めることができません", "error_text_crypto_currency" : "小数桁数\n12以下でなければなりません", "error_text_minimal_limit" : "${provider} の取引は作成されません。 金額は最小額より少ない: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "検索言語", "search_currency": "検索通貨", "new_template" : "新しいテンプレート", - "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します" + "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します", + "wallet_name_exists": "その名前のウォレットはすでに存在しています" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 124edbd7c..afe78fa3a 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "금액은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 2보다 작거나 같아야합니다", "error_text_subaddress_name" : "하위 주소 이름은 포함 할 수 없습니다 ` , ' \" 기호 \n1 ~ 20 자 사이 여야합니다", "error_text_amount" : "금액은 숫자 만 포함 할 수 있습니다", - "error_text_wallet_name" : "지갑 이름은 문자, 숫자 만 포함 할 수 있습니다\n1 ~ 15 자 사이 여야합니다", + "error_text_wallet_name" : "지갑 이름은 문자, 숫자, _ - 기호만 포함할 수 있습니다.\n1~33자 사이여야 합니다.", "error_text_keys" : "지갑 키는 16 진수로 64 자만 포함 할 수 있습니다", "error_text_crypto_currency" : "소수 자릿수\n12 이하 여야합니다", "error_text_minimal_limit" : "거래 ${provider} 가 생성되지 않습니다. 금액이 최소보다 적습니다. ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "검색 언어", "search_currency": "통화 검색", "new_template" : "새 템플릿", - "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다." + "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다.", + "wallet_name_exists": "해당 이름의 지갑이 이미 존재합니다." } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 9ac238793..5dfd9c3f2 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "Waarde van bedrag kan het beschikbare saldo niet overschrijden.\nHet aantal breukcijfers moet kleiner zijn dan of gelijk zijn aan 2", "error_text_subaddress_name" : "Naam subadres mag niet bevatten ` , ' \" symbolen\nen moet tussen de 1 en 20 tekens lang zijn", "error_text_amount" : "Bedrag kan alleen cijfers bevatten", - "error_text_wallet_name" : "Naam portemonnee kan alleen letters, cijfers bevatten\nen moet tussen de 1 en 15 tekens lang zijn", + "error_text_wallet_name" : "Naam portemonnee kan alleen letters, cijfers , _ - symbolen bevatten\nen moet tussen de 1 en 33 tekens lang zijn", "error_text_keys" : "Portefeuillesleutels kunnen maximaal 64 tekens bevatten in hexadecimale volgorde", "error_text_crypto_currency" : "Het aantal breukcijfers\nmoet kleiner zijn dan of gelijk zijn aan 12", "error_text_minimal_limit" : "Ruil voor ${provider} is niet gemaakt. Bedrag is minder dan minimaal: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "Zoektaal", "search_currency": "Zoek valuta", "new_template" : "Nieuwe sjabloon", - "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work" + "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work", + "wallet_name_exists": "Portemonnee met die naam bestaat al" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index d6db3d206..0fa9ac3a3 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "Wartość kwoty nie może przekroczyć dostępnego salda.\nLiczba cyfr ułamkowych musi być mniejsza lub równa 2", "error_text_subaddress_name" : "Nazwa podadresu nie może zawierać ` , ' \" symbolika\ni musi mieć od 1 do 20 znaków", "error_text_amount" : "Kwota może zawierać tylko liczby", - "error_text_wallet_name" : "Nazwa portfela może zawierać tylko litery i cyfry\ni musi mieć od 1 do 15 znaków", + "error_text_wallet_name" : "Nazwa portfela może zawierać tylko litery, cyfry, _ - symbole\ni musi mieć od 1 do 33 znaków", "error_text_keys" : "Klucze portfela mogą zawierać tylko 64 znaki w systemie szesnastkowym", "error_text_crypto_currency" : "Liczba cyfr ułamkowych\nmusi być mniejsza lub równa 12", "error_text_minimal_limit" : "Wymiana dla ${provider} nie została utworzona. Kwota jest mniejsza niż minimalna: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "Wyszukaj język", "search_currency": "Wyszukaj walutę", "new_template" : "Nowy szablon", - "electrum_address_disclaimer": "Za każdym razem, gdy korzystasz z jednego z nich, generujemy nowe adresy, ale poprzednie adresy nadal działają" + "electrum_address_disclaimer": "Za każdym razem, gdy korzystasz z jednego z nich, generujemy nowe adresy, ale poprzednie adresy nadal działają", + "wallet_name_exists": "Portfel o tej nazwie już istnieje" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index f44ffaf6f..558884ca8 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "O valor do valor não pode exceder o saldo disponível.\nO número de dígitos decimais deve ser menor ou igual a 2", "error_text_subaddress_name" : "O nome do sub-endereço não pode conter os símbolos ` , ' \" \ne deve ter entre 1 e 20 caracteres", "error_text_amount" : "A quantia deve conter apenas números", - "error_text_wallet_name" : "O nome da carteira só pode conter letras, números\ne deve ter entre 1 e 15 caracteres", + "error_text_wallet_name" : "O nome da carteira só pode conter letras, números, _ - símbolos\ne deve ter entre 1 e 33 caracteres", "error_text_keys" : "As chaves da carteira podem conter apenas 64 caracteres em hexadecimal", "error_text_crypto_currency" : "O número de dígitos decimais\ndeve ser menor ou igual a 12", "error_text_minimal_limit" : "A troca por ${provider} não é criada. O valor é menor que o mínimo: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "Idioma de pesquisa", "search_currency": "Pesquisar moeda", "new_template" : "Novo modelo", - "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando" + "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando", + "wallet_name_exists": "A carteira com esse nome já existe" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index bbb3482b2..1dbc473ab 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "Значение суммы не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 2", "error_text_subaddress_name" : "Имя субадреса не может содержать ` , ' \" символы\nи должно быть от 1 до 20 символов в длину", "error_text_amount" : "Баланс может содержать только цифры", - "error_text_wallet_name" : "Имя кошелька может содержать только буквы, цифры\nи должно быть от 1 до 15 символов в длину", + "error_text_wallet_name" : "Имя кошелька может содержать только буквы, цифры, _ - символы\nи должно быть от 1 до 33 символов в длину", "error_text_keys" : "Ключи кошелька могут содержать только 64 символа в hex", "error_text_crypto_currency" : "Количество цифр после запятой\nдолжно быть меньше или равно 12", "error_text_minimal_limit" : "Сделка для ${provider} не создана. Сумма меньше минимальной: ${min} ${currency}", @@ -533,5 +533,6 @@ "search_language": "Язык поиска", "search_currency": "Валюта поиска", "new_template" : "Новый шаблон", - "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать." + "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать.", + "wallet_name_exists": "Кошелек с таким именем уже существует" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index ec13da291..a152e35a8 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -326,7 +326,7 @@ "error_text_fiat" : "Значення суми не може перевищувати доступний баланс.\nКількість цифр після коми повинно бути меншим або дорівнювати 2", "error_text_subaddress_name" : "Ім'я субадреси не може містити ` , ' \" символи\nі може бути від 1 до 20 символів в довжину", "error_text_amount" : "Баланс може містити тільки цифри", - "error_text_wallet_name" : "Ім'я гаманця може містити тільки букви, цифри\nі повинно бути від 1 до 15 символів в довжину", + "error_text_wallet_name" : "Ім'я гаманця може містити тільки букви, цифри, символи _ -\nі повинно бути від 1 до 33 символів в довжину", "error_text_keys" : "Ключі гаманця можуть містити тільки 64 символів в hex", "error_text_crypto_currency" : "Кількість цифр після коми\nповинно бути меншим або дорівнювати 12", "error_text_minimal_limit" : "Операція для ${provider} не створена. Сума менша мінімальної: ${min} ${currency}", @@ -532,5 +532,6 @@ "search_language": "Мова пошуку", "search_currency": "Шукати валюту", "new_template" : "Новий шаблон", - "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати" + "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати", + "wallet_name_exists": "Гаманець з такою назвою вже існує" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index d1cb625ee..9b9fe7a79 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -327,7 +327,7 @@ "error_text_fiat" : "金额不能超过可用余额.\n小数位数必须小于或等于2", "error_text_subaddress_name" : "子地址名称不能包含`,' \" 符号\n并且必须在1到20个字符之间", "error_text_amount" : "金额只能包含数字", - "error_text_wallet_name" : "钱包名称只能包含字母,数字\n且必须介于1到15个字符之间", + "error_text_wallet_name" : "钱包名称只能包含字母、数字、_ - 符号\n并且长度必须在 1 到 33 个字符之间", "error_text_keys" : "钱包密钥只能包含16个字符的十六进制字符", "error_text_crypto_currency" : "小数位数\n必须小于或等于12", "error_text_minimal_limit" : "未创建 ${provider} 交易。 金额小于最小值:${min} ${currency}", @@ -531,5 +531,6 @@ "search_language": "搜索语言", "search_currency": "搜索货币", "new_template" : "新模板", - "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效" + "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效", + "wallet_name_exists": "同名的钱包已经存在" } From 3bd9301be1c4b906d88b973bec00943584514716 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 1 Jul 2022 12:17:09 +0200 Subject: [PATCH 15/22] Redesign exchange currency picker (#402) --- lib/src/screens/contact/contact_page.dart | 1 + .../exchange/widgets/currency_picker.dart | 229 ++++++++---------- .../widgets/currency_picker_item_widget.dart | 113 ++++----- .../widgets/currency_picker_widget.dart | 65 +++-- .../exchange/widgets/exchange_card.dart | 2 +- lib/src/widgets/alert_close_button.dart | 10 +- lib/src/widgets/picker.dart | 145 ++++++----- 7 files changed, 259 insertions(+), 306 deletions(-) diff --git a/lib/src/screens/contact/contact_page.dart b/lib/src/screens/contact/contact_page.dart index 6ac236352..234cdb673 100644 --- a/lib/src/screens/contact/contact_page.dart +++ b/lib/src/screens/contact/contact_page.dart @@ -150,6 +150,7 @@ class ContactPage extends BasePage { contactViewModel.currencies.indexOf(contactViewModel.currency), items: contactViewModel.currencies, title: S.of(context).please_select, + hintText: S.of(context).search_currency, onItemSelected: (CryptoCurrency item) => contactViewModel.currency = item), context: context); diff --git a/lib/src/screens/exchange/widgets/currency_picker.dart b/lib/src/screens/exchange/widgets/currency_picker.dart index 4cbda9b99..5e4a31831 100644 --- a/lib/src/screens/exchange/widgets/currency_picker.dart +++ b/lib/src/screens/exchange/widgets/currency_picker.dart @@ -1,8 +1,8 @@ import 'dart:ui'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker_item_widget.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_utils.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/picker_item.dart'; +import 'package:cake_wallet/src/widgets/alert_close_button.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -13,8 +13,9 @@ class CurrencyPicker extends StatefulWidget { CurrencyPicker( {@required this.selectedAtIndex, @required this.items, - @required this.title, @required this.onItemSelected, + this.title, + this.hintText, this.isMoneroWallet = false, this.isConvertFrom = false}); @@ -24,6 +25,7 @@ class CurrencyPicker extends StatefulWidget { final Function(CryptoCurrency) onItemSelected; final bool isMoneroWallet; final bool isConvertFrom; + final String hintText; @override CurrencyPickerState createState() => CurrencyPickerState(items); @@ -34,11 +36,8 @@ class CurrencyPickerState extends State { : isSearchBarActive = false, textFieldValue = '', subPickerItemsList = [], - appBarTextStyle = TextStyle( - fontSize: 20, - fontFamily: 'Lato', - backgroundColor: Colors.transparent, - color: Colors.white); + appBarTextStyle = + TextStyle(fontSize: 20, fontFamily: 'Lato', backgroundColor: Colors.transparent, color: Colors.white); @override void initState() { @@ -61,13 +60,10 @@ class CurrencyPickerState extends State { TextStyle appBarTextStyle; void cleanSubPickerItemsList() { - subPickerItemsList = pickerItemsList - .where((element) => items.contains(element.original)) - .toList(); + subPickerItemsList = pickerItemsList.where((element) => items.contains(element.original)).toList(); } - void currencySearchBySubstring( - String subString, List> list) { + void currencySearchBySubstring(String subString, List> list) { setState(() { if (subString.isNotEmpty) { subPickerItemsList = subPickerItemsList @@ -84,118 +80,104 @@ class CurrencyPickerState extends State { @override Widget build(BuildContext context) { return AlertBackground( - child: SafeArea( - child: Scaffold( - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, - body: Column( - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 26.0, vertical: 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - isSearchBarActive - ? Expanded( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - InkWell( - child: Text( - S.of(context).cancel, - style: appBarTextStyle, - ), - onTap: () { - setState(() { - isSearchBarActive = false; - textFieldValue = ''; - cleanSubPickerItemsList(); - }); - }), - Container( - width: 100.0, - child: CupertinoTextField( - autofocus: true, - placeholder: - S.of(context).search + '...', - placeholderStyle: appBarTextStyle, - decoration: BoxDecoration( - color: Colors.transparent), - cursorColor: Colors.white, - cursorHeight: 23.0, - style: appBarTextStyle, - onChanged: (value) { - this.textFieldValue = value; - cleanSubPickerItemsList(); - currencySearchBySubstring( - textFieldValue, - subPickerItemsList); - }), - ) - ], - ), - ) - : Text( - widget.title, - style: appBarTextStyle, - ), - IconButton( - splashRadius: 23, - icon: Icon(Icons.search, color: Colors.white), - onPressed: () { - setState(() { - isSearchBarActive = true; - }); - }, - ) - ]), - ), - Expanded( - flex: 12, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 26.0, vertical: 26.0), - child: Container( - child: CurrencyPickerWidget( - crossAxisCount: 2, - selectedAtIndex: widget.selectedAtIndex, - itemsCount: subPickerItemsList.length, - pickerItemsList: subPickerItemsList, - pickListItem: (int index) { - setState(() { - widget.selectedAtIndex = index; - }); - widget - .onItemSelected(subPickerItemsList[index].original); - if (widget.isConvertFrom && - !widget.isMoneroWallet && - (subPickerItemsList[index].original == - CryptoCurrency.xmr)) { - } else { - Navigator.of(context).pop(); - } - }, + child: Stack( + alignment: Alignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.title?.isNotEmpty ?? false) + Container( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Text( + widget.title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.white, ), ), ), - ), - Expanded( - flex: 2, - child: Container( - width: 42.0, - alignment: Alignment.topCenter, - child: FittedBox( - child: FloatingActionButton( - elevation: 0, - backgroundColor: Colors.white, - onPressed: () { - Navigator.of(context).pop(); - }, - child: Icon( - Icons.close_outlined, - color: Palette.darkBlueCraiola, - size: 30.0, + Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: Container( + color: Theme.of(context).accentTextTheme.title.color, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.65, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.hintText != null) + Padding( + padding: const EdgeInsets.all(16), + child: TextFormField( + style: TextStyle(color: Theme.of(context).primaryTextTheme.title.color), + decoration: InputDecoration( + hintText: widget.hintText, + prefixIcon: Image.asset("assets/images/search_icon.png"), + filled: true, + fillColor: const Color(0xffF2F0FA), + alignLabelWithHint: false, + contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: const BorderSide( + color: Colors.transparent, + )), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: const BorderSide( + color: Colors.transparent, + )), + ), + onChanged: (value) { + this.textFieldValue = value; + cleanSubPickerItemsList(); + currencySearchBySubstring(textFieldValue, subPickerItemsList); + }, + ), + ), + Divider( + color: Theme.of(context).accentTextTheme.title.backgroundColor, + height: 1, + ), + if (widget.selectedAtIndex != -1) + AspectRatio( + aspectRatio: 6, + child: PickerItemWidget( + title: pickerItemsList[widget.selectedAtIndex].title, + iconPath: pickerItemsList[widget.selectedAtIndex].iconPath, + isSelected: true, + tag: pickerItemsList[widget.selectedAtIndex].tag, + ), + ), + Flexible( + child: CurrencyPickerWidget( + crossAxisCount: 2, + selectedAtIndex: widget.selectedAtIndex, + pickerItemsList: subPickerItemsList, + pickListItem: (int index) { + setState(() { + widget.selectedAtIndex = index; + }); + widget.onItemSelected(subPickerItemsList[index].original); + if (widget.isConvertFrom && + !widget.isMoneroWallet && + (subPickerItemsList[index].original == CryptoCurrency.xmr)) { + } else { + Navigator.of(context).pop(); + } + }, + ), + ), + ], ), ), ), @@ -203,7 +185,8 @@ class CurrencyPickerState extends State { ), ], ), - ), + AlertCloseButton(), + ], ), ); } diff --git a/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart b/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart index d38c5bd45..b710494fd 100644 --- a/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart +++ b/lib/src/screens/exchange/widgets/currency_picker_item_widget.dart @@ -2,8 +2,7 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/palette.dart'; class PickerItemWidget extends StatelessWidget { - const PickerItemWidget( - {this.iconPath, this.title, this.isSelected, this.tag, this.onTap}); + const PickerItemWidget({this.iconPath, this.title, this.isSelected = false, this.tag, this.onTap}); final String iconPath; final String title; @@ -16,74 +15,56 @@ class PickerItemWidget extends StatelessWidget { return GestureDetector( onTap: onTap, child: Container( - color: isSelected - ? Theme.of(context).textTheme.bodyText1.color - : Theme.of(context).accentTextTheme.headline6.color, - child: Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - child: Image.asset( - iconPath, - height: 32.0, - width: 32.0, - ), - ), + color: Theme.of(context).accentTextTheme.headline6.color, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 24), + child: Row( + children: [ + Container( + child: Image.asset( + iconPath, + height: 20.0, + width: 20.0, ), - Expanded( - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.centerLeft, - children: [ - Text( - title, - style: TextStyle( - color: isSelected - ? Palette.blueCraiola - : Theme.of(context).primaryTextTheme.title.color, - fontSize: 18.0, - fontFamily: 'Lato', + ), + const SizedBox(width: 6), + Expanded( + child: Row( + children: [ + Text( + title, + style: TextStyle( + color: isSelected ? Palette.blueCraiola : Theme.of(context).primaryTextTheme.title.color, + fontSize: isSelected ? 16 : 14.0, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + ), + ), + if (tag != null) + Align( + alignment: Alignment.topCenter, + child: Container( + width: 35.0, + height: 18.0, + child: Center( + child: Text( + tag, + style: TextStyle( + fontSize: 7.0, fontFamily: 'Lato', color: Theme.of(context).textTheme.body1.color), + ), + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.0), + //border: Border.all(color: ), + color: Theme.of(context).textTheme.body1.decorationColor, + ), ), ), - tag != null - ? Positioned( - top: -20.0, - right: 7.0, - child: Container( - width: 35.0, - height: 18.0, - child: Center( - child: Text( - tag, - style: TextStyle( - fontSize: 7.0, - fontFamily: 'Lato', - color: Theme.of(context) - .textTheme - .body1 - .color), - ), - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6.0), - //border: Border.all(color: ), - color: Theme.of(context) - .textTheme - .body1 - .decorationColor, - ), - ), - ) - : Container(), - ], - ), + ], ), - ], - ), + ), + if (isSelected) Icon(Icons.check_circle, color: Theme.of(context).accentTextTheme.body2.color) + ], ), ), ), diff --git a/lib/src/screens/exchange/widgets/currency_picker_widget.dart b/lib/src/screens/exchange/widgets/currency_picker_widget.dart index 46029a44a..ec0a11356 100644 --- a/lib/src/screens/exchange/widgets/currency_picker_widget.dart +++ b/lib/src/screens/exchange/widgets/currency_picker_widget.dart @@ -4,58 +4,47 @@ import 'picker_item.dart'; import 'currency_picker_item_widget.dart'; class CurrencyPickerWidget extends StatelessWidget { - const CurrencyPickerWidget({ + CurrencyPickerWidget({ @required this.crossAxisCount, @required this.selectedAtIndex, - @required this.itemsCount, @required this.pickerItemsList, @required this.pickListItem, }); final int crossAxisCount; final int selectedAtIndex; - final int itemsCount; final Function pickListItem; final List> pickerItemsList; + final ScrollController _scrollController = ScrollController(); + @override Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).accentTextTheme.headline6.backgroundColor, - borderRadius: BorderRadius.circular(14.0), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(14.0), - child: Scrollbar( - showTrackOnHover: true, - isAlwaysShown: true, - thickness: 6.0, - radius: Radius.circular(3), - child: GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - crossAxisSpacing: 1, - mainAxisExtent: constraints.maxHeight / 8, - mainAxisSpacing: 1), - itemCount: pickerItemsList.length, - itemBuilder: (BuildContext ctx, index) { - return PickerItemWidget( - onTap: () { - pickListItem(index); - }, - title: pickerItemsList[index].title, - iconPath: pickerItemsList[index].iconPath, - isSelected: index == selectedAtIndex, - tag: pickerItemsList[index].tag, - ); - }), + return Container( + color: Theme.of(context).accentTextTheme.headline6.backgroundColor, + child: Scrollbar( + controller: _scrollController, + child: GridView.builder( + controller: _scrollController, + padding: EdgeInsets.zero, + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + crossAxisSpacing: 2, + childAspectRatio: 3, ), - ), - ); - }, + itemCount: pickerItemsList.length, + itemBuilder: (BuildContext ctx, index) { + return PickerItemWidget( + onTap: () { + pickListItem(index); + }, + title: pickerItemsList[index].title, + iconPath: pickerItemsList[index].iconPath, + tag: pickerItemsList[index].tag, + ); + }), + ), ); } } diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index ec57323ac..384fc8d23 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -445,7 +445,7 @@ class ExchangeCardState extends State { builder: (_) => CurrencyPicker( selectedAtIndex: widget.currencies.indexOf(_selectedCurrency), items: widget.currencies, - title: S.of(context).change_currency, + hintText: S.of(context).search_currency, isMoneroWallet: _isMoneroWallet, isConvertFrom: widget.hasRefundAddress, onItemSelected: (CryptoCurrency item) => diff --git a/lib/src/widgets/alert_close_button.dart b/lib/src/widgets/alert_close_button.dart index 9349eb5b7..e1b6863be 100644 --- a/lib/src/widgets/alert_close_button.dart +++ b/lib/src/widgets/alert_close_button.dart @@ -1,10 +1,16 @@ +import 'package:cake_wallet/palette.dart'; import 'package:flutter/material.dart'; class AlertCloseButton extends StatelessWidget { - AlertCloseButton({@required this.image}); + AlertCloseButton({this.image}); final Image image; + final closeButton = Image.asset( + 'assets/images/close.png', + color: Palette.darkBlueCraiola, + ); + @override Widget build(BuildContext context) { return Positioned( @@ -19,7 +25,7 @@ class AlertCloseButton extends StatelessWidget { shape: BoxShape.circle ), child: Center( - child: image, + child: image ?? closeButton, ), ), ) diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index b5cad676b..0029c8191 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -3,7 +3,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/src/widgets/alert_close_button.dart'; -import 'package:cake_wallet/palette.dart'; class Picker extends StatefulWidget { Picker({ @@ -49,10 +48,6 @@ class PickerState extends State { final TextEditingController searchController = TextEditingController(); - final closeButton = Image.asset( - 'assets/images/close.png', - color: Palette.darkBlueCraiola, - ); ScrollController controller = ScrollController(); @override @@ -98,81 +93,78 @@ class PickerState extends State { ), Padding( padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: GestureDetector( - onTap: () => null, - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(30)), - child: Container( - color: Theme.of(context).accentTextTheme.title.color, - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.65, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.hintText != null) - Padding( - padding: const EdgeInsets.all(16), - child: TextFormField( - controller: searchController, - style: TextStyle(color: Theme.of(context).primaryTextTheme.title.color), - decoration: InputDecoration( - hintText: widget.hintText, - prefixIcon: Image.asset("assets/images/search_icon.png"), - filled: true, - fillColor: Theme.of(context).accentTextTheme.display2.color, - alignLabelWithHint: false, - contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(14), - borderSide: const BorderSide( - color: Colors.transparent, - )), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(14), - borderSide: const BorderSide( - color: Colors.transparent, - )), - ), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: Container( + color: Theme.of(context).accentTextTheme.title.color, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.65, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.hintText != null) + Padding( + padding: const EdgeInsets.all(16), + child: TextFormField( + controller: searchController, + style: TextStyle(color: Theme.of(context).primaryTextTheme.title.color), + decoration: InputDecoration( + hintText: widget.hintText, + prefixIcon: Image.asset("assets/images/search_icon.png"), + filled: true, + fillColor: Theme.of(context).accentTextTheme.display2.color, + alignLabelWithHint: false, + contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: const BorderSide( + color: Colors.transparent, + )), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: const BorderSide( + color: Colors.transparent, + )), ), ), - Divider( - color: Theme.of(context).accentTextTheme.title.backgroundColor, - height: 1, ), - if (widget.selectedAtIndex != -1) buildSelectedItem(), - Flexible( - child: Stack( - alignment: Alignment.center, - children: [ - (items?.length ?? 0) > 3 ? Scrollbar( - controller: controller, - child: itemsList(), - ) : itemsList(), - (widget.description?.isNotEmpty ?? false) - ? Positioned( - bottom: 24, - left: 24, - right: 24, - child: Text( - widget.description, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - fontFamily: 'Lato', - decoration: TextDecoration.none, - color: Theme.of(context).primaryTextTheme.title.color, - ), + Divider( + color: Theme.of(context).accentTextTheme.title.backgroundColor, + height: 1, + ), + if (widget.selectedAtIndex != -1) buildSelectedItem(), + Flexible( + child: Stack( + alignment: Alignment.center, + children: [ + (items?.length ?? 0) > 3 ? Scrollbar( + controller: controller, + child: itemsList(), + ) : itemsList(), + (widget.description?.isNotEmpty ?? false) + ? Positioned( + bottom: 24, + left: 24, + right: 24, + child: Text( + widget.description, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + fontFamily: 'Lato', + decoration: TextDecoration.none, + color: Theme.of(context).primaryTextTheme.title.color, ), - ) - : Offstage(), - ], - ), + ), + ) + : Offstage(), + ], ), - ], - ), + ), + ], ), ), ), @@ -180,7 +172,7 @@ class PickerState extends State { ) ], ), - AlertCloseButton(image: closeButton) + AlertCloseButton(), ], ), ); @@ -193,6 +185,7 @@ class PickerState extends State { ? GridView.builder( padding: EdgeInsets.zero, controller: controller, + shrinkWrap: true, itemCount: items == null || items.isEmpty ? 0 : items.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, From 8321c9fbbe9769178115284dbb68bb2644e3130b Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 1 Jul 2022 13:04:00 +0200 Subject: [PATCH 16/22] Enable different currencies (#401) --- lib/entities/fiat_currency.dart | 3 + lib/src/screens/buy/pre_order_page.dart | 115 ++++++++++-------- lib/view_model/buy/buy_amount_view_model.dart | 20 ++- lib/view_model/buy/buy_view_model.dart | 1 + .../settings/settings_view_model.dart | 5 +- 5 files changed, 90 insertions(+), 54 deletions(-) diff --git a/lib/entities/fiat_currency.dart b/lib/entities/fiat_currency.dart index 1e5c61e1e..36a3c44a1 100644 --- a/lib/entities/fiat_currency.dart +++ b/lib/entities/fiat_currency.dart @@ -8,6 +8,9 @@ class FiatCurrency extends EnumerableItem with Serializable { static List get all => _all.values.toList(); + static List get currenciesAvailableToBuyWith => + [aud, brl, cad, chf, czk, eur, dkk, gbp, hkd, ils, jpy, krw, mxn, myr, nok, nzd, pln, sek, sgd, thb, usd, zar]; + static const aud = FiatCurrency(symbol: 'AUD', countryCode: "aus", fullName: "Australian Dollar"); static const bgn = FiatCurrency(symbol: 'BGN', countryCode: "bgr", fullName: "Bulgarian Lev"); static const brl = FiatCurrency(symbol: 'BRL', countryCode: "bra", fullName: "Brazilian Real"); diff --git a/lib/src/screens/buy/pre_order_page.dart b/lib/src/screens/buy/pre_order_page.dart index 3f403bf85..38fe4a813 100644 --- a/lib/src/screens/buy/pre_order_page.dart +++ b/lib/src/screens/buy/pre_order_page.dart @@ -2,6 +2,8 @@ import 'dart:ui'; import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/src/screens/buy/widgets/buy_list_item.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; @@ -27,7 +29,6 @@ class PreOrderPage extends BasePage { PreOrderPage({@required this.buyViewModel}) : _amountFocus = FocusNode(), _amountController = TextEditingController() { - _amountController.addListener(() { final amount = _amountController.text; @@ -110,52 +111,70 @@ class PreOrderPage extends BasePage { .decorationColor, ], begin: Alignment.topLeft, end: Alignment.bottomRight), ), - child: Padding( - padding: EdgeInsets.only(top: 100, bottom: 65), - child: Center( - child: Container( - width: 210, - child: BaseTextFormField( - focusNode: _amountFocus, - controller: _amountController, - keyboardType: - TextInputType.numberWithOptions( - signed: false, decimal: true), - inputFormatters: [ - FilteringTextInputFormatter - .allow(RegExp(_amountPattern)) - ], - prefixIcon: Padding( - padding: EdgeInsets.only(top: 2), - child: - Text(buyViewModel.fiatCurrency.title + ': ', - style: TextStyle( - fontSize: 36, - fontWeight: FontWeight.w600, - color: Colors.white, - )), - ), - hintText: '0.00', - borderColor: Theme.of(context) - .primaryTextTheme - .body2 - .decorationColor, - borderWidth: 0.5, - textStyle: TextStyle( + child: Padding( + padding: EdgeInsets.only(top: 100, bottom: 65), + child: Center( + child: Container( + width: 210, + child: BaseTextFormField( + focusNode: _amountFocus, + controller: _amountController, + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + inputFormatters: [FilteringTextInputFormatter.allow(RegExp(_amountPattern))], + prefixIcon: GestureDetector( + onTap: () { + showPopUp( + context: context, + builder: (_) => Picker( + hintText: S.current.search_currency, + items: FiatCurrency.currenciesAvailableToBuyWith, + selectedAtIndex: + FiatCurrency.currenciesAvailableToBuyWith.indexOf(buyViewModel.fiatCurrency), + onItemSelected: (FiatCurrency selectedCurrency) { + buyViewModel.buyAmountViewModel.fiatCurrency = selectedCurrency; + }, + images: FiatCurrency.currenciesAvailableToBuyWith + .map((e) => Image.asset("assets/images/flags/${e.countryCode}.png")) + .toList(), + isGridView: true, + matchingCriteria: (FiatCurrency currency, String searchText) { + return currency.title.toLowerCase().contains(searchText) || + currency.fullName.toLowerCase().contains(searchText); + }, + ), + ); + }, + child: Padding( + padding: EdgeInsets.only(top: 2), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.keyboard_arrow_down, color: Colors.white), + Text( + buyViewModel.fiatCurrency.title + ': ', + style: TextStyle( fontSize: 36, - fontWeight: FontWeight.w500, - color: Colors.white), - placeholderTextStyle: TextStyle( - color: Theme.of(context) - .primaryTextTheme - .headline - .decorationColor, - fontWeight: FontWeight.w500, - fontSize: 36), - ) - ) - ) - ) + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ], + ), + ), + ), + hintText: '0.00', + borderColor: Theme.of(context).primaryTextTheme.body2.decorationColor, + borderWidth: 0.5, + textStyle: TextStyle(fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white), + placeholderTextStyle: TextStyle( + color: Theme.of(context).primaryTextTheme.headline.decorationColor, + fontWeight: FontWeight.w500, + fontSize: 36, + ), + ), + ), + ), + ), ), if (buyViewModel.isShowProviderButtons) Padding( padding: EdgeInsets.only(top: 38, bottom: 18), @@ -273,7 +292,7 @@ class PreOrderPage extends BasePage { buyViewModel.isRunning = true; final url = await buyViewModel.fetchUrl(); - + if (url.isNotEmpty) { if (buyViewModel.selectedProvider is MoonPayBuyProvider) { if (await canLaunch(url)) await launch(url); @@ -287,4 +306,4 @@ class PreOrderPage extends BasePage { buyViewModel.reset(); buyViewModel.isRunning = false; } -} \ No newline at end of file +} diff --git a/lib/view_model/buy/buy_amount_view_model.dart b/lib/view_model/buy/buy_amount_view_model.dart index e027166f9..d1913f679 100644 --- a/lib/view_model/buy/buy_amount_view_model.dart +++ b/lib/view_model/buy/buy_amount_view_model.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; @@ -6,12 +8,24 @@ part 'buy_amount_view_model.g.dart'; class BuyAmountViewModel = BuyAmountViewModelBase with _$BuyAmountViewModel; abstract class BuyAmountViewModelBase with Store { - BuyAmountViewModelBase() : amount = ''; + BuyAmountViewModelBase() { + amount = ''; + + int selectedIndex = FiatCurrency.currenciesAvailableToBuyWith + .indexOf(getIt.get().fiatCurrency); + + if (selectedIndex == -1) { + selectedIndex = FiatCurrency.currenciesAvailableToBuyWith + .indexOf(FiatCurrency.usd); + } + fiatCurrency = FiatCurrency.currenciesAvailableToBuyWith[selectedIndex]; + } @observable String amount; - FiatCurrency get fiatCurrency => FiatCurrency.usd; + @observable + FiatCurrency fiatCurrency; @computed double get doubleAmount { @@ -25,4 +39,4 @@ abstract class BuyAmountViewModelBase with Store { return _amount; } -} \ No newline at end of file +} diff --git a/lib/view_model/buy/buy_view_model.dart b/lib/view_model/buy/buy_view_model.dart index b7b74d7ff..e484b819c 100644 --- a/lib/view_model/buy/buy_view_model.dart +++ b/lib/view_model/buy/buy_view_model.dart @@ -54,6 +54,7 @@ abstract class BuyViewModelBase with Store { double get doubleAmount => buyAmountViewModel.doubleAmount; + @computed FiatCurrency get fiatCurrency => buyAmountViewModel.fiatCurrency; CryptoCurrency get cryptoCurrency => walletTypeToCryptoCurrency(type); diff --git a/lib/view_model/settings/settings_view_model.dart b/lib/view_model/settings/settings_view_model.dart index 67cf35635..d335c8688 100644 --- a/lib/view_model/settings/settings_view_model.dart +++ b/lib/view_model/settings/settings_view_model.dart @@ -1,4 +1,3 @@ -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/language_service.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; @@ -172,9 +171,9 @@ abstract class SettingsViewModelBase with Store { displayItem: (dynamic code) { return LanguageService.list[code]; }, - selectedItem: () => getIt.get().languageCode, + selectedItem: () => _settingsStore.languageCode, onItemSelected: (String code) { - getIt.get().languageCode = code; + _settingsStore.languageCode = code; }, images: LanguageService.list.keys.map( (e) => Image.asset("assets/images/flags/${LanguageService.localeCountryCode[e]}.png")) From fa2e9cd8107c645a3bb4a50491b8a8b6ec6152db Mon Sep 17 00:00:00 2001 From: Serhii-Borodenko Date: Thu, 7 Jul 2022 18:02:38 +0300 Subject: [PATCH 17/22] fix exchangenow payment request --- lib/src/screens/exchange/widgets/exchange_card.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 384fc8d23..a330675fd 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -316,9 +316,10 @@ class ExchangeCardState extends State { if (amountController.text.isNotEmpty) { _showAmountPopup(context, paymentRequest); - } else { - amountController.text = paymentRequest.amount; + return; } + widget.amountFocusNode.requestFocus(); + amountController.text = paymentRequest.amount; }, placeholder: widget.hasRefundAddress ? S.of(context).refund_address @@ -465,6 +466,7 @@ class ExchangeCardState extends State { rightButtonText: S.of(context).ok, leftButtonText: S.of(context).cancel, actionRightButton: () { + widget.amountFocusNode.requestFocus(); amountController.text = paymentRequest.amount; Navigator.of(context).pop(); }, From 52b8bafac9baa98c874f1b64c74963918564885c Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 11 Jul 2022 18:20:16 +0300 Subject: [PATCH 18/22] Exchange screens access improvements (#409) * move tag prop to the crypto currency * add currency icons --- assets/images/zaddr_icon.png | Bin 0 -> 16312 bytes assets/images/zec_icon.png | Bin 0 -> 16312 bytes cw_core/lib/crypto_currency.dart | 81 +++++--- .../changenow_exchange_provider.dart | 26 +-- .../sideshift_exchange_provider.dart | 11 +- .../exchange/widgets/currency_picker.dart | 49 ++--- .../widgets/currency_picker_widget.dart | 2 +- .../exchange/widgets/currency_utils.dart | 101 --------- .../exchange/widgets/exchange_card.dart | 36 +++- lib/src/screens/send/widgets/send_card.dart | 196 +++++++++++------- 10 files changed, 237 insertions(+), 265 deletions(-) create mode 100644 assets/images/zaddr_icon.png create mode 100644 assets/images/zec_icon.png delete mode 100644 lib/src/screens/exchange/widgets/currency_utils.dart diff --git a/assets/images/zaddr_icon.png b/assets/images/zaddr_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..139acf84c54f2e3622db5346db88043071dea2c2 GIT binary patch literal 16312 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 Zso}Sq{Fyg?a{MECqG6z3{@5<;e*gqh6AJ(U literal 0 HcmV?d00001 diff --git a/assets/images/zec_icon.png b/assets/images/zec_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..139acf84c54f2e3622db5346db88043071dea2c2 GIT binary patch literal 16312 zcmbVzcRX8P+`qO;RZ%r-wkSpIRgKZstlip;ReK~+Q88i^RYg&|q*fKRSH#}CirSJJ zv)4mBRvxht(SV_%m3|j*hE1-_Ur%s{(ld0MKT`aLZK#2!NEi!cAKL8 z1BHwrg|q+#FVS#{T+H(7nSn7y+s@|*CGI&pnwQhN+UO*aD@@$%TI`yf6co28o~S=E z@xyIodUykxH#F#8`#t5OiDni-;ZDy(yd$fVK0DrIS8{GFT;VEw#?p6q{c2j>F=FIr zAH)A9_5T6?lk2(GxY^kC%SbW$vrjU^+&N*9m$%K9IpSDmGag}WuAo0sNS$LaZ1A1o zwyMC%7yFRlGp+ojrttvtX*yof*Ub8h8x8P)V1}@9|A=oF?A;sbg;!OhxneK95pBu* zy6ASK5PdP~(ng8Dz4KH>(ORFw&~#Z#ZxN3K>pz2DAJW41IIa&;Pr7U6EsUcSA4 z;5*xS=J@Lhm)%@h3`zNsj?hb;SFb&tW?z{mRtF>g&|jkd>po3rqWN}_d{wl*Y1GV_ zcmLII&K-lHMzv#DGHZ}pYw&F97RF6<&Aa~^=yZSMPliCVKd}nndURNe4ye@vm_2Rf6C3N}9$h#>DN4M)F>0Q{(_m8gXBh0^eb0>ao zPyiR@C@ZTKpywWn(c6MOzjG!}EmXG1yWFGt2KRctG3)L<_xz%T{4(A3>cQUX0|n0q zp4SgrY&~;7>IWCeOg=TX@q*K(HL7H~ zubf8mIk1P=ubi(TN>4w&>v(P?zeW|4HH2`x0o;Rp?tEM?D$Ft?6RGiSxzt=|ZJF|} zU+3Y*#MgJ)cyE5hG&mx$LJL>k-T?fn5~1V(`OYM1Qv>Rk?K|OX$qtH$!@zg@PTO`9Qir}k@FkuE>=x*iq>>-AglIQ?~K{`7-w#V|<7B&TkI;JmgiE!AdF zBd^on)^KG~TRE%0=>r)j`*Bm@i6@YvJBAlgu0*YH zXraN;Xw!~TsHX{LVvKa1xbJ7l^?vz*kNMa0jiSM#zAn>+g6wh|q>q8MfeMf2j3PdE ze!7^TZ$rLQpHVb<6aS>e-9IU(=>aru{=M_3<`0rPtjoQ_HfmPjbp9VZK(% zO)4g*(wZv-IM(=AY-^@T2JAVpC$(id`r9PS&x^%prGFOPBtlD&YSQ|3a3@ie`<5ft zQ`-5IIy5`JkoXi_G18p=YTaNo*#`@<=(x2x74q^Ax6UgkW2p>SPkGsD>OwURa>=uB z(BI_yVfAdgPtWo@uxLtwec)ni{rLXiM+%9w@!r@p8)G+{T)t=M4?Tay7_GPRJ@xYy zqcR@veQ`y_g;_C;?B9e_x54>h1fBhKqOWXLm z`glP6c|-6r;Tp)v`n;wWbKAxv84uO6D!B`z{H8)#ni3_*yCQvSoe6zvY156}Me-9%C^BfVH{usv;=;xj+NNbX*LdA{~ zsn!PD8`1J)?&`$QJ-uMkq+k)GCjYNZPeXHKdIU`!Zoz>l_pOz?hLI%UU!grrFqZ2U z15L5=Mu?Z=%Ts%;b*UU;rl*#K?5{NvtiTpH!j3?>c5;5z+Uxr9#b?0JwEfxcB>*;ilpT-?Gl42>KSUt$2QbE&? z{F^^7G$f*$`Iz<&ROvBDxkNkHVj#<-B+K@oW?sIVaN8QJj-yRA zH6k6gB10RU%q(BRyvvE%xpXc^8~5kLl8S!eG4`_OsNbG`2=y&ie}Hlr`UE$prRXW( z>Sg{^gFYlN6_tQF;K6Zzn=_pCj4tE!;!MC)xY@>QKeI<^*mJ$DKZx%K*A@ghQR_pP zMZHzd>!A-?|{ z7vdFCW2%G2i%op>R|E>z-yO>A4D?AsFaLetWe-!Y3@Q%fE-RIS>MmA(WV{Cy7Eg}` zHVVSbo(wcP2+H02Dm-Vo%^oz%9_YpqIRy6o{(>EJ&z&0JnQwZ9TfMwYZOYAk-_HIh5%Eo+d@f|SCsj!%dZ5*oj<`oe;@P(;9xX%MvhC3EA> zHCH5popt5+6@Q$9oN-Z(qmC~N9dtxt_G$Of3s^cAbQ5jRsipRV^xKP9_q`bFj%<`+ zkS6X)n=cy_3C$7l`|gTa?BhE4Vi=T5wADXAokVkp9TcuQyisx0#Hr!xBy)%%-OBG` zP=k#83P9#0GaXbycY-hZ9UA{M&Fn#qFbB>C(|cplG#j5y@=Q0;(FL8>;9QRtpECKv z%q%{a9U15Xow~K7q%iKe`Qo+`Ei)9L@y;8q|Rmx;TMWHOxmD@ zR%Aw8X7fcI|J60Yy`AF>!|X?awjNmst>fO7-_imGsb@P4v!P-0-ZDg7>YVs%Oj-=+ z@7w1nuR&{VRt?NtYYb)@QuPK#Ln{KOacjc{B=F+Qgg329b~NG0pF=Y-WCa>oYTHr9 z>2sCLN2KD?FR_@EXwZmmEJIjXoT6JEv37wTDy2x@c`^Ge8hZ`y|yr}(Nx8SA@xE@C(={Z?)^1O-obn8sC=w@V;zmw&nXRU?_D{eAurbO+Vw{Ln;)7!+(ndIYI8y*SPx7;`=B2z z(JSDxzKst5<23)0bzmGq-*73IbLw!jG>3Q{bgx!DBE1sbrNPW(KE{n#W z`Mxk#Wyp>b5>jPAdhnE$S8wQCgx6tPG8|edmQ(0fHE>U5ruP$s*QJsUb7xPsOzfR_ zBtwDRbh2%bAvGCm(`eu=iyVf1frfl&kXUmqya@@1t|WuHatpz`Q71}dKBq$u(hA|+ zFMVl99m$Y4ERLKIlo9OM_T^$#3LLVvO@Zkk9h5Y070IO>pov?D;X@H-?U>{&`I{>; zW@LbdapfOV%Wq}1^o-43>-Vn^9^(F~3C@6vLf;VW3-v)jRI@ZTxZGT!p%7W}S8DCq zrqE(hU=B3IpRa-lX*>JO7k^C%C~A(u*ZbJ`I>k04-k(rC2)Qq9a&|FB!P^tWQTNEE zf)k$zt>oqcoW7Nf7c7*&|E(0ipdLK?PX{&r4fm21hDROB#owiRq5_LOLptj69GW0+ zR`m!^%{5=)F!NELS~DqtU9aj>#dtK)uuF-jy1)*kf*I7DiS8{s$`bWh;$a1KAOaz2 zv67iZO7@FWZUAQmiEU;$jJQLi%gIq^4~S=+s=)Ry0XHkH=dJ*w_4_!4l=YUwF)hTU zoz$!_8yG!ZZ;B(~_SNYzAyx)cCOmrjo363od?=f-lFcTT3J^X*_=}i7d9LY!7l9)G zlqQ4|m1eRG3j}>jd&_o$!HU`a9S9Bx*(Y>KAhTjC!am05)AI{JL#1^yg%M$9&S>3^ z1BzUH<%Ux7e+*$`x+lUGtBzZox)00ui%ZOFYWoeiRa$@gW@@saGHg=*NJqzeaG&f& z%!_iXaL-=F9dzBvUY7Wki-3Qn^>B*EtMXO?X7+e?gAx#~vQ`Su_pJ9ixcdG}vi7de zB*o0|8{&c!CC4iIR$aJo;GX@fzG>4-b|5B9j7#PFw?l*GZuei`IbW9!C+ZQ_apCrR zCSBBkW;>>ndj7Bu(}8GhwYIqeU~4Njz)zxywy@|x-hB{ngQwIg&O zygISqIfk7~+oylqQFkA6%nU~nYb@jPy#FE1mv8L+lLer}8#+ItpFU*qx>PD!1&xPt zLR`8@km-8m>^45n*C~2!gV!)jQze5H`_+04Ec7R@KG|AY2Fs5DtJV13sma~C*P-j3QUay(i0=SV5*Nqk%#zW! z4ImLp{>~8If^VvauXLWHG5Yhv(Zs_S((ZVbN%Y(rH|akS>%DBtIDF^dBw}cjtYspz!N2~*SOMmV%2Fm5 z3iXjXioHNJ^*tj`fL;f{4y1t5jRjk|@A$xQu6x_!)YG;q{yP&h;!?2f-sNu#JnwNN z{{qi&VgS#9<9NNYC4TYSSLg`2b8GUz=e=4aIeNvk)gOKM*Ki*RtNjGw=;aBS1INqE zPywdvqs}lNM=xCzp!OX@hZ%P`hU6(URMq!LToCQnulOgMC=zd;WX#y4vaa1qgTP;% zNi8-%u=V;y!ODMS<^Z~y5cu9lhhqj-=$U3w>_&MW23ncP3*_?~=0hcg z)Ef5sp374-XFZ|E0G*_{UMf-oC}l2OnBDH5FQL@6fdlW*bW;tlF?-rJlI<7K(48eQ zJEW&W_&$x+j<_>Expnp`Mt3^kYJA1I)Vy|UR6U*s!`Vq1=%yjPoGaSXZjHUEa2Xh~ zLk3Gf9Y1=-e<(XSBhCDRMOsGD6AP4#I)hM)(B zDfhR0Lo3V3Sqgr`vvCafRooppeGAJuL5_jrN=~~yBBGzP_1tVc0NZQrbJy1L`z@i$ z%+{>%M@`HkX@swGBf@O0BCeKCkQp8Fj!pV`v&BoeEvyR8t82|=WR^Oh% z@F`ZNejbP#rt7O)^}SjfS$&Hg-p4NFTm?BE`&wVq#0Kc%TP`=;4<|0|^W4Bi=qg9t z`TBem?d2dDV#Qzw@@;AD=_c(I6#mJ~nzWL%>$B!^lQ7L$HJf@he5P zGALgC>;?<&P7K)WO);~6Qjl8NrR*ds445BiTOVy#Dqakuk%>`hY*0=TYRiMRUG61? zhjv;GTxQ}{x$1tN)f#)sGPscH^LG7HhVyoCWA{`abSZu3K*Jt>Mi+RBP1Oe+1y5m1O~0Z zQ%|lf-S9Y(*yhDy+qudg);rl644zX%KvLLX{XY}*RBHa0*&v1I2fU$HHr1!=VqM;# z;)9l96+|O(LTg8rePE~`(;{>JX`#dN8e#bj@N*>FXrOG3yIQO`xi2m=x)BgH*=ckH znxXU6+QM2vA%0BhFkep1o!*U#x_cv=rSpfSG<6#jRd?#gSkc+zO3f`DN4xAFVOyQN z$!|~h%K{Ad_Qa3GpYeW#d1`9zq;6CI;Lm-&72tmgJVvh01g1Od$m6OOh79=qKfAQ2S_;mu!x z?zby4+VChAdW-xpNJ*nzVnEGouup3Do$qXsmRL}OY{DEB4? zFhM}@;L*Lp2Y0u#tSsq2ExayDf>sxViGK{(n7G$cedDCWoNLf1|9-W_?Byf@e960K zxVPMun*9hH^W92wUeB^_blHQ1`ird)k~!IhF4mJYb@~As2pUKf%C)sJ9<(}(eE zh-bWpI$*CQ9VVtjzdQHM%NfO6dCLPMJxqFIquP zniWn&z@PZo-DXiWfjH4lHpGqiVJy6V`EB;%lOD+Su| z$o59(IcF* zEU-sd&!>Xf7JuGAw8})@F{0a-P8#WGvsg`kG3Tdg^6qQ+kD2x&3cvGw7-0H9G~=yp zG~(&KxSlX}3ut(=^ThJ*yk=GVhXLb6BH&%aaQsCqxg^6b>=Ko9gT^C_3=Ki;IxWPK zog%hlfK*24DNFOaa<+Z)h(m4Rd% zo~HAnqaeH&fk71CL+L;2XTxgQ$(MiIlzHjGJRL%X0b3Fj#iHWFq$kzP$8L?a3w?wE zBs&D2=1te7%s*sewXkrVxA9wxa7aNHTKkT&VA^j1H2RfAVLAly#%jV`sGj3-ulgB4 zX^e^z4LC3FvC9y^_;A$aTvolV z%T(^`?b>#c81X`f5H7g0_Ats ztqF2sWBi1Pv>TYq<8q~;G*fidL@ppK#?E7FX0tv=K^nWvoX?=+&%`f?vAon251GFO zWUX=B0N}vA7||@l6Y<9%psXO(dxSe9A7_G>e7&^1q7!QZCVEKUKp4w;dOb{nl4_~$ z<==h}p3y-1GW%}>*>=ckUjJDK)IPKy5%%D0R30;pr(%7@x5Av?RM`T%0_;Jf{UbYUlsYWLb-2rn`40+;7VKO!Qp2t+dP~4$mU6#H63F&Af%^vK8J9g;vs$r)lJIY2jAoTaUfmC*DRt`*b&t_q?g5r z`#{_>`|fRipSFtBY~Fz_fQGOEOe>$lh@R6;r}jcWQhx&uYBS557X)_-oOh zHG)Fevbsr0P4|bV7AYurHva7eD6lO?hE85L3_>cuUfFtFF4TkgfHNH?9X*nBE{f}x z_k4QvnP9oF7Cx=JeSPgACgcf3RTvXK;XfqE%Z%k|t_iX_KF>ZJ{KJRwuiPDkRGe1F z%k2}paOWWl5)LhXG0#3_OdXLrlmTUI!Ho0i8zOfqagKavtMb#IuW;rJKI9S941Q8W=u>YXa(_AWHmogI%&dPJE4ma4M)(I`vnQT(Sx!Zn*7viJdI?dCGCZA3peY_|sV?`F2W zU$8$Oh&rjg$h!?Of@D`hc5kr@?ffyN zzOC5aQ$}_`>?2e>*%(4yLGv9EV-J=I`_ex91%8(%9M6Z1eAt+kNjfAN1sc`ACj0+ z8x_yH=S^g*{dnG#8TinpZy78DgUtJ z^B#;c4lk!sQ^xLj$=uweFJ{s_%#{Z>U62R2u5l4`eyu~=(UbBOGC0TBeHxz0>De6(e@L`xzEfh7e#+P^>6l% zxILpvj&6Gt;|v?Ug<`Y6(`mSwNAj+n%QKW!4L9E+bP~Afntbk?ZfmJm#XT$-*$Wge zjLIwi*(330n~d-0Jw5Y1ahk{YJA-ocgWE~aq3iE86?+gOC@$sS_WY8Q z##!7$ve88K+Skw>YIdl;hn=BJnchF&Y?R7spibU6gQY0J@e3Zn1F@G*QbKTb{ohU% ztV4*%DTb7Ew@>}XU^gxF+kK9(Q9V((la$nZxBPuq%rAz|jW!d5*5k5164J{7Eo;fn z96j=7=xIbtGY4V4*H#1N#titoi>t+$=QRh&3|T=`{VQckQ>GTMQzqAgX8V+cF-((d z7e;KPF0TK9csvMMN4eEP%>H~^&6kDb$%Xi#tbFsDi?Dh% zsxwcjpwN8bxFst2hT{FYVE3{(V#*dtlY^#vl0|uuV`iyQ)8Cdzy2^l9E(YOH?qV~W zKi9WhKI>O;mkj@vca=(7q>jBxso?pv_z|p&P_j}s?h}5i?WcI9$2S>Z84gZirbXd@ zy6qXDg)t73pyx1@gfOuJ~jzJ#6`z!k}cAIUKUZTzvnG>5*(DY<_GWDhH#^`dnn_mNY zb`CT>8rLf^cnR=soMirqNCS|0n6jgX?jlThX5c1)}C-#v0(t?-i zrtfYXpxmtgAxY3^sGt6yy35K`q8L+!@u?K`G2L-;I`?w?f*(7+#jH8wj`)RF^4;b) z0CM8D!v6fi)H;B@pPJQEi{$-W?il_AM+$epJ13@;XE1Ms+c#xVmQJDv;t>Jh$vNA} z8@ip$@9mpDQ|e77xo`ZK+NYVCUS%~{Vg{t#L*p?>HMGy^X=^l@2IO%O^8ico!*aYp z=EpI=ue)oS^14X#3H4yQ)#OiGbEW}tTjBrgEVxhQ`-LafRg`v9?X1R*8L(W3NX}$b zE5Gtnlwu-lbl{V&yUpl;bPCE7A9v3PI@y1;0a>FmY`>>qK5@D3Ws!PTZgYD|&!>Lc zxTSFSi_Z7zQh4^&`8mExRZf%L$LsRYO6ezI?BOu*-C_b}quSB#B6vE|MTTbZ9c%V@xxvEn-)rCmdr zE|dH4@XUNI_+&nMIE&c&%gCT=D1l2Qk$1xdZrw2}==06r$tyyeRAFvPY$_L7Od`7={KbZ;(y%3&lw?hqK4aG#;aG#^{hznQqH z7S*$|h$S7`bJy}`%(7J95F6hyDlAs)L9*%Ot90+!9We-ZFkSmyV6E*IfYA!y_-b;I z>N?x{yueRd&DNx`JxE){#auk`TkH~!>7Cu*$e4Oz%x;acWq0RsB4oY5E=-htNI!3; z{57#e13tl`4hGx25~hDIi9LfyD2npkcZ0^|%XVMm!~eKG_>V5fGoDJT{hL_+qqjSh zFq{I0cy*=~yrgMuiAlNK8_?ZCiTg(uN*w5XE?$>RmHwDAg0Wx!_*M=}Wl+t_Y=bWG zNu#;T^Z$>ihK3gU0$m#k?@(b^Oh|$r7-W=JSp^E1^L~qvcy$}+2n^|L+rMmcD?*LE zT+#S)iC;ztUQNt*W)dnB8^P?1>%1J_#6Q0cl$#IKX3A3YH;`__2J~sGObn8msW3uh zvYUw2xA0$}*_pZl%nF4b;0KpXTqiHKrBCigKng3a951;fHC@wbZe>zUriI~Bf>1$# znzjBXSvLNLBcrqXZ-WYju)`yyqbAT~=0EVvXzuN-?QP%?%LC=WcTfYktkr^i2E%mkNjWx$Rw69 z5T0F=C$Cr#;j<PB@6a;CdFiGEj@_y;)!fV;Y?_>l70dSwuf9|}S zSxLPf?r*C^Q1ORjJmoLU%rW9{6~@G$43KkQ|_ZO74GJA4{{9?=8uM6$Hw07?NWDjxSpm zuMH!a$?$g)7;78+`Q0&dC0V6zh@mFa`r_|c=>9wK)r)ie{4qmX+FWRc6(oCy+gEw-2oYSFhIrlv!Zpn0_{xlmu{IWGf+vV`|t|EOYk zZj5>Y!I@~)S=pv7P$IM%niwKxR%GP8_Z@Tgtl{snYJ9~RKX5pcI%P%yE%k}ToVB>Y z7ZD%By|R^f0rMIsde4I|H@oegE!XL70FInlA?ZZ8AAS2-bO%M=x9fy8+3HjKLqMoa z%xq!Ti@muk=wjgNUFwDb1g2FPa2rWNqAwhaj(I4bW>WJ512e7n5MwpI@g$0Or`;9G zWPxJUk!TOQ*mDe>)b$=OCqAYIi&O z(x4vMD76Vx7U1pHx{G_)dn#H#4aj4u^p|2@04m*foGhPv#kF4Yb{@lpq%6N8c(JFSym=Nt&|KP74}l& z6%>TXCb)qS8V{8<12nCe>#EFx`2k084J=g?=f*x$Pv~h*AUiEQs#zm&6;|i4IQQ=F z2ss?`;zM4%M~`2RgKGDRopH~Jld*7R_&YT&edr>G?@EA(5O8ybRcMFafsYO(zo`m3 z7~Q3!#Tv9@ekbrkn@odZ!Pgq*x%CKmq4*IJVOAPP{}tmE3r-a>Cy~t!!Zyg9DNfJS z_Y~nfN|ea~Y8sqv72qc1$`}6?6Z&6evK&F2ud11tpnWDlr5MkMu_88Zm^6goi!UOR z9KYJ*c#}Z<3DMnJyu3R2)t!@#G-&LbgKk_X1^K?&Ix^XM`hm-?)K?V~*jI@?t{ z_x{R#68~a(9;Y_Jv)c@MK;PM^wa#06M%VLq{aw21LGCCIJp)jLpKnrLtU5*JhjjY8 zfD))1@e|cvgX8Zj(ikf}`-*C6tMSxYNNRWdj?EzuLAIZMRV?;-)I8?J?)#G;O=LHe z-Nii3BhO;BkoUtle6i+R@R#Rj()*I35CV^7#7y*Qzu^HtQADa z0vx(x7r*>aH5StL7PFC;yHLS(INjz$yLO@8%d6*K1GFj}!gb zRZ)Lls4Fo=z`N4$EVuZ<4NXjK5W2O!bbxE4Ch<5}5O72?5t~}hQFqTD_t9{sRc^rk z_23GDXw=5_4yvJ`2WT$@Zi;vPUshO6Uo5-Zb0{GLf)1kC$(eKJ6T@}rzA$av^i5zibx?hIF&6Tc(QPd>yO#-xKkCtu5S$3=) z6o?YN=OggK5iE8Z2Gte<3)I2yVouN^iIUZ2DZwm9DR2-`*RY$DtWCBi=Sm&{(5~6$ ztvf*z$Rd%>DS^*3Jg-%rFGO+ObHeaM^Z%o}ib=?G0)27jJT$@Y+|nY8r5Y+PIf&JB zzX=bSEwcDeP)yCm{n0T^;5^-*a-Wm{42VH`j_;dFyd@5&OEQQD%d8eDv4UgS(TaF(jTo(4`#V=QKM(-Ey ze9%LPa$e91x)T9N6cfGo`I6q_kvlcytqUofb+#seNBinqD?AC6A~DR!D}*1OB#!fs z?TNT< zY4V%dn+@lsfo|W+WW@T2PPLx8?!Lh?JGW)(lwAnAZa=<0h2iITYGb0_D`mF82c26V zH+UgSaN)MKHG}=9E`&k&I?8*I6owngNB*`X9(~}?Ql04Gl{6P3zpDjXZa2jh{LSwf zWT{)(T&l+75QAReFSxb&+8Zt}+7s!8kxNW<=bdA_xH;OcB3@}9z}L*iSwI1%YtnSz zF=nOXGm*vA96z;5@_C!c}uk*XdnUIWsL{M44@YKauO%D4{%l_Rgvfi8k2=&DK z$5(UZdq^XRK*$A0;D*i#OH@5^>K4OOid1GGv`r5RFPs(cgBDWxU#U%pL`pQ2@%M?s z3O7kRUALvnZyLiSvX9{0vOHcOXq}if`t}nTNPMOA>!OHftd3Cv?TnXi-T6#W}6^f$4)bQYsududkm z$aVYJjh;JxX=msCEM|Gp9%j1C{30&5nnkmyEbV-^ZT7F$^l#Dcy;m7Ro~#x&$jZu{ zN{8B)9{#<4*XoB>QdKrdg|;R$BGo*{y*DeqE;LL7yMU2Yne6xaxcKgroc@WYw=yoI?zrFa%$ZkNXZ+bt@Y1 z_^e#&`<2Ew($5h|_lv&Q3H4k1cPa}yo+LwS=3b-GnrcU95XoJ;WE)mo7Uj%G?4(yOZsbvJ`+o~5l&_6y18Y)wx+MAFz0pI=__`qONV0y;I^1MOl zYM2e%gn@IU)0rTG!%Uh!X-16};B|4iqu7vnM0nptN&3-A>f;7vD(_#RJ%w(VsV97- z)$NOHKm2Qwhj>owE@}|lZT0Md3S}HcY2F=?y2Xhd?LK_SgJ1*=?uEi_BjiZ45*i!++lcS#gjxHHwt4Yn*>b1wET z&Ke*W%R#PNPr%Ch>d>V)rH!opW#JpgOt(e9$H|Oo65BO=Wg80ZdtyAaH0?27q^q?k zK3wjupSE_xVrC5>!5rA}`EY)^*PP&PdUNae(^qa;vG}{b4LdAp%ySZ{*yScAn+L;S zv+8KPQ{7JOu8ZIt`QI)h>nd$3Hgs=sQeO)`Qn&6L*k@60ZGAYkI>2h&LkrEhX5`{G z8A5`6hE_|4PHRjvx7EIADLw_s`%D^WP&oPnOM_-#&M2D1^1lE=mpJPvBhS7txTK!~Ef|vc`}mg}_=>_~4LXU*Az) zERHM0aFF9O!m45LFT(S0U#oS^=rMZXQ2&v09wHF;JIxL?>z_Qw84bDVT=Q7`@ut1b z1dJHq$qWv|Zlqsb{YvuzSuz`tvIfie$x77Zf-g_j?jv8-x@_KDKTvWenInD?{wXBMsF47EeGn` z=x+8N{0;I?va)U4sLhc^MXJ3iB*cD+*>QJ2%l%8dEA=|Nybq}FO7ptC+Wh-7xrwBV zt+Dim?07b*ux#TuHe|^-{idUhxdp9>T9ejJD zbMw1R$8k7t$kFvj(PAFKWigeBHnwLIi;8G2|lr0dMi8XHQOx3D^$;yYQk=KJ;AG$fL>`j}Wh$-AjaV z0n05ect>v9J^4ewM%h^btF1>(Gl@gadntQF5y4-~wPOJlWrPsY(&7_i!-Z9MXwSiq ziOj$8Eslf9%}RQRa>2m5YF)^V*AaoS)Awl77cjBr$d*6#x^ub%*LlPDYY#85pdpg& zjpcm_)>?u!3E%w2e45oFK7RNfYo43WDid5M?4LPRICxfH+%LAF?{3hMzt&TS%xGOp zJhnnkOr@r=83%l%v|iJPjO%o@8T0LUJ$Hv+jAgW%h=3e7{vJEIXDMx}lbCy~MHxTu zz?q>k-(2&9<&(n)Wp=3b8)Ectmnif{IKG? zt`1WV*jCbsrSW)Z$D!O*kt!6e&>ch3%KEJy?S3+_U~Fz!p9PwzJ}31 zU9cP8VypMJ%r4yU9`owYcNiAYUHCI4@E2@Pz8b46y%@qU`@QQ~7LpMiT8(|<06t+O0 zwL`AIP(Xc^^8NAAC0uJpCf->dx%l&@O*8YQto<+w{n}gtkuDUxC@1ka+)m#c=Pvu> zVIdwp*(vu!lJjxhq{uVxEhqVb``vCORb|V*b;{=#r9cl&%JmjiKN6uETt<0sN1Mln|DO1S5A+a^!`_;O z!FTBz?#Fs*-%-%=^sQQrQFG5%aF9ylL!mCxIIsMUoyn=))7fxRyw8=kDO*a85IJ5{f2iB^DGa?IPO*HixBx+G{t?f2wN`jFM8U=X*D7p~ zEpRH}{)dQ5rK!!U5gf46v%s}W6&K_==X9h`#@&;tW(o)8(|(aR5^+YPZ9Ox#xbznu zXx1#!DE(=?r*NVxv_tN72(GteiC5=9ER^F`AUp3{&%xN`iU1pq?ARZI+6u%!*0dC_ zDd)jSH3ypejm7VY8ZLKTf<+)7&++ddk|A!Mkh6)K6zXh*;e?Q`dCLF8AItxjDj$^3 Zso}Sq{Fyg?a{MECqG6z3{@5<;e*gqh6AJ(U literal 0 HcmV?d00001 diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index ddcdb5856..802ef5e3e 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -5,9 +5,13 @@ part 'crypto_currency.g.dart'; @HiveType(typeId: 0) class CryptoCurrency extends EnumerableItem with Serializable { - const CryptoCurrency({final String title, final int raw}) + const CryptoCurrency({final String title, this.tag, this.name, this.iconPath, final int raw}) : super(title: title, raw: raw); + final String tag; + final String name; + final String iconPath; + static const all = [ CryptoCurrency.xmr, CryptoCurrency.ada, @@ -24,39 +28,44 @@ class CryptoCurrency extends EnumerableItem with Serializable { CryptoCurrency.usdterc20, CryptoCurrency.xlm, CryptoCurrency.xrp, - CryptoCurrency.xhv + CryptoCurrency.xhv, + CryptoCurrency.zaddr, + CryptoCurrency.zec ]; - static const xmr = CryptoCurrency(title: 'XMR', raw: 0); - static const ada = CryptoCurrency(title: 'ADA', raw: 1); - static const bch = CryptoCurrency(title: 'BCH', raw: 2); - static const bnb = CryptoCurrency(title: 'BNB BEP2', raw: 3); - static const btc = CryptoCurrency(title: 'BTC', raw: 4); - static const dai = CryptoCurrency(title: 'DAI', raw: 5); - static const dash = CryptoCurrency(title: 'DASH', raw: 6); - static const eos = CryptoCurrency(title: 'EOS', raw: 7); - static const eth = CryptoCurrency(title: 'ETH', raw: 8); - static const ltc = CryptoCurrency(title: 'LTC', raw: 9); + static const xmr = CryptoCurrency(title: 'XMR', iconPath: 'assets/images/monero_icon.png', name: 'Monero', raw: 0); + static const ada = CryptoCurrency(title: 'ADA', iconPath: 'assets/images/ada_icon.png', name: 'Cardano', raw: 1); + static const bch = CryptoCurrency(title: 'BCH', iconPath: 'assets/images/bch_icon.png',name: 'Bitcoin Cash', raw: 2); + static const bnb = CryptoCurrency(title: 'BNB', iconPath: 'assets/images/bnb_icon.png', tag: 'BSC', name: 'Binance Coin', raw: 3); + static const btc = CryptoCurrency(title: 'BTC', iconPath: 'assets/images/btc.png', name: 'Bitcoin', raw: 4); + static const dai = CryptoCurrency(title: 'DAI', iconPath: 'assets/images/dai_icon.png', tag: 'ETH', name: 'Dai', raw: 5); + static const dash = CryptoCurrency(title: 'DASH', iconPath: 'assets/images/dash_icon.png', name: 'Dash', raw: 6); + static const eos = CryptoCurrency(title: 'EOS', iconPath: 'assets/images/eos_icon.png', name: 'EOS', raw: 7); + static const eth = CryptoCurrency(title: 'ETH', iconPath: 'assets/images/eth_icon.png', name: 'Ethereum', raw: 8); + static const ltc = CryptoCurrency(title: 'LTC', iconPath: 'assets/images/litecoin-ltc_icon.png', name: 'Litecoin',raw: 9); static const nano = CryptoCurrency(title: 'NANO', raw: 10); - static const trx = CryptoCurrency(title: 'TRX', raw: 11); - static const usdt = CryptoCurrency(title: 'USDT', raw: 12); - static const usdterc20 = CryptoCurrency(title: 'USDTERC20', raw: 13); - static const xlm = CryptoCurrency(title: 'XLM', raw: 14); - static const xrp = CryptoCurrency(title: 'XRP', raw: 15); - static const xhv = CryptoCurrency(title: 'XHV', raw: 16); - - static const xag = CryptoCurrency(title: 'XAG', raw: 17); - static const xau = CryptoCurrency(title: 'XAU', raw: 18); - static const xaud = CryptoCurrency(title: 'XAUD', raw: 19); - static const xbtc = CryptoCurrency(title: 'XBTC', raw: 20); - static const xcad = CryptoCurrency(title: 'XCAD', raw: 21); - static const xchf = CryptoCurrency(title: 'XCHF', raw: 22); - static const xcny = CryptoCurrency(title: 'XCNY', raw: 23); - static const xeur = CryptoCurrency(title: 'XEUR', raw: 24); - static const xgbp = CryptoCurrency(title: 'XGBP', raw: 25); - static const xjpy = CryptoCurrency(title: 'XJPY', raw: 26); - static const xnok = CryptoCurrency(title: 'XNOK', raw: 27); - static const xnzd = CryptoCurrency(title: 'XNZD', raw: 28); - static const xusd = CryptoCurrency(title: 'XUSD', raw: 29); + static const trx = CryptoCurrency(title: 'TRX', iconPath: 'assets/images/trx_icon.png', name: 'TRON', raw: 11); + static const usdt = CryptoCurrency(title: 'USDT', iconPath: 'assets/images/usdt_icon.png', tag: 'OMNI', name: 'USDT', raw: 12); + static const usdterc20 = CryptoCurrency(title: 'USDT', iconPath: 'assets/images/usdterc20_icon.png', tag: 'ETH', name: 'USDT', raw: 13); + static const xlm = CryptoCurrency(title: 'XLM', iconPath: 'assets/images/xlm_icon.png', name: 'Stellar', raw: 14); + static const xrp = CryptoCurrency(title: 'XRP', iconPath: 'assets/images/xrp_icon.png', name: 'Ripple', raw: 15); + static const xhv = CryptoCurrency(title: 'XHV', iconPath: 'assets/images/xhv_logo.png', name: 'Haven Protocol', raw: 16); + + static const xag = CryptoCurrency(title: 'XAG', tag: 'XHV', raw: 17); + static const xau = CryptoCurrency(title: 'XAU', tag: 'XHV', raw: 18); + static const xaud = CryptoCurrency(title: 'XAUD', tag: 'XHV', raw: 19); + static const xbtc = CryptoCurrency(title: 'XBTC', tag: 'XHV', raw: 20); + static const xcad = CryptoCurrency(title: 'XCAD', tag: 'XHV', raw: 21); + static const xchf = CryptoCurrency(title: 'XCHF', tag: 'XHV', raw: 22); + static const xcny = CryptoCurrency(title: 'XCNY', tag: 'XHV', raw: 23); + static const xeur = CryptoCurrency(title: 'XEUR', tag: 'XHV', raw: 24); + static const xgbp = CryptoCurrency(title: 'XGBP', tag: 'XHV', raw: 25); + static const xjpy = CryptoCurrency(title: 'XJPY', tag: 'XHV', raw: 26); + static const xnok = CryptoCurrency(title: 'XNOK', tag: 'XHV', raw: 27); + static const xnzd = CryptoCurrency(title: 'XNZD', tag: 'XHV', raw: 28); + static const xusd = CryptoCurrency(title: 'XUSD', tag: 'XHV', raw: 29); + + static const zaddr = CryptoCurrency(title: 'ZZEC', tag: 'ZEC', name: 'Shielded Zcash', iconPath: 'assets/images/zaddr_icon.png', raw: 30); + static const zec = CryptoCurrency(title: 'TZEC', tag: 'ZEC', name: 'Transparent Zcash', iconPath: 'assets/images/zec_icon.png', raw: 31); static CryptoCurrency deserialize({int raw}) { switch (raw) { @@ -120,6 +129,10 @@ class CryptoCurrency extends EnumerableItem with Serializable { return CryptoCurrency.xnzd; case 29: return CryptoCurrency.xusd; + case 30: + return CryptoCurrency.zaddr; + case 31: + return CryptoCurrency.zec; default: return null; } @@ -187,6 +200,10 @@ class CryptoCurrency extends EnumerableItem with Serializable { return CryptoCurrency.xnzd; case 'xusd': return CryptoCurrency.xusd; + case 'zaddr': + return CryptoCurrency.zaddr; + case 'zec': + return CryptoCurrency.zec; default: return null; } diff --git a/lib/exchange/changenow/changenow_exchange_provider.dart b/lib/exchange/changenow/changenow_exchange_provider.dart index ba5c18bdc..c6594516d 100644 --- a/lib/exchange/changenow/changenow_exchange_provider.dart +++ b/lib/exchange/changenow/changenow_exchange_provider.dart @@ -240,33 +240,23 @@ class ChangeNowExchangeProvider extends ExchangeProvider { } String networkFor(CryptoCurrency currency) { - const bnbTitle = 'bnb'; - switch (currency) { case CryptoCurrency.usdt: return CryptoCurrency.btc.title.toLowerCase(); - case CryptoCurrency.usdterc20: - return CryptoCurrency.eth.title.toLowerCase(); - case CryptoCurrency.bnb: - return bnbTitle; - case CryptoCurrency.dai: - return CryptoCurrency.eth.title.toLowerCase(); default: - return currency.title.toLowerCase(); + return currency.tag != null + ? currency.tag.toLowerCase() + : currency.title.toLowerCase(); + } } } - static String normalizeCryptoCurrency(CryptoCurrency currency) { - const bnbTitle = 'bnb'; - - switch(currency) { - case CryptoCurrency.bnb: - return bnbTitle; - case CryptoCurrency.usdterc20: - return CryptoCurrency.usdt.title.toLowerCase(); + String normalizeCryptoCurrency(CryptoCurrency currency) { + switch(currency) { + case CryptoCurrency.zec: + return 'zec'; default: return currency.title.toLowerCase(); } } -} diff --git a/lib/exchange/sideshift/sideshift_exchange_provider.dart b/lib/exchange/sideshift/sideshift_exchange_provider.dart index d4db3dcae..b828c2f6f 100644 --- a/lib/exchange/sideshift/sideshift_exchange_provider.dart +++ b/lib/exchange/sideshift/sideshift_exchange_provider.dart @@ -250,14 +250,15 @@ class SideShiftExchangeProvider extends ExchangeProvider { String get title => 'SideShift'; static String normalizeCryptoCurrency(CryptoCurrency currency) { - const bnbTitle = 'bsc'; - const usdterc20 = 'usdtErc20'; - switch (currency) { + case CryptoCurrency.zaddr: + return 'zaddr'; + case CryptoCurrency.zec: + return 'zec'; case CryptoCurrency.bnb: - return bnbTitle; + return currency.tag.toLowerCase(); case CryptoCurrency.usdterc20: - return usdterc20; + return 'usdtErc20'; default: return currency.title.toLowerCase(); } diff --git a/lib/src/screens/exchange/widgets/currency_picker.dart b/lib/src/screens/exchange/widgets/currency_picker.dart index 5e4a31831..abe07e56b 100644 --- a/lib/src/screens/exchange/widgets/currency_picker.dart +++ b/lib/src/screens/exchange/widgets/currency_picker.dart @@ -1,6 +1,5 @@ import 'dart:ui'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker_item_widget.dart'; -import 'package:cake_wallet/src/screens/exchange/widgets/currency_utils.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/picker_item.dart'; import 'package:cake_wallet/src/widgets/alert_close_button.dart'; import 'package:flutter/cupertino.dart'; @@ -35,45 +34,33 @@ class CurrencyPickerState extends State { CurrencyPickerState(this.items) : isSearchBarActive = false, textFieldValue = '', - subPickerItemsList = [], + subPickerItemsList = items, appBarTextStyle = - TextStyle(fontSize: 20, fontFamily: 'Lato', backgroundColor: Colors.transparent, color: Colors.white); + TextStyle(fontSize: 20, fontFamily: 'Lato', backgroundColor: Colors.transparent, color: Colors.white); + - @override - void initState() { - pickerItemsList = CryptoCurrency.all - .map((CryptoCurrency cur) => PickerItem(cur, - title: CurrencyUtils.titleForCurrency(cur), - iconPath: CurrencyUtils.iconPathForCurrency(cur), - tag: CurrencyUtils.tagForCurrency(cur), - description: CurrencyUtils.descriptionForCurrency(cur))) - .toList(); - cleanSubPickerItemsList(); - super.initState(); - } List> pickerItemsList; List items; bool isSearchBarActive; String textFieldValue; - List> subPickerItemsList; + List subPickerItemsList; TextStyle appBarTextStyle; - void cleanSubPickerItemsList() { - subPickerItemsList = pickerItemsList.where((element) => items.contains(element.original)).toList(); - } + void cleanSubPickerItemsList() => subPickerItemsList = items; - void currencySearchBySubstring(String subString, List> list) { + void currencySearchBySubstring(String subString) { setState(() { if (subString.isNotEmpty) { - subPickerItemsList = subPickerItemsList + subPickerItemsList = items .where((element) => - element.title.contains(subString.toUpperCase()) || - element.description.contains(subString.toLowerCase())) + (element.title != null ? element.title.toLowerCase().contains(subString.toLowerCase()) : false) || + (element.tag != null ? element.tag.toLowerCase().contains(subString.toLowerCase()) : false) || + (element.name != null ? element.name.toLowerCase().contains(subString.toLowerCase()) : false)) .toList(); - } else { - cleanSubPickerItemsList(); + return; } + cleanSubPickerItemsList(); }); } @@ -140,7 +127,7 @@ class CurrencyPickerState extends State { onChanged: (value) { this.textFieldValue = value; cleanSubPickerItemsList(); - currencySearchBySubstring(textFieldValue, subPickerItemsList); + currencySearchBySubstring(textFieldValue); }, ), ), @@ -152,10 +139,10 @@ class CurrencyPickerState extends State { AspectRatio( aspectRatio: 6, child: PickerItemWidget( - title: pickerItemsList[widget.selectedAtIndex].title, - iconPath: pickerItemsList[widget.selectedAtIndex].iconPath, + title: items[widget.selectedAtIndex].title, + iconPath: items[widget.selectedAtIndex].iconPath, isSelected: true, - tag: pickerItemsList[widget.selectedAtIndex].tag, + tag: items[widget.selectedAtIndex].tag, ), ), Flexible( @@ -167,10 +154,10 @@ class CurrencyPickerState extends State { setState(() { widget.selectedAtIndex = index; }); - widget.onItemSelected(subPickerItemsList[index].original); + widget.onItemSelected(subPickerItemsList[index]); if (widget.isConvertFrom && !widget.isMoneroWallet && - (subPickerItemsList[index].original == CryptoCurrency.xmr)) { + (subPickerItemsList[index] == CryptoCurrency.xmr)) { } else { Navigator.of(context).pop(); } diff --git a/lib/src/screens/exchange/widgets/currency_picker_widget.dart b/lib/src/screens/exchange/widgets/currency_picker_widget.dart index ec0a11356..254e85d4a 100644 --- a/lib/src/screens/exchange/widgets/currency_picker_widget.dart +++ b/lib/src/screens/exchange/widgets/currency_picker_widget.dart @@ -14,7 +14,7 @@ class CurrencyPickerWidget extends StatelessWidget { final int crossAxisCount; final int selectedAtIndex; final Function pickListItem; - final List> pickerItemsList; + final List pickerItemsList; final ScrollController _scrollController = ScrollController(); diff --git a/lib/src/screens/exchange/widgets/currency_utils.dart b/lib/src/screens/exchange/widgets/currency_utils.dart deleted file mode 100644 index 83c6d0af9..000000000 --- a/lib/src/screens/exchange/widgets/currency_utils.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:cw_core/crypto_currency.dart'; - -class CurrencyUtils { - static String tagForCurrency(CryptoCurrency cur) { - switch (cur) { - case CryptoCurrency.bnb: - return 'BEP2'; - case CryptoCurrency.dai: - return 'ETH'; - case CryptoCurrency.usdt: - return 'OMNI'; - case CryptoCurrency.usdterc20: - return 'ETH'; - default: - return null; - } - } - - static String iconPathForCurrency(CryptoCurrency cur) { - switch (cur) { - case CryptoCurrency.xmr: - return 'assets/images/monero_icon.png'; - case CryptoCurrency.ada: - return 'assets/images/ada_icon.png'; - case CryptoCurrency.bch: - return 'assets/images/bch_icon.png'; - case CryptoCurrency.bnb: - return 'assets/images/bnb_icon.png'; - case CryptoCurrency.btc: - return 'assets/images/btc.png'; - case CryptoCurrency.dai: - return 'assets/images/dai_icon.png'; - case CryptoCurrency.dash: - return 'assets/images/dash_icon.png'; - case CryptoCurrency.eos: - return 'assets/images/eos_icon.png'; - case CryptoCurrency.eth: - return 'assets/images/eth_icon.png'; - case CryptoCurrency.ltc: - return 'assets/images/litecoin-ltc_icon.png'; - case CryptoCurrency.trx: - return 'assets/images/trx_icon.png'; - case CryptoCurrency.usdt: - return 'assets/images/usdt_icon.png'; - case CryptoCurrency.usdterc20: - return 'assets/images/usdterc20_icon.png'; - case CryptoCurrency.xlm: - return 'assets/images/xlm_icon.png'; - case CryptoCurrency.xrp: - return 'assets/images/xrp_icon.png'; - case CryptoCurrency.xhv: - return 'assets/images/xhv_logo.png'; - default: - return null; - } - } - - static String titleForCurrency(CryptoCurrency cur) { - switch (cur) { - case CryptoCurrency.bnb: - return 'BNB'; - case CryptoCurrency.usdterc20: - return 'USDT'; - default: - return cur.title; - } - } - - static String descriptionForCurrency(CryptoCurrency cur) { - switch (cur) { - case CryptoCurrency.xmr: - return 'monero'; - case CryptoCurrency.ada: - return 'cardano'; - case CryptoCurrency.bch: - return 'bitcoin cash'; - case CryptoCurrency.bnb: - return 'binance bep2'; - case CryptoCurrency.btc: - return 'bitcoin'; - case CryptoCurrency.dai: - return 'dai eth'; - case CryptoCurrency.eth: - return 'ethereum'; - case CryptoCurrency.ltc: - return 'litecoin'; - case CryptoCurrency.trx: - return 'tron'; - case CryptoCurrency.usdt: - return 'usdt omni'; - case CryptoCurrency.usdterc20: - return 'tether ERC20 eth'; - case CryptoCurrency.xlm: - return 'lumens'; - case CryptoCurrency.xrp: - return 'ripple'; - default: - return cur.title; - } - } -} diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index a330675fd..ac21408a3 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -176,7 +176,7 @@ class ExchangeCardState extends State { padding: EdgeInsets.only(right: 5), child: widget.imageArrow, ), - Text(_selectedCurrency.toString() + ':', + Text(_selectedCurrency.toString(), style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16, @@ -184,6 +184,40 @@ class ExchangeCardState extends State { ]), ), ), + _selectedCurrency.tag != null ? Padding( + padding: const EdgeInsets.only(right:3.0), + child: Container( + height: 32, + decoration: BoxDecoration( + color: widget.addressButtonsColor ?? Theme.of(context) + .primaryTextTheme + .display1 + .color, + borderRadius: + BorderRadius.all(Radius.circular(6))), + child: Center( + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Text(_selectedCurrency.tag, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Theme.of(context) + .primaryTextTheme + .display1 + .decorationColor)), + ), + ), + ), + ) : Container(), + Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Text(':', + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white)), + ), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index eb110f3b1..69858a3ac 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -173,85 +173,129 @@ class SendCardState extends State Observer( builder: (_) => Padding( padding: const EdgeInsets.only(top: 20), - child: Stack( - children: [ - BaseTextFormField( - focusNode: cryptoAmountFocus, - controller: cryptoAmountController, - keyboardType: - TextInputType.numberWithOptions( - signed: false, decimal: true), - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')) - ], - prefixIcon: Padding( - padding: EdgeInsets.only(top: 9), - child: Text( - sendViewModel.selectedCryptoCurrency.title + - ':', + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + children: [ + Text( + sendViewModel.selectedCryptoCurrency.title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + )), + sendViewModel.selectedCryptoCurrency.tag != null ? Padding( + padding: const EdgeInsets.fromLTRB(3.0,0,3.0,0), + child: Container( + height: 32, + decoration: BoxDecoration( + color: Theme.of(context) + .primaryTextTheme + .display1 + .color, + borderRadius: + BorderRadius.all(Radius.circular(6))), + child: Center( + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Text( sendViewModel.selectedCryptoCurrency.tag, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Theme.of(context) + .primaryTextTheme + .display1 + .decorationColor)), + ), + ), + ), + ) : Container(), + Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Text(':', style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - )), + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white)), ), - suffixIcon: SizedBox( - width: prefixIconWidth, - ), - hintText: '0.0000', - borderColor: Theme.of(context) - .primaryTextTheme - .headline - .color, - textStyle: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.white), - placeholderTextStyle: TextStyle( - color: Theme.of(context) - .primaryTextTheme - .headline - .decorationColor, - fontWeight: FontWeight.w500, - fontSize: 14), - validator: output.sendAll - ? sendViewModel.allAmountValidator - : sendViewModel - .amountValidator), - if (!sendViewModel.isBatchSending) Positioned( - top: 2, - right: 0, - child: Container( - width: prefixIconWidth, - height: prefixIconHeight, - child: InkWell( - onTap: () async => - output.setSendAll(), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context) - .primaryTextTheme - .display1 - .color, - borderRadius: - BorderRadius.all( - Radius.circular(6))), - child: Center( - child: Text( - S.of(context).all, - textAlign: - TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: - FontWeight.bold, - color: - Theme.of(context) - .primaryTextTheme - .display1 - .decorationColor))), - ))))]) + ], + ), + ), + Expanded( + child: Stack( + children: [ + BaseTextFormField( + focusNode: cryptoAmountFocus, + controller: cryptoAmountController, + keyboardType: + TextInputType.numberWithOptions( + signed: false, decimal: true), + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')) + ], + suffixIcon: SizedBox( + width: prefixIconWidth, + ), + hintText: '0.0000', + borderColor: Colors.transparent, + textStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.white), + placeholderTextStyle: TextStyle( + color: Theme.of(context) + .primaryTextTheme + .headline + .decorationColor, + fontWeight: FontWeight.w500, + fontSize: 14), + validator: output.sendAll + ? sendViewModel.allAmountValidator + : sendViewModel + .amountValidator), + if (!sendViewModel.isBatchSending) Positioned( + top: 2, + right: 0, + child: Container( + width: prefixIconWidth, + height: prefixIconHeight, + child: InkWell( + onTap: () async => + output.setSendAll(), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .primaryTextTheme + .display1 + .color, + borderRadius: + BorderRadius.all( + Radius.circular(6))), + child: Center( + child: Text( + S.of(context).all, + textAlign: + TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: + FontWeight.bold, + color: + Theme.of(context) + .primaryTextTheme + .display1 + .decorationColor))), + ))))]), + ), + ], + ) )), + Divider(height: 1,color: Theme.of(context) + .primaryTextTheme + .headline + .decorationColor), Observer( builder: (_) => Padding( padding: EdgeInsets.only(top: 10), From a63c099f26d77c021e843a786fa20b2071230e7e Mon Sep 17 00:00:00 2001 From: M Date: Thu, 14 Jul 2022 12:48:59 +0100 Subject: [PATCH 19/22] Add aditional check if wallet exists with special name --- lib/core/wallet_creation_service.dart | 22 ++++++++++++++++++- lib/di.dart | 3 ++- lib/view_model/wallet_creation_vm.dart | 13 ++++++----- lib/view_model/wallet_new_vm.dart | 10 ++++----- .../wallet_restoration_from_keys_vm.dart | 7 +++--- .../wallet_restoration_from_seed_vm.dart | 7 +++--- lib/view_model/wallet_restore_view_model.dart | 11 +++++----- 7 files changed, 45 insertions(+), 28 deletions(-) diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index dcb7983f5..636547308 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -1,6 +1,8 @@ import 'package:cake_wallet/di.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:hive/hive.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cw_core/wallet_base.dart'; @@ -14,7 +16,8 @@ class WalletCreationService { {WalletType initialType, this.secureStorage, this.keyService, - this.sharedPreferences}) + this.sharedPreferences, + this.walletInfoSource}) : type = initialType { if (type != null) { changeWalletType(type: type); @@ -25,6 +28,7 @@ class WalletCreationService { final FlutterSecureStorage secureStorage; final SharedPreferences sharedPreferences; final KeyService keyService; + final Box walletInfoSource; WalletService _service; void changeWalletType({@required WalletType type}) { @@ -32,7 +36,21 @@ class WalletCreationService { _service = getIt.get(param1: type); } + bool exists(String name) { + final walletName = name.toLowerCase(); + return walletInfoSource + .values + .any((walletInfo) => walletInfo.name.toLowerCase() == walletName); + } + + void checkIfExists(String name) { + if (exists(name)) { + throw Exception('Wallet with name ${name} already exists!'); + } + } + Future create(WalletCredentials credentials) async { + checkIfExists(credentials.name); final password = generateWalletPassword(type); credentials.password = password; await keyService.saveWalletPassword( @@ -41,6 +59,7 @@ class WalletCreationService { } Future restoreFromKeys(WalletCredentials credentials) async { + checkIfExists(credentials.name); final password = generateWalletPassword(type); credentials.password = password; await keyService.saveWalletPassword( @@ -49,6 +68,7 @@ class WalletCreationService { } Future restoreFromSeed(WalletCredentials credentials) async { + checkIfExists(credentials.name); final password = generateWalletPassword(type); credentials.password = password; await keyService.saveWalletPassword( diff --git a/lib/di.dart b/lib/di.dart index 7391e59ca..29fed4c56 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -215,7 +215,8 @@ Future setup( initialType: type, keyService: getIt.get(), secureStorage: getIt.get(), - sharedPreferences: getIt.get())); + sharedPreferences: getIt.get(), + walletInfoSource: _walletInfoSource)); getIt.registerFactoryParam((type, _) => WalletNewVM(getIt.get(), diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index 8a4c76eb5..50629bb9b 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/core/wallet_creation_service.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -15,7 +16,7 @@ part 'wallet_creation_vm.g.dart'; class WalletCreationVM = WalletCreationVMBase with _$WalletCreationVM; abstract class WalletCreationVMBase with Store { - WalletCreationVMBase(this._appStore, this._walletInfoSource, + WalletCreationVMBase(this._appStore, this._walletInfoSource, this.walletCreationService, {@required this.type, @required this.isRecovery}) { state = InitialExecutionState(); name = ''; @@ -29,14 +30,12 @@ abstract class WalletCreationVMBase with Store { WalletType type; final bool isRecovery; + final WalletCreationService walletCreationService; final Box _walletInfoSource; final AppStore _appStore; - bool nameExists(String name) { - final walletNameList = _walletInfoSource.values.map((e) => e.name.toLowerCase()).toList(); - - return walletNameList.contains(name.toLowerCase()); - } + bool nameExists(String name) + => walletCreationService.exists(name); Future create({dynamic options}) async { try { @@ -44,6 +43,8 @@ abstract class WalletCreationVMBase with Store { if (name?.isEmpty ?? true) { name = await generateName(); } + + walletCreationService.checkIfExists(name); final dirPath = await pathForWalletDir(name: name, type: type); final path = await pathForWallet(name: name, type: type); final credentials = getCredentials(options); diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index b044ea369..97a2aaee2 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -17,19 +17,17 @@ part 'wallet_new_vm.g.dart'; class WalletNewVM = WalletNewVMBase with _$WalletNewVM; abstract class WalletNewVMBase extends WalletCreationVM with Store { - WalletNewVMBase(AppStore appStore, this._walletCreationService, + WalletNewVMBase(AppStore appStore, WalletCreationService walletCreationService, Box walletInfoSource, {@required WalletType type}) : selectedMnemonicLanguage = '', - super(appStore, walletInfoSource, type: type, isRecovery: false); + super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: false); @observable String selectedMnemonicLanguage; bool get hasLanguageSelector => type == WalletType.monero || type == WalletType.haven; - final WalletCreationService _walletCreationService; - @override WalletCredentials getCredentials(dynamic options) { switch (type) { @@ -50,7 +48,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { @override Future process(WalletCredentials credentials) async { - _walletCreationService.changeWalletType(type: type); - return _walletCreationService.create(credentials); + walletCreationService.changeWalletType(type: type); + return walletCreationService.create(credentials); } } diff --git a/lib/view_model/wallet_restoration_from_keys_vm.dart b/lib/view_model/wallet_restoration_from_keys_vm.dart index 060745695..a51d8011c 100644 --- a/lib/view_model/wallet_restoration_from_keys_vm.dart +++ b/lib/view_model/wallet_restoration_from_keys_vm.dart @@ -20,9 +20,9 @@ class WalletRestorationFromKeysVM = WalletRestorationFromKeysVMBase abstract class WalletRestorationFromKeysVMBase extends WalletCreationVM with Store { WalletRestorationFromKeysVMBase(AppStore appStore, - this._walletCreationService, Box walletInfoSource, + WalletCreationService walletCreationService, Box walletInfoSource, {@required WalletType type, @required this.language}) - : super(appStore, walletInfoSource, type: type, isRecovery: true); + : super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true); @observable int height; @@ -42,7 +42,6 @@ abstract class WalletRestorationFromKeysVMBase extends WalletCreationVM bool get hasRestorationHeight => type == WalletType.monero; final String language; - final WalletCreationService _walletCreationService; @override WalletCredentials getCredentials(dynamic options) { @@ -68,5 +67,5 @@ abstract class WalletRestorationFromKeysVMBase extends WalletCreationVM @override Future process(WalletCredentials credentials) async => - _walletCreationService.restoreFromKeys(credentials); + walletCreationService.restoreFromKeys(credentials); } diff --git a/lib/view_model/wallet_restoration_from_seed_vm.dart b/lib/view_model/wallet_restoration_from_seed_vm.dart index 1af730f13..ffd4184e0 100644 --- a/lib/view_model/wallet_restoration_from_seed_vm.dart +++ b/lib/view_model/wallet_restoration_from_seed_vm.dart @@ -20,9 +20,9 @@ class WalletRestorationFromSeedVM = WalletRestorationFromSeedVMBase abstract class WalletRestorationFromSeedVMBase extends WalletCreationVM with Store { WalletRestorationFromSeedVMBase(AppStore appStore, - this._walletCreationService, Box walletInfoSource, + WalletCreationService walletCreationService, Box walletInfoSource, {@required WalletType type, @required this.language, this.seed}) - : super(appStore, walletInfoSource, type: type, isRecovery: true); + : super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true); @observable String seed; @@ -33,7 +33,6 @@ abstract class WalletRestorationFromSeedVMBase extends WalletCreationVM bool get hasRestorationHeight => type == WalletType.monero; final String language; - final WalletCreationService _walletCreationService; @override WalletCredentials getCredentials(dynamic options) { @@ -53,5 +52,5 @@ abstract class WalletRestorationFromSeedVMBase extends WalletCreationVM @override Future process(WalletCredentials credentials) async => - _walletCreationService.restoreFromSeed(credentials); + walletCreationService.restoreFromSeed(credentials); } diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 1943f76b9..83dcc57ce 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -22,7 +22,7 @@ class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreViewModel; abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { - WalletRestoreViewModelBase(AppStore appStore, this._walletCreationService, + WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService, Box walletInfoSource, {@required WalletType type}) : availableModes = (type == WalletType.monero || type == WalletType.haven) @@ -30,11 +30,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { : [WalletRestoreMode.seed], hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven, hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven, - super(appStore, walletInfoSource, type: type, isRecovery: true) { + super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) { isButtonEnabled = !hasSeedLanguageSelector && !hasBlockchainHeightLanguageSelector; mode = WalletRestoreMode.seed; - _walletCreationService.changeWalletType(type: type); + walletCreationService.changeWalletType(type: type); } static const moneroSeedMnemonicLength = 25; @@ -44,7 +44,6 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { final List availableModes; final bool hasSeedLanguageSelector; final bool hasBlockchainHeightLanguageSelector; - final WalletCreationService _walletCreationService; @observable WalletRestoreMode mode; @@ -123,9 +122,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { @override Future process(WalletCredentials credentials) async { if (mode == WalletRestoreMode.keys) { - return _walletCreationService.restoreFromKeys(credentials); + return walletCreationService.restoreFromKeys(credentials); } - return _walletCreationService.restoreFromSeed(credentials); + return walletCreationService.restoreFromSeed(credentials); } } From 9ea57146f77d55e48cbdb5af476d20a0835fefbb Mon Sep 17 00:00:00 2001 From: M Date: Thu, 14 Jul 2022 12:56:37 +0100 Subject: [PATCH 20/22] Remove backups from menu --- lib/src/screens/dashboard/wallet_menu.dart | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/src/screens/dashboard/wallet_menu.dart b/lib/src/screens/dashboard/wallet_menu.dart index 704ddb2a6..b7d12a190 100644 --- a/lib/src/screens/dashboard/wallet_menu.dart +++ b/lib/src/screens/dashboard/wallet_menu.dart @@ -52,24 +52,6 @@ class WalletMenu { image: Image.asset('assets/images/open_book_menu.png', height: 16, width: 16), handler: () => Navigator.of(context).pushNamed(Routes.addressBook)), - WalletMenuItem( - title: S.current.backup, - image: Image.asset('assets/images/restore_wallet.png', - height: 16, - width: 16, - color: Palette.darkBlue), - handler: () { - Navigator - .of(context) - .pushNamed( - Routes.auth, - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { - if (isAuthenticatedSuccessfully) { - auth.close(); - Navigator.of(auth.context).pushNamed(Routes.backup); - } - }); - }), WalletMenuItem( title: S.current.settings_title, image: Image.asset('assets/images/settings_menu.png', From 8fec3272cf3d7f9d6a36e431580aacda636331d8 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 14 Jul 2022 20:19:47 +0100 Subject: [PATCH 21/22] Temporarily remove ZZEC and TZEC --- cw_core/lib/crypto_currency.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 802ef5e3e..fead230d9 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -29,8 +29,8 @@ class CryptoCurrency extends EnumerableItem with Serializable { CryptoCurrency.xlm, CryptoCurrency.xrp, CryptoCurrency.xhv, - CryptoCurrency.zaddr, - CryptoCurrency.zec + //CryptoCurrency.zaddr, + //CryptoCurrency.zec ]; static const xmr = CryptoCurrency(title: 'XMR', iconPath: 'assets/images/monero_icon.png', name: 'Monero', raw: 0); static const ada = CryptoCurrency(title: 'ADA', iconPath: 'assets/images/ada_icon.png', name: 'Cardano', raw: 1); From b72443a8c4f2fb50e34a7e5510afb52cf13f366d Mon Sep 17 00:00:00 2001 From: mkyq <53115730+mkyq@users.noreply.github.com> Date: Tue, 19 Jul 2022 15:29:28 +0100 Subject: [PATCH 22/22] Release 4.4.3 (#415) * Add ability for change password for wallets classes. * Update generateWalletPassword * Add WalletLoadingService * Add update monero password after wallet loading. * Update version for Cake Wallet to 4.4.2 (103) * Changed version for Cake Wallet to 4.4.3 (104). * Changed version for Cake Wallet android * Changed version for Monero.com ios and android. --- .../lib/electrum_transaction_history.dart | 7 ++- cw_bitcoin/lib/electrum_wallet.dart | 9 +++- cw_core/lib/wallet_base.dart | 2 + cw_haven/ios/Classes/haven_api.cpp | 10 ++++ cw_haven/lib/api/signatures.dart | 2 + cw_haven/lib/api/types.dart | 2 + cw_haven/lib/api/wallet.dart | 19 +++++++ cw_haven/lib/haven_wallet.dart | 5 ++ cw_monero/ios/Classes/monero_api.cpp | 10 ++++ cw_monero/lib/api/signatures.dart | 2 + cw_monero/lib/api/types.dart | 2 + cw_monero/lib/api/wallet.dart | 19 +++++++ cw_monero/lib/monero_wallet.dart | 5 ++ lib/core/generate_wallet_password.dart | 10 +--- lib/core/wallet_creation_service.dart | 42 ++++++++++++--- lib/core/wallet_loading_service.dart | 52 +++++++++++++++++++ lib/di.dart | 9 +++- lib/entities/load_current_wallet.dart | 7 ++- lib/entities/preferences_key.dart | 4 ++ .../wallet_list/wallet_list_view_model.dart | 15 +++--- .../wallet_restoration_from_keys_vm.dart | 2 +- .../wallet_restoration_from_seed_vm.dart | 2 +- lib/view_model/wallet_restore_view_model.dart | 2 +- scripts/android/app_env.sh | 8 +-- scripts/ios/app_env.sh | 8 +-- 25 files changed, 214 insertions(+), 41 deletions(-) create mode 100644 lib/core/wallet_loading_service.dart diff --git a/cw_bitcoin/lib/electrum_transaction_history.dart b/cw_bitcoin/lib/electrum_transaction_history.dart index d1459d7f7..94f328900 100644 --- a/cw_bitcoin/lib/electrum_transaction_history.dart +++ b/cw_bitcoin/lib/electrum_transaction_history.dart @@ -24,7 +24,7 @@ abstract class ElectrumTransactionHistoryBase } final WalletInfo walletInfo; - final String _password; + String _password; int _height; Future init() async => await _load(); @@ -51,6 +51,11 @@ abstract class ElectrumTransactionHistoryBase } } + Future changePassword(String password) async { + _password = password; + await save(); + } + Future> _read() async { final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 2902a21ca..f6d3f30f8 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -109,7 +109,7 @@ abstract class ElectrumWalletBase extends WalletBase BitcoinWalletKeys( wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey); - final String _password; + String _password; List unspentCoins; List _feeRates; Map> _scripthashesUpdateSubject; @@ -392,6 +392,13 @@ abstract class ElectrumWalletBase extends WalletBase changePassword(String password) async { + _password = password; + await save(); + await transactionHistory.changePassword(password); + } + bitcoin.ECPair keyPairFor({@required int index}) => generateKeyPair(hd: hd, index: index, network: networkType); diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index b8c1aaa45..173ba8155 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -69,4 +69,6 @@ abstract class WalletBase< Future rescan({int height}); void close(); + + Future changePassword(String password); } diff --git a/cw_haven/ios/Classes/haven_api.cpp b/cw_haven/ios/Classes/haven_api.cpp index 92bca4294..c1013bf87 100644 --- a/cw_haven/ios/Classes/haven_api.cpp +++ b/cw_haven/ios/Classes/haven_api.cpp @@ -565,6 +565,16 @@ extern "C" store_lock.unlock(); } + bool set_password(char *password, Utf8Box &error) { + bool is_changed = get_current_wallet()->setPassword(std::string(password)); + + if (!is_changed) { + error = Utf8Box(strdup(get_current_wallet()->errorString().c_str())); + } + + return is_changed; + } + bool transaction_create(char *address, char *asset_type, char *payment_id, char *amount, uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) { diff --git a/cw_haven/lib/api/signatures.dart b/cw_haven/lib/api/signatures.dart index c9db9ac8d..9dd1c8dac 100644 --- a/cw_haven/lib/api/signatures.dart +++ b/cw_haven/lib/api/signatures.dart @@ -51,6 +51,8 @@ typedef set_recovering_from_seed = Void Function(Int8); typedef store_c = Void Function(Pointer); +typedef set_password = Int8 Function(Pointer password, Pointer error); + typedef set_listener = Void Function(); typedef get_syncing_height = Int64 Function(); diff --git a/cw_haven/lib/api/types.dart b/cw_haven/lib/api/types.dart index 09b6f77e0..878297501 100644 --- a/cw_haven/lib/api/types.dart +++ b/cw_haven/lib/api/types.dart @@ -51,6 +51,8 @@ typedef SetRecoveringFromSeed = void Function(int); typedef Store = void Function(Pointer); +typedef SetPassword = int Function(Pointer password, Pointer error); + typedef SetListener = void Function(); typedef GetSyncingHeight = int Function(); diff --git a/cw_haven/lib/api/wallet.dart b/cw_haven/lib/api/wallet.dart index e490743d2..3370fd3e0 100644 --- a/cw_haven/lib/api/wallet.dart +++ b/cw_haven/lib/api/wallet.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:ffi'; import 'package:ffi/ffi.dart'; +import 'package:cw_haven/api/structs/ut8_box.dart'; import 'package:cw_haven/api/convert_utf8_to_string.dart'; import 'package:cw_haven/api/signatures.dart'; import 'package:cw_haven/api/types.dart'; @@ -67,6 +68,9 @@ final setRecoveringFromSeedNative = havenApi final storeNative = havenApi.lookup>('store').asFunction(); +final setPasswordNative = + havenApi.lookup>('set_password').asFunction(); + final setListenerNative = havenApi .lookup>('set_listener') .asFunction(); @@ -193,6 +197,21 @@ void storeSync() { free(pathPointer); } +void setPasswordSync(String password) { + final passwordPointer = Utf8.toUtf8(password); + final errorMessagePointer = allocate(); + final changed = setPasswordNative(passwordPointer, errorMessagePointer) != 0; + free(passwordPointer); + + if (!changed) { + final message = errorMessagePointer.ref.getValue(); + free(errorMessagePointer); + throw Exception(message); + } + + free(errorMessagePointer); +} + void closeCurrentWallet() => closeCurrentWalletNative(); String getSecretViewKey() => diff --git a/cw_haven/lib/haven_wallet.dart b/cw_haven/lib/haven_wallet.dart index 7fb8ed157..c107d2f52 100644 --- a/cw_haven/lib/haven_wallet.dart +++ b/cw_haven/lib/haven_wallet.dart @@ -240,6 +240,11 @@ abstract class HavenWalletBase extends WalletBase changePassword(String password) async { + haven_wallet.setPasswordSync(password); + } + Future getNodeHeight() async => haven_wallet.getNodeHeight(); Future isConnected() async => haven_wallet.isConnected(); diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index f81f63d16..4d3f5f56b 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -467,6 +467,16 @@ extern "C" store_lock.unlock(); } + bool set_password(char *password, Utf8Box &error) { + bool is_changed = get_current_wallet()->setPassword(std::string(password)); + + if (!is_changed) { + error = Utf8Box(strdup(get_current_wallet()->errorString().c_str())); + } + + return is_changed; + } + bool transaction_create(char *address, char *payment_id, char *amount, uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) { diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart index 9781aff2e..16f983480 100644 --- a/cw_monero/lib/api/signatures.dart +++ b/cw_monero/lib/api/signatures.dart @@ -47,6 +47,8 @@ typedef set_recovering_from_seed = Void Function(Int8); typedef store_c = Void Function(Pointer); +typedef set_password = Int8 Function(Pointer password, Pointer error); + typedef set_listener = Void Function(); typedef get_syncing_height = Int64 Function(); diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart index 4caa1283f..3438b89fc 100644 --- a/cw_monero/lib/api/types.dart +++ b/cw_monero/lib/api/types.dart @@ -47,6 +47,8 @@ typedef SetRecoveringFromSeed = void Function(int); typedef Store = void Function(Pointer); +typedef SetPassword = int Function(Pointer password, Pointer error); + typedef SetListener = void Function(); typedef GetSyncingHeight = int Function(); diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 72507e912..9e84d7865 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:ffi'; import 'package:ffi/ffi.dart'; +import 'package:cw_monero/api/structs/ut8_box.dart'; import 'package:cw_monero/api/convert_utf8_to_string.dart'; import 'package:cw_monero/api/signatures.dart'; import 'package:cw_monero/api/types.dart'; @@ -67,6 +68,9 @@ final setRecoveringFromSeedNative = moneroApi final storeNative = moneroApi.lookup>('store').asFunction(); +final setPasswordNative = + moneroApi.lookup>('set_password').asFunction(); + final setListenerNative = moneroApi .lookup>('set_listener') .asFunction(); @@ -197,6 +201,21 @@ void storeSync() { free(pathPointer); } +void setPasswordSync(String password) { + final passwordPointer = Utf8.toUtf8(password); + final errorMessagePointer = allocate(); + final changed = setPasswordNative(passwordPointer, errorMessagePointer) != 0; + free(passwordPointer); + + if (!changed) { + final message = errorMessagePointer.ref.getValue(); + free(errorMessagePointer); + throw Exception(message); + } + + free(errorMessagePointer); +} + void closeCurrentWallet() => closeCurrentWalletNative(); String getSecretViewKey() => diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 6ac31c9f0..175bd96f5 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -258,6 +258,11 @@ abstract class MoneroWalletBase extends WalletBase changePassword(String password) async { + monero_wallet.setPasswordSync(password); + } + Future getNodeHeight() async => monero_wallet.getNodeHeight(); Future isConnected() async => monero_wallet.isConnected(); diff --git a/lib/core/generate_wallet_password.dart b/lib/core/generate_wallet_password.dart index 71fb68d9c..c9a9fac57 100644 --- a/lib/core/generate_wallet_password.dart +++ b/lib/core/generate_wallet_password.dart @@ -1,12 +1,6 @@ import 'package:uuid/uuid.dart'; import 'package:cw_core/key.dart'; -import 'package:cw_core/wallet_type.dart'; -String generateWalletPassword(WalletType type) { - switch (type) { - case WalletType.monero: - return Uuid().v4(); - default: - return generateKey(); - } +String generateWalletPassword() { + return generateKey(); } diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index 636547308..9c37657f2 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/di.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; @@ -31,6 +32,8 @@ class WalletCreationService { final Box walletInfoSource; WalletService _service; + static const _isNewMoneroWalletPasswordUpdated = true; + void changeWalletType({@required WalletType type}) { this.type = type; _service = getIt.get(param1: type); @@ -51,28 +54,55 @@ class WalletCreationService { Future create(WalletCredentials credentials) async { checkIfExists(credentials.name); - final password = generateWalletPassword(type); + final password = generateWalletPassword(); credentials.password = password; await keyService.saveWalletPassword( password: password, walletName: credentials.name); - return await _service.create(credentials); + final wallet = await _service.create(credentials); + + if (wallet.type == WalletType.monero) { + await sharedPreferences + .setBool( + PreferencesKey.moneroWalletUpdateV1Key(wallet.name), + _isNewMoneroWalletPasswordUpdated); + } + + return wallet; } Future restoreFromKeys(WalletCredentials credentials) async { checkIfExists(credentials.name); - final password = generateWalletPassword(type); + final password = generateWalletPassword(); credentials.password = password; await keyService.saveWalletPassword( password: password, walletName: credentials.name); - return await _service.restoreFromKeys(credentials); + final wallet = await _service.restoreFromKeys(credentials); + + if (wallet.type == WalletType.monero) { + await sharedPreferences + .setBool( + PreferencesKey.moneroWalletUpdateV1Key(wallet.name), + _isNewMoneroWalletPasswordUpdated); + } + + return wallet; } Future restoreFromSeed(WalletCredentials credentials) async { checkIfExists(credentials.name); - final password = generateWalletPassword(type); + final password = generateWalletPassword(); credentials.password = password; await keyService.saveWalletPassword( password: password, walletName: credentials.name); - return await _service.restoreFromSeed(credentials); + final wallet = await _service.restoreFromSeed(credentials); + + if (wallet.type == WalletType.monero) { + await sharedPreferences + .setBool( + PreferencesKey.moneroWalletUpdateV1Key(wallet.name), + _isNewMoneroWalletPasswordUpdated); + } + + return wallet; } } diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart new file mode 100644 index 000000000..844e5c1ca --- /dev/null +++ b/lib/core/wallet_loading_service.dart @@ -0,0 +1,52 @@ +import 'package:cake_wallet/core/generate_wallet_password.dart'; +import 'package:cake_wallet/core/key_service.dart'; +import 'package:cake_wallet/entities/preferences_key.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class WalletLoadingService { + WalletLoadingService( + this.sharedPreferences, + this.keyService, + this.walletServiceFactory); + + final SharedPreferences sharedPreferences; + final KeyService keyService; + final WalletService Function(WalletType type) walletServiceFactory; + + Future load(WalletType type, String name) async { + if (walletServiceFactory == null) { + throw Exception('WalletLoadingService.walletServiceFactory is not set'); + } + final walletService = walletServiceFactory?.call(type); + final password = await keyService.getWalletPassword(walletName: name); + final wallet = await walletService.openWallet(name, password); + + if (type == WalletType.monero) { + await upateMoneroWalletPassword(wallet); + } + + return wallet; + } + + Future upateMoneroWalletPassword(WalletBase wallet) async { + final key = PreferencesKey.moneroWalletUpdateV1Key(wallet.name); + var isPasswordUpdated = sharedPreferences.getBool(key) ?? false; + + if (isPasswordUpdated) { + return; + } + + final password = generateWalletPassword(); + // Save new generated password with backup key for case + // if wallet will change password, but it will faild to updated in secure storage + final bakWalletName = '#__${wallet.name}_bak__#'; + await keyService.saveWalletPassword(walletName: bakWalletName, password: password); + await wallet.changePassword(password); + await keyService.saveWalletPassword(walletName: wallet.name, password: password); + isPasswordUpdated = true; + await sharedPreferences.setBool(key, isPasswordUpdated); + } +} \ No newline at end of file diff --git a/lib/di.dart b/lib/di.dart index 29fed4c56..2270f1863 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -124,6 +124,7 @@ import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart'; +import 'package:cake_wallet/core/wallet_loading_service.dart'; final getIt = GetIt.instance; @@ -218,6 +219,12 @@ Future setup( sharedPreferences: getIt.get(), walletInfoSource: _walletInfoSource)); + getIt.registerFactory( + () => WalletLoadingService( + getIt.get(), + getIt.get(), + (WalletType type) => getIt.get(param1: type))); + getIt.registerFactoryParam((type, _) => WalletNewVM(getIt.get(), getIt.get(param1: type), _walletInfoSource, @@ -352,7 +359,7 @@ Future setup( getIt.registerFactory(() => WalletListViewModel( _walletInfoSource, getIt.get(), - getIt.get())); + getIt.get())); getIt.registerFactory(() => WalletListPage(walletListViewModel: getIt.get())); diff --git a/lib/entities/load_current_wallet.dart b/lib/entities/load_current_wallet.dart index 6751b7709..56502cc59 100644 --- a/lib/entities/load_current_wallet.dart +++ b/lib/entities/load_current_wallet.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/core/key_service.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:cake_wallet/core/wallet_loading_service.dart'; Future loadCurrentWallet() async { final appStore = getIt.get(); @@ -15,9 +16,7 @@ Future loadCurrentWallet() async { getIt.get().getInt(PreferencesKey.currentWalletType) ?? 0; final type = deserializeFromInt(typeRaw); - final password = - await getIt.get().getWalletPassword(walletName: name); - final _service = getIt.get(param1: type); - final wallet = await _service.openWallet(name, password); + final walletLoadingService = getIt.get(); + final wallet = await walletLoadingService.load(type, name); appStore.changeCurrentWallet(wallet); } diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 82e100c42..f4a0008a2 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -22,4 +22,8 @@ class PreferencesKey { static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin'; static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowYatPopup = 'should_show_yat_popup'; + static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; + + static String moneroWalletUpdateV1Key(String name) + => '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}'; } diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 67a80db8f..1afe3347d 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -1,9 +1,9 @@ +import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cake_wallet/view_model/wallet_new_vm.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/core/key_service.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cw_core/wallet_info.dart'; @@ -16,7 +16,7 @@ class WalletListViewModel = WalletListViewModelBase with _$WalletListViewModel; abstract class WalletListViewModelBase with Store { WalletListViewModelBase(this._walletInfoSource, this._appStore, - this._keyService) { + this._walletLoadingService) { wallets = ObservableList(); _updateList(); } @@ -26,17 +26,14 @@ abstract class WalletListViewModelBase with Store { final AppStore _appStore; final Box _walletInfoSource; - final KeyService _keyService; + final WalletLoadingService _walletLoadingService; WalletType get currentWalletType => _appStore.wallet.type; @action - Future loadWallet(WalletListItem wallet) async { - final password = - await _keyService.getWalletPassword(walletName: wallet.name); - final walletService = getIt.get(param1: wallet.type); - final loadedWallet = await walletService.openWallet(wallet.name, password); - _appStore.changeCurrentWallet(loadedWallet); + Future loadWallet(WalletListItem walletItem) async { + final wallet = await _walletLoadingService.load(walletItem.type, walletItem.name); + _appStore.changeCurrentWallet(wallet); _updateList(); } diff --git a/lib/view_model/wallet_restoration_from_keys_vm.dart b/lib/view_model/wallet_restoration_from_keys_vm.dart index a51d8011c..ae51bc833 100644 --- a/lib/view_model/wallet_restoration_from_keys_vm.dart +++ b/lib/view_model/wallet_restoration_from_keys_vm.dart @@ -45,7 +45,7 @@ abstract class WalletRestorationFromKeysVMBase extends WalletCreationVM @override WalletCredentials getCredentials(dynamic options) { - final password = generateWalletPassword(type); + final password = generateWalletPassword(); switch (type) { case WalletType.monero: diff --git a/lib/view_model/wallet_restoration_from_seed_vm.dart b/lib/view_model/wallet_restoration_from_seed_vm.dart index ffd4184e0..a4fb3c34a 100644 --- a/lib/view_model/wallet_restoration_from_seed_vm.dart +++ b/lib/view_model/wallet_restoration_from_seed_vm.dart @@ -36,7 +36,7 @@ abstract class WalletRestorationFromSeedVMBase extends WalletCreationVM @override WalletCredentials getCredentials(dynamic options) { - final password = generateWalletPassword(type); + final password = generateWalletPassword(); switch (type) { case WalletType.monero: diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 83dcc57ce..ce6546655 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -53,7 +53,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { @override WalletCredentials getCredentials(dynamic options) { - final password = generateWalletPassword(type); + final password = generateWalletPassword(); final height = options['height'] as int; name = options['name'] as String; diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 689b866eb..9245c9c1f 100755 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.0.6" -MONERO_COM_BUILD_NUMBER=13 +MONERO_COM_VERSION="1.0.7" +MONERO_COM_BUILD_NUMBER=14 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.4.1" -CAKEWALLET_BUILD_NUMBER=104 +CAKEWALLET_VERSION="4.4.3" +CAKEWALLET_BUILD_NUMBER=105 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index ca7e5de70..de6d2adf8 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.0.6" -MONERO_COM_BUILD_NUMBER=16 +MONERO_COM_VERSION="1.0.7" +MONERO_COM_BUILD_NUMBER=17 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.4.1" -CAKEWALLET_BUILD_NUMBER=100 +CAKEWALLET_VERSION="4.4.3" +CAKEWALLET_BUILD_NUMBER=104 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven"