From d83054eded301e6e8478bbe1eb5f98aa9ca03a0f Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Tue, 23 Jan 2024 01:53:20 +0200 Subject: [PATCH 1/8] Enable DFX for polygon wallet (#1267) --- lib/buy/dfx/dfx_buy_provider.dart | 5 +++++ lib/entities/provider_types.dart | 14 ++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index 78a5277ce..8e2d58d11 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -46,6 +46,8 @@ class DFXBuyProvider extends BuyProvider { return 'XMR'; case WalletType.ethereum: return 'ETH'; + case WalletType.polygon: + return 'MATIC'; default: throw Exception("WalletType is not available for DFX ${wallet.type}"); } @@ -61,6 +63,8 @@ class DFXBuyProvider extends BuyProvider { return 'Monero'; case WalletType.ethereum: return 'Ethereum'; + case WalletType.polygon: + return 'Polygon'; default: throw Exception("WalletType is not available for DFX ${wallet.type}"); } @@ -141,6 +145,7 @@ class DFXBuyProvider extends BuyProvider { String getSignature(String message) { switch (wallet.type) { case WalletType.ethereum: + case WalletType.polygon: return wallet.signMessage(message); case WalletType.monero: case WalletType.litecoin: diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart index a3c203960..ed688590c 100644 --- a/lib/entities/provider_types.dart +++ b/lib/entities/provider_types.dart @@ -65,9 +65,10 @@ class ProvidersHelper { case WalletType.litecoin: case WalletType.bitcoinCash: return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood]; + case WalletType.polygon: + return [ProviderType.askEachTime, ProviderType.dfx]; case WalletType.none: case WalletType.haven: - case WalletType.polygon: return []; } } @@ -76,17 +77,22 @@ class ProvidersHelper { switch (walletType) { case WalletType.bitcoin: case WalletType.ethereum: - return [ProviderType.askEachTime, ProviderType.onramper, - ProviderType.moonpaySell, ProviderType.dfx]; + return [ + ProviderType.askEachTime, + ProviderType.onramper, + ProviderType.moonpaySell, + ProviderType.dfx, + ]; case WalletType.litecoin: case WalletType.bitcoinCash: return [ProviderType.askEachTime, ProviderType.moonpaySell]; + case WalletType.polygon: + return [ProviderType.askEachTime, ProviderType.dfx]; case WalletType.monero: case WalletType.nano: case WalletType.banano: case WalletType.none: case WalletType.haven: - case WalletType.polygon: return []; } } From efb41aa6bc71c328eda735a3139415466057c80b Mon Sep 17 00:00:00 2001 From: Adegoke David <64401859+Blazebrain@users.noreply.github.com> Date: Tue, 23 Jan 2024 00:58:09 +0100 Subject: [PATCH 2/8] feat: Remove ability to create haven wallets (#1273) --- .../new_wallet/new_wallet_type_page.dart | 18 +++++++++++++++++- res/values/strings_ar.arb | 7 ++++--- res/values/strings_bg.arb | 5 +++-- res/values/strings_cs.arb | 5 +++-- 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_ha.arb | 5 +++-- res/values/strings_hi.arb | 5 +++-- res/values/strings_hr.arb | 5 +++-- res/values/strings_id.arb | 5 +++-- res/values/strings_it.arb | 5 +++-- res/values/strings_ja.arb | 5 +++-- res/values/strings_ko.arb | 5 +++-- res/values/strings_my.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_th.arb | 5 +++-- res/values/strings_tl.arb | 5 +++-- res/values/strings_tr.arb | 5 +++-- res/values/strings_uk.arb | 5 +++-- res/values/strings_ur.arb | 5 +++-- res/values/strings_yo.arb | 5 +++-- res/values/strings_zh.arb | 5 +++-- 27 files changed, 96 insertions(+), 54 deletions(-) diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index 225e5b82d..000310060 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -1,12 +1,14 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; +import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/src/widgets/search_bar_widget.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; @@ -70,7 +72,8 @@ class WalletTypeFormState extends State { Widget build(BuildContext context) { return Center( child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + constraints: + BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: Column( children: [ Padding( @@ -128,6 +131,19 @@ class WalletTypeFormState extends State { throw Exception('Wallet Type is not selected yet.'); } + if (selected == WalletType.haven) { + return await showPopUp( + context: context, + builder: (BuildContext context) { + return PopUpCancellableAlertDialog( + contentText: S.of(context).pause_wallet_creation, + actionButtonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } + widget.onTypeSelected(context, selected!); } } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 18035bce5..8ad9ed45b 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -756,11 +756,12 @@ "dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ", "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ", "wallet_seed_legacy": "بذرة محفظة قديمة", - "default_sell_provider": " ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ", + "default_sell_provider": "ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ", "select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ", "custom_drag": "مخصص (عقد وسحب)", "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ", "receivable_balance": "التوازن القادم", "confirmed_tx": "مؤكد", - "transaction_details_source_address": "عنوان المصدر" -} \ No newline at end of file + "transaction_details_source_address": "عنوان المصدر", + "pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ" +} diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index e2c88a45d..6f1c96af5 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -758,5 +758,6 @@ "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)", "receivable_balance": "Баланс за вземания", "confirmed_tx": "Потвърдено", - "transaction_details_source_address": "Адрес на източника" -} \ No newline at end of file + "transaction_details_source_address": "Адрес на източника", + "pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза." +} diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 7d2e8a86f..bb63ea00a 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -758,5 +758,6 @@ "switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)", "receivable_balance": "Zůstatek pohledávek", "confirmed_tx": "Potvrzeno", - "transaction_details_source_address": "Zdrojová adresa" -} \ No newline at end of file + "transaction_details_source_address": "Zdrojová adresa", + "pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena." +} diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index b151cbf6a..a0795960a 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -766,5 +766,6 @@ "switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)", "receivable_balance": "Forderungsbilanz", "confirmed_tx": "Bestätigt", - "transaction_details_source_address": "Quelladresse" -} \ No newline at end of file + "transaction_details_source_address": "Quelladresse", + "pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert." +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index c70d1f7c2..20ec41126 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -767,5 +767,6 @@ "switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)", "receivable_balance": "Receivable Balance", "confirmed_tx": "Confirmed", - "transaction_details_source_address": "Source address" -} \ No newline at end of file + "transaction_details_source_address": "Source address", + "pause_wallet_creation": "Ability to create Haven Wallet is currently paused." +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 9655edf60..e3783afe0 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -766,5 +766,6 @@ "switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)", "receivable_balance": "Saldo de cuentas por cobrar", "confirmed_tx": "Confirmado", - "transaction_details_source_address": "Dirección de la fuente" -} \ No newline at end of file + "transaction_details_source_address": "Dirección de la fuente", + "pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada." +} diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index ea5862e80..5ae616bda 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -766,5 +766,6 @@ "switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)", "receivable_balance": "Solde de créances", "confirmed_tx": "Confirmé", - "transaction_details_source_address": "Adresse source" -} \ No newline at end of file + "transaction_details_source_address": "Adresse source", + "pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue." +} diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 8b1b58c74..cdbfeb1b6 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -748,5 +748,6 @@ "switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)", "receivable_balance": "Daidaituwa da daidaituwa", "confirmed_tx": "Tabbatar", - "transaction_details_source_address": "Adireshin Incord" -} \ No newline at end of file + "transaction_details_source_address": "Adireshin Incord", + "pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet." +} diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index a4070cb12..22519a1c4 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -766,5 +766,6 @@ "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)", "receivable_balance": "प्राप्य शेष", "confirmed_tx": "की पुष्टि", - "transaction_details_source_address": "स्रोत पता" -} \ No newline at end of file + "transaction_details_source_address": "स्रोत पता", + "pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।" +} diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 94383e1d2..c951baed4 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -764,5 +764,6 @@ "switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)", "receivable_balance": "Stanje potraživanja", "confirmed_tx": "Potvrđen", - "transaction_details_source_address": "Adresa izvora" -} \ No newline at end of file + "transaction_details_source_address": "Adresa izvora", + "pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana." +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 86997c8aa..d383da478 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -754,5 +754,6 @@ "switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)", "receivable_balance": "Saldo piutang", "confirmed_tx": "Dikonfirmasi", - "transaction_details_source_address": "Alamat sumber" -} \ No newline at end of file + "transaction_details_source_address": "Alamat sumber", + "pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda." +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 257139d54..29edecaad 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -766,5 +766,6 @@ "switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)", "receivable_balance": "Bilanciamento creditizio", "confirmed_tx": "Confermato", - "transaction_details_source_address": "Indirizzo di partenza" -} \ No newline at end of file + "transaction_details_source_address": "Indirizzo di partenza", + "pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa." +} diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 3c648f1ad..d15c79c34 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -766,5 +766,6 @@ "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)", "receivable_balance": "売掛金残高", "confirmed_tx": "確認済み", - "transaction_details_source_address": "ソースアドレス" -} \ No newline at end of file + "transaction_details_source_address": "ソースアドレス", + "pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。" +} diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 1903ff1db..2a53ecf77 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -764,5 +764,6 @@ "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)", "receivable_balance": "채권 잔액", "confirmed_tx": "확인", - "transaction_details_source_address": "소스 주소" -} \ No newline at end of file + "transaction_details_source_address": "소스 주소", + "pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다." +} diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 8933afeff..04986ad5c 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -764,5 +764,6 @@ "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)", "receivable_balance": "လက်ကျန်ငွေ", "confirmed_tx": "အတည်ပြုသည်", - "transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ" -} \ No newline at end of file + "transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ", + "pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။" +} diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index b05e29efd..6c2ed2691 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -766,5 +766,6 @@ "switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)", "receivable_balance": "Het saldo", "confirmed_tx": "Bevestigd", - "transaction_details_source_address": "Bron adres" -} \ No newline at end of file + "transaction_details_source_address": "Bron adres", + "pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken." +} diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index a4f63255a..5e37e982a 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -766,5 +766,6 @@ "switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)", "receivable_balance": "Saldo należności", "confirmed_tx": "Potwierdzony", - "transaction_details_source_address": "Adres źródłowy" -} \ No newline at end of file + "transaction_details_source_address": "Adres źródłowy", + "pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana." +} diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index f30ea9635..66f0dd928 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -765,5 +765,6 @@ "switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)", "receivable_balance": "Saldo a receber", "confirmed_tx": "Confirmado", - "transaction_details_source_address": "Endereço de Origem" -} \ No newline at end of file + "transaction_details_source_address": "Endereço de Origem", + "pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada." +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 47c82263b..60c7bbc15 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -766,5 +766,6 @@ "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon).", "receivable_balance": "Баланс дебиторской задолженности", "confirmed_tx": "Подтвержденный", - "transaction_details_source_address": "Адрес источника" -} \ No newline at end of file + "transaction_details_source_address": "Адрес источника", + "pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена." +} diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index a4abe9b14..b28964357 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -764,5 +764,6 @@ "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)", "receivable_balance": "ยอดลูกหนี้", "confirmed_tx": "ซึ่งยืนยันแล้ว", - "transaction_details_source_address": "ที่อยู่แหล่งกำเนิด" -} \ No newline at end of file + "transaction_details_source_address": "ที่อยู่แหล่งกำเนิด", + "pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว" +} diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index e2c29c742..d30a25ad5 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -760,5 +760,6 @@ "switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)", "receivable_balance": "Natatanggap na balanse", "confirmed_tx": "Nakumpirma", - "transaction_details_source_address": "SOURCE ADDRESS" -} \ No newline at end of file + "transaction_details_source_address": "SOURCE ADDRESS", + "pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet." +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 91d130d60..563ca5332 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -764,5 +764,6 @@ "switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)", "receivable_balance": "Alacak bakiyesi", "confirmed_tx": "Onaylanmış", - "transaction_details_source_address": "Kaynak adresi" -} \ No newline at end of file + "transaction_details_source_address": "Kaynak adresi", + "pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı." +} diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 5cb5a6754..9418d6065 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -766,5 +766,6 @@ "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)", "receivable_balance": "Баланс дебіторської заборгованості", "confirmed_tx": "Підтверджений", - "transaction_details_source_address": "Адреса джерела" -} \ No newline at end of file + "transaction_details_source_address": "Адреса джерела", + "pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено." +} diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index d367d11cb..61e97d751 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -758,5 +758,6 @@ "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ ", "receivable_balance": "قابل وصول توازن", "confirmed_tx": "تصدیق", - "transaction_details_source_address": "ماخذ ایڈریس" -} \ No newline at end of file + "transaction_details_source_address": "ماخذ ایڈریس", + "pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ" +} diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 332297b4b..dbff620a1 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -760,5 +760,6 @@ "switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)", "receivable_balance": "Iwontunws.funfun ti o gba", "confirmed_tx": "Jẹrisi", - "transaction_details_source_address": "Adirẹsi orisun" -} \ No newline at end of file + "transaction_details_source_address": "Adirẹsi orisun", + "pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ." +} diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 76afa40d1..249a412c4 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -765,5 +765,6 @@ "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试(以太坊、Polygon)", "receivable_balance": "应收余额", "confirmed_tx": "确认的", - "transaction_details_source_address": "源地址" -} \ No newline at end of file + "transaction_details_source_address": "源地址", + "pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。" +} From d664f0a16fd030ed8dae530df66cf6356d6d3aa7 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Mon, 22 Jan 2024 16:30:16 -0800 Subject: [PATCH 3/8] Cw 540 encrypt shared (#1269) * start writing migration * save * finishing up migration * more fixes * fixes * migration fixes * remove commented code and ensure widget initialization in root.dart * use securekey static functions everywhere applicable --- lib/core/auth_service.dart | 8 +- lib/core/backup_service.dart | 85 +--- lib/di.dart | 24 +- lib/entities/default_settings_migration.dart | 79 ++++ lib/entities/preferences_key.dart | 26 +- lib/entities/secret_store_key.dart | 65 +++ lib/main.dart | 2 +- lib/src/screens/root/root.dart | 14 +- lib/store/settings_store.dart | 427 ++++++++++++------ .../security_settings_view_model.dart | 2 +- .../wallet_list/wallet_list_view_model.dart | 2 +- 11 files changed, 452 insertions(+), 282 deletions(-) diff --git a/lib/core/auth_service.dart b/lib/core/auth_service.dart index c072bf65e..a99aef31d 100644 --- a/lib/core/auth_service.dart +++ b/lib/core/auth_service.dart @@ -72,11 +72,11 @@ class AuthService with Store { void saveLastAuthTime() { int timestamp = DateTime.now().millisecondsSinceEpoch; - sharedPreferences.setInt(PreferencesKey.lastAuthTimeMilliseconds, timestamp); + secureStorage.write(key: SecureKey.lastAuthTimeMilliseconds, value: timestamp.toString()); } - bool requireAuth() { - final timestamp = sharedPreferences.getInt(PreferencesKey.lastAuthTimeMilliseconds); + Future requireAuth() async { + final timestamp = int.tryParse(await secureStorage.read(key: SecureKey.lastAuthTimeMilliseconds) ?? '0'); final duration = _durationToRequireAuth(timestamp ?? 0); final requiredPinInterval = settingsStore.pinTimeOutDuration; @@ -100,7 +100,7 @@ class AuthService with Store { 'Either route or onAuthSuccess param must be passed.'); if (!conditionToDetermineIfToUse2FA) { - if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) { + if (!(await requireAuth()) && !_alwaysAuthenticateRoutes.contains(route)) { if (onAuthSuccess != null) { onAuthSuccess(true); } else { diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 7dd0b50f3..9b5c4c8db 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -213,8 +213,6 @@ class BackupService { final defaultBuyProvider = data[PreferencesKey.defaultBuyProvider] as int?; final currentTransactionPriorityKeyLegacy = data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?; - final allowBiometricalAuthentication = - data[PreferencesKey.allowBiometricalAuthenticationKey] as bool?; final currentBitcoinElectrumSererId = data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int?; final currentLanguageCode = data[PreferencesKey.currentLanguageCode] as String?; @@ -227,23 +225,6 @@ class BackupService { data[PreferencesKey.currentDefaultSettingsMigrationVersion] as int?; final moneroTransactionPriority = data[PreferencesKey.moneroTransactionPriority] as int?; final bitcoinTransactionPriority = data[PreferencesKey.bitcoinTransactionPriority] as int?; - final selectedCake2FAPreset = data[PreferencesKey.selectedCake2FAPreset] as int?; - final shouldRequireTOTP2FAForAccessingWallet = - data[PreferencesKey.shouldRequireTOTP2FAForAccessingWallet] as bool?; - final shouldRequireTOTP2FAForSendsToContact = - data[PreferencesKey.shouldRequireTOTP2FAForSendsToContact] as bool?; - final shouldRequireTOTP2FAForSendsToNonContact = - data[PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact] as bool?; - final shouldRequireTOTP2FAForSendsToInternalWallets = - data[PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets] as bool?; - final shouldRequireTOTP2FAForExchangesToInternalWallets = - data[PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets] as bool?; - final shouldRequireTOTP2FAForAddingContacts = - data[PreferencesKey.shouldRequireTOTP2FAForAddingContacts] as bool?; - final shouldRequireTOTP2FAForCreatingNewWallets = - data[PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets] as bool?; - final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = - data[PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings] as bool?; final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?; final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?; final useEtherscan = data[PreferencesKey.useEtherscan] as bool?; @@ -294,14 +275,7 @@ class BackupService { if (currentTransactionPriorityKeyLegacy != null) await _sharedPreferences.setInt( PreferencesKey.currentTransactionPriorityKeyLegacy, currentTransactionPriorityKeyLegacy); - - if (DeviceInfo.instance.isDesktop) { - await _sharedPreferences.setBool(PreferencesKey.allowBiometricalAuthenticationKey, false); - } else if (allowBiometricalAuthentication != null) { - await _sharedPreferences.setBool( - PreferencesKey.allowBiometricalAuthenticationKey, allowBiometricalAuthentication); - } - + if (currentBitcoinElectrumSererId != null) await _sharedPreferences.setInt( PreferencesKey.currentBitcoinElectrumSererIdKey, currentBitcoinElectrumSererId); @@ -344,43 +318,6 @@ class BackupService { await _sharedPreferences.setInt( PreferencesKey.bitcoinTransactionPriority, bitcoinTransactionPriority); - if (selectedCake2FAPreset != null) - await _sharedPreferences.setInt(PreferencesKey.selectedCake2FAPreset, selectedCake2FAPreset); - - if (shouldRequireTOTP2FAForAccessingWallet != null) - await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet, - shouldRequireTOTP2FAForAccessingWallet); - - if (shouldRequireTOTP2FAForSendsToContact != null) - await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForSendsToContact, - shouldRequireTOTP2FAForSendsToContact); - - if (shouldRequireTOTP2FAForSendsToNonContact != null) - await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact, - shouldRequireTOTP2FAForSendsToNonContact); - - if (shouldRequireTOTP2FAForSendsToInternalWallets != null) - await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets, - shouldRequireTOTP2FAForSendsToInternalWallets); - - if (shouldRequireTOTP2FAForExchangesToInternalWallets != null) - await _sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets, - shouldRequireTOTP2FAForExchangesToInternalWallets); - - if (shouldRequireTOTP2FAForAddingContacts != null) - await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts, - shouldRequireTOTP2FAForAddingContacts); - - if (shouldRequireTOTP2FAForCreatingNewWallets != null) - await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets, - shouldRequireTOTP2FAForCreatingNewWallets); - - if (shouldRequireTOTP2FAForAllSecurityAndBackupSettings != null) - await _sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, - shouldRequireTOTP2FAForAllSecurityAndBackupSettings); - if (sortBalanceTokensBy != null) await _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy); @@ -532,8 +469,6 @@ class BackupService { PreferencesKey.currentPinLength: _sharedPreferences.getInt(PreferencesKey.currentPinLength), PreferencesKey.currentTransactionPriorityKeyLegacy: _sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy), - PreferencesKey.allowBiometricalAuthenticationKey: - _sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey), PreferencesKey.currentBitcoinElectrumSererIdKey: _sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey), PreferencesKey.currentLanguageCode: @@ -550,24 +485,6 @@ class BackupService { _sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority), PreferencesKey.currentFiatApiModeKey: _sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey), - PreferencesKey.selectedCake2FAPreset: - _sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset), - PreferencesKey.shouldRequireTOTP2FAForAccessingWallet: - _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet), - PreferencesKey.shouldRequireTOTP2FAForSendsToContact: - _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToContact), - PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact: - _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact), - PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets: - _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets), - PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets: _sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets), - PreferencesKey.shouldRequireTOTP2FAForAddingContacts: - _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts), - PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets: - _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets), - PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings: _sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings), PreferencesKey.sortBalanceBy: _sharedPreferences.getInt(PreferencesKey.sortBalanceBy), PreferencesKey.pinNativeTokenAtTop: _sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop), diff --git a/lib/di.dart b/lib/di.dart index 61a04bf1c..a37c76b34 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -276,6 +276,7 @@ Future setup({ if (!_isSetupFinished) { getIt.registerSingletonAsync(() => SharedPreferences.getInstance()); + getIt.registerSingleton(secureStorage); } if (!_isSetupFinished) { getIt.registerFactory(() => BackgroundTasks()); @@ -302,7 +303,6 @@ Future setup({ getIt.registerFactory>(() => _nodeSource); getIt.registerFactory>(() => _powNodeSource, instanceName: Node.boxName + "pow"); - getIt.registerSingleton(secureStorage); getIt.registerSingleton(AuthenticationStore()); getIt.registerSingleton(WalletListStore()); getIt.registerSingleton(NodeListStoreBase.instance); @@ -344,17 +344,19 @@ Future setup({ walletInfoSource: _walletInfoSource)); getIt.registerFactoryParam( - (type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get())); + (type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get())); getIt.registerFactory(() => WalletLoadingService( getIt.get(), getIt.get(), (WalletType type) => getIt.get(param1: type))); - - getIt.registerFactoryParam((type, _) => - WalletNewVM(getIt.get(), - getIt.get(param1: type), _walletInfoSource, - getIt.get(param1: type),type: type)); + + getIt.registerFactoryParam((type, _) => WalletNewVM( + getIt.get(), + getIt.get(param1: type), + _walletInfoSource, + getIt.get(param1: type), + type: type)); getIt.registerFactoryParam((WalletType type, _) { return WalletRestorationFromQRVM(getIt.get(), @@ -805,8 +807,7 @@ Future setup({ .registerFactory(() => DFXBuyProvider(wallet: getIt.get().wallet!)); getIt.registerFactory(() => MoonPaySellProvider( - settingsStore: getIt.get().settingsStore, - wallet: getIt.get().wallet!)); + settingsStore: getIt.get().settingsStore, wallet: getIt.get().wallet!)); getIt.registerFactory(() => OnRamperBuyProvider( getIt.get().settingsStore, @@ -919,8 +920,7 @@ Future setup({ (param1, isCreate) => NewWalletTypePage(onTypeSelected: param1, isCreate: isCreate ?? true)); getIt.registerFactoryParam( - (seedPhraseLength, _) - => PreSeedPage(seedPhraseLength)); + (seedPhraseLength, _) => PreSeedPage(seedPhraseLength)); getIt.registerFactoryParam((trade, _) => TradeDetailsViewModel( @@ -954,7 +954,7 @@ Future setup({ getIt.registerFactory(() => BuyAmountViewModel()); getIt.registerFactoryParam( - (isBuyOption, _) => BuySellOptionsPage(getIt.get(), isBuyOption)); + (isBuyOption, _) => BuySellOptionsPage(getIt.get(), isBuyOption)); getIt.registerFactory(() { final wallet = getIt.get().wallet; diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 22868ae28..1279beeca 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -185,6 +185,9 @@ Future defaultSettingsMigration( case 25: await rewriteSecureStoragePin(secureStorage: secureStorage); break; + case 26: + await insecureStorageMigration(secureStorage: secureStorage, sharedPreferences: sharedPreferences); + break; default: break; } @@ -378,6 +381,82 @@ Node getMoneroDefaultNode({required Box nodes}) { } } +Future insecureStorageMigration({ + required SharedPreferences sharedPreferences, + required FlutterSecureStorage secureStorage, +}) async { + bool? allowBiometricalAuthentication = + sharedPreferences.getBool(SecureKey.allowBiometricalAuthenticationKey); + bool? useTOTP2FA = sharedPreferences.getBool(SecureKey.useTOTP2FA); + bool? shouldRequireTOTP2FAForAccessingWallet = + sharedPreferences.getBool(SecureKey.shouldRequireTOTP2FAForAccessingWallet); + bool? shouldRequireTOTP2FAForSendsToContact = + sharedPreferences.getBool(SecureKey.shouldRequireTOTP2FAForSendsToContact); + bool? shouldRequireTOTP2FAForSendsToNonContact = + sharedPreferences.getBool(SecureKey.shouldRequireTOTP2FAForSendsToNonContact); + bool? shouldRequireTOTP2FAForSendsToInternalWallets = + sharedPreferences.getBool(SecureKey.shouldRequireTOTP2FAForSendsToInternalWallets); + bool? shouldRequireTOTP2FAForExchangesToInternalWallets = + sharedPreferences.getBool(SecureKey.shouldRequireTOTP2FAForExchangesToInternalWallets); + bool? shouldRequireTOTP2FAForExchangesToExternalWallets = + sharedPreferences.getBool(SecureKey.shouldRequireTOTP2FAForExchangesToExternalWallets); + bool? shouldRequireTOTP2FAForAddingContacts = + sharedPreferences.getBool(SecureKey.shouldRequireTOTP2FAForAddingContacts); + bool? shouldRequireTOTP2FAForCreatingNewWallets = + sharedPreferences.getBool(SecureKey.shouldRequireTOTP2FAForCreatingNewWallets); + bool? shouldRequireTOTP2FAForAllSecurityAndBackupSettings = + sharedPreferences.getBool(SecureKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings); + int? selectedCake2FAPreset = sharedPreferences.getInt(SecureKey.selectedCake2FAPreset); + String? totpSecretKey = sharedPreferences.getString(SecureKey.totpSecretKey); + int? pinTimeOutDuration = sharedPreferences.getInt(SecureKey.pinTimeOutDuration); + int? lastAuthTimeMilliseconds = sharedPreferences.getInt(SecureKey.lastAuthTimeMilliseconds); + + try { + await secureStorage.write( + key: SecureKey.allowBiometricalAuthenticationKey, + value: allowBiometricalAuthentication.toString()); + await secureStorage.write(key: SecureKey.useTOTP2FA, value: useTOTP2FA.toString()); + await secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForAccessingWallet, + value: shouldRequireTOTP2FAForAccessingWallet.toString()); + await secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForSendsToContact, + value: shouldRequireTOTP2FAForSendsToContact.toString()); + await secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForSendsToNonContact, + value: shouldRequireTOTP2FAForSendsToNonContact.toString()); + await secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForSendsToInternalWallets, + value: shouldRequireTOTP2FAForSendsToInternalWallets.toString()); + await secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForExchangesToInternalWallets, + value: shouldRequireTOTP2FAForExchangesToInternalWallets.toString()); + await secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForExchangesToExternalWallets, + value: shouldRequireTOTP2FAForExchangesToExternalWallets.toString()); + await secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForAddingContacts, + value: shouldRequireTOTP2FAForAddingContacts.toString()); + await secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForCreatingNewWallets, + value: shouldRequireTOTP2FAForCreatingNewWallets.toString()); + await secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + value: shouldRequireTOTP2FAForAllSecurityAndBackupSettings.toString()); + await secureStorage.write( + key: SecureKey.selectedCake2FAPreset, value: selectedCake2FAPreset.toString()); + await secureStorage.write(key: SecureKey.totpSecretKey, value: totpSecretKey.toString()); + await secureStorage.write( + key: SecureKey.pinTimeOutDuration, value: pinTimeOutDuration.toString()); + await secureStorage.write( + key: SecureKey.lastAuthTimeMilliseconds, value: lastAuthTimeMilliseconds.toString()); + } catch (e) { + print("Error migrating shared preferences to secure storage!: $e"); + // this actually shouldn't be that big of a problem since we don't delete the old keys in this update + // and we read and write to the new locations when loading storage, the migration is just for extra safety + } +} + Future rewriteSecureStoragePin({required FlutterSecureStorage secureStorage}) async { // the bug only affects ios/mac: if (!Platform.isIOS && !Platform.isMacOS) { diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 2d5e64817..75e61b5e8 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -23,8 +23,6 @@ class PreferencesKey { static const walletListOrder = 'wallet_list_order'; static const walletListAscending = 'wallet_list_ascending'; static const currentFiatApiModeKey = 'current_fiat_api_mode'; - static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication'; - static const useTOTP2FA = 'use_totp_2fa'; static const failedTotpTokenTrials = 'failed_token_trials'; static const disableExchangeKey = 'disable_exchange'; static const exchangeStatusKey = 'exchange_status'; @@ -33,6 +31,7 @@ class PreferencesKey { static const displayActionListModeKey = 'display_list_mode'; static const currentPinLength = 'current_pin_length'; static const currentLanguageCode = 'language_code'; + static const currentSeedPhraseLength = 'current_seed_phrase_length'; static const currentDefaultSettingsMigrationVersion = 'current_default_settings_migration_version'; static const moneroTransactionPriority = 'current_fee_priority_monero'; @@ -47,8 +46,6 @@ class PreferencesKey { static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; static const syncModeKey = 'sync_mode'; static const syncAllKey = 'sync_all'; - static const pinTimeOutDuration = 'pin_timeout_duration'; - static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds'; static const lastPopupDate = 'last_popup_date'; static const lastAppReviewDate = 'last_app_review_date'; static const sortBalanceBy = 'sort_balance_by'; @@ -75,25 +72,4 @@ class PreferencesKey { static const lastSeenAppVersion = 'last_seen_app_version'; static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard'; static const isNewInstall = 'is_new_install'; - static const shouldRequireTOTP2FAForAccessingWallet = - 'should_require_totp_2fa_for_accessing_wallets'; - static const shouldRequireTOTP2FAForSendsToContact = - 'should_require_totp_2fa_for_sends_to_contact'; - static const shouldRequireTOTP2FAForSendsToNonContact = - 'should_require_totp_2fa_for_sends_to_non_contact'; - static const shouldRequireTOTP2FAForSendsToInternalWallets = - 'should_require_totp_2fa_for_sends_to_internal_wallets'; - static const shouldRequireTOTP2FAForExchangesToInternalWallets = - 'should_require_totp_2fa_for_exchanges_to_internal_wallets'; - static const shouldRequireTOTP2FAForExchangesToExternalWallets = - 'should_require_totp_2fa_for_exchanges_to_external_wallets'; - static const shouldRequireTOTP2FAForAddingContacts = - 'should_require_totp_2fa_for_adding_contacts'; - static const shouldRequireTOTP2FAForCreatingNewWallets = - 'should_require_totp_2fa_for_creating_new_wallets'; - static const shouldRequireTOTP2FAForAllSecurityAndBackupSettings = - 'should_require_totp_2fa_for_all_security_and_backup_settings'; - static const selectedCake2FAPreset = 'selected_cake_2fa_preset'; - static const totpSecretKey = 'totp_secret_key'; - static const currentSeedPhraseLength = 'current_seed_phrase_length'; } diff --git a/lib/entities/secret_store_key.dart b/lib/entities/secret_store_key.dart index 00ec06767..2ee490c74 100644 --- a/lib/entities/secret_store_key.dart +++ b/lib/entities/secret_store_key.dart @@ -1,3 +1,6 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + enum SecretStoreKey { moneroWalletPassword, pinCodePassword, backupPassword } const moneroWalletPassword = "MONERO_WALLET_PASSWORD"; @@ -35,3 +38,65 @@ String generateStoreKeyFor({ return _key; } + +class SecureKey { + static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication'; + static const useTOTP2FA = 'use_totp_2fa'; + static const shouldRequireTOTP2FAForAccessingWallet = + 'should_require_totp_2fa_for_accessing_wallets'; + static const shouldRequireTOTP2FAForSendsToContact = + 'should_require_totp_2fa_for_sends_to_contact'; + static const shouldRequireTOTP2FAForSendsToNonContact = + 'should_require_totp_2fa_for_sends_to_non_contact'; + static const shouldRequireTOTP2FAForSendsToInternalWallets = + 'should_require_totp_2fa_for_sends_to_internal_wallets'; + static const shouldRequireTOTP2FAForExchangesToInternalWallets = + 'should_require_totp_2fa_for_exchanges_to_internal_wallets'; + static const shouldRequireTOTP2FAForExchangesToExternalWallets = + 'should_require_totp_2fa_for_exchanges_to_external_wallets'; + static const shouldRequireTOTP2FAForAddingContacts = + 'should_require_totp_2fa_for_adding_contacts'; + static const shouldRequireTOTP2FAForCreatingNewWallets = + 'should_require_totp_2fa_for_creating_new_wallets'; + static const shouldRequireTOTP2FAForAllSecurityAndBackupSettings = + 'should_require_totp_2fa_for_all_security_and_backup_settings'; + static const selectedCake2FAPreset = 'selected_cake_2fa_preset'; + static const totpSecretKey = 'totp_secret_key'; + static const pinTimeOutDuration = 'pin_timeout_duration'; + static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds'; + + static Future getInt({ + required FlutterSecureStorage secureStorage, + required SharedPreferences sharedPreferences, + required String key, + }) async { + int? value = int.tryParse((await secureStorage.read(key: key) ?? '')); + value ??= sharedPreferences.getInt(key); + return value; + } + + static Future getBool({ + required FlutterSecureStorage secureStorage, + required SharedPreferences sharedPreferences, + required String key, + }) async { + String? value = (await secureStorage.read(key: key) ?? ''); + if (value.toLowerCase() == "true") { + return true; + } else if (value.toLowerCase() == "false") { + return false; + } else { + return sharedPreferences.getBool(key); + } + } + + static Future getString({ + required FlutterSecureStorage secureStorage, + required SharedPreferences sharedPreferences, + required String key, + }) async { + String? value = await secureStorage.read(key: key); + value ??= sharedPreferences.getString(key); + return value; + } +} diff --git a/lib/main.dart b/lib/main.dart index 165db1ddd..306b109a0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -163,7 +163,7 @@ Future initializeAppConfigs() async { transactionDescriptions: transactionDescriptions, secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, - initialMigrationVersion: 25); + initialMigrationVersion: 26); } Future initialSetup( diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index 5f7fcd205..b3d503162 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -53,13 +53,17 @@ class RootState extends State with WidgetsBindingObserver { @override void initState() { - _requestAuth = widget.authService.requireAuth(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + bool value = await widget.authService.requireAuth(); + setState(() { + _requestAuth = value; + }); + }); _isInactiveController = StreamController.broadcast(); _isInactive = false; _postFrameCallback = false; WidgetsBinding.instance.addObserver(this); super.initState(); - if (DeviceInfo.instance.isMobile) { initUniLinks(); } @@ -105,8 +109,10 @@ class RootState extends State with WidgetsBindingObserver { break; case AppLifecycleState.resumed: - setState(() { - _requestAuth = widget.authService.requireAuth(); + widget.authService.requireAuth().then((value) { + setState(() { + _requestAuth = value; + }); }); break; default: diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 7bccc4fc5..191eeb43f 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/entities/background_tasks.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; +import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/seed_phrase_length.dart'; import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; @@ -24,6 +25,7 @@ import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_list.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:package_info/package_info.dart'; @@ -44,7 +46,8 @@ class SettingsStore = SettingsStoreBase with _$SettingsStore; abstract class SettingsStoreBase with Store { SettingsStoreBase( - {required BackgroundTasks backgroundTasks, + {required FlutterSecureStorage secureStorage, + required BackgroundTasks backgroundTasks, required SharedPreferences sharedPreferences, required bool initialShouldShowMarketPlaceInDashboard, required FiatCurrency initialFiatCurrency, @@ -109,6 +112,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? initialBitcoinCashTransactionPriority}) : nodes = ObservableMap.of(nodes), powNodes = ObservableMap.of(powNodes), + _secureStorage = secureStorage, _sharedPreferences = sharedPreferences, _backgroundTasks = backgroundTasks, fiatCurrency = initialFiatCurrency, @@ -187,8 +191,9 @@ abstract class SettingsStoreBase with Store { final key = 'buyProvider_${walletType.toString()}'; final providerId = sharedPreferences.getString(key); if (providerId != null) { - defaultBuyProviders[walletType] = ProviderType.values - .firstWhere((provider) => provider.id == providerId, orElse: () => ProviderType.askEachTime); + defaultBuyProviders[walletType] = ProviderType.values.firstWhere( + (provider) => provider.id == providerId, + orElse: () => ProviderType.askEachTime); } else { defaultBuyProviders[walletType] = ProviderType.askEachTime; } @@ -198,8 +203,9 @@ abstract class SettingsStoreBase with Store { final key = 'sellProvider_${walletType.toString()}'; final providerId = sharedPreferences.getString(key); if (providerId != null) { - defaultSellProviders[walletType] = ProviderType.values - .firstWhere((provider) => provider.id == providerId, orElse: () => ProviderType.askEachTime); + defaultSellProviders[walletType] = ProviderType.values.firstWhere( + (provider) => provider.id == providerId, + orElse: () => ProviderType.askEachTime); } else { defaultSellProviders[walletType] = ProviderType.askEachTime; } @@ -312,74 +318,6 @@ abstract class SettingsStoreBase with Store { reaction((_) => currentTheme, (ThemeBase theme) => sharedPreferences.setInt(PreferencesKey.currentTheme, theme.raw)); - reaction( - (_) => allowBiometricalAuthentication, - (bool biometricalAuthentication) => sharedPreferences.setBool( - PreferencesKey.allowBiometricalAuthenticationKey, biometricalAuthentication)); - - reaction( - (_) => selectedCake2FAPreset, - (Cake2FAPresetsOptions selectedCake2FAPreset) => sharedPreferences.setInt( - PreferencesKey.selectedCake2FAPreset, selectedCake2FAPreset.serialize())); - - reaction( - (_) => shouldRequireTOTP2FAForAccessingWallet, - (bool requireTOTP2FAForAccessingWallet) => sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForAccessingWallet, - requireTOTP2FAForAccessingWallet)); - - reaction( - (_) => shouldRequireTOTP2FAForSendsToContact, - (bool requireTOTP2FAForSendsToContact) => sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForSendsToContact, requireTOTP2FAForSendsToContact)); - - reaction( - (_) => shouldRequireTOTP2FAForSendsToNonContact, - (bool requireTOTP2FAForSendsToNonContact) => sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact, - requireTOTP2FAForSendsToNonContact)); - - reaction( - (_) => shouldRequireTOTP2FAForSendsToInternalWallets, - (bool requireTOTP2FAForSendsToInternalWallets) => sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets, - requireTOTP2FAForSendsToInternalWallets)); - - reaction( - (_) => shouldRequireTOTP2FAForExchangesToInternalWallets, - (bool requireTOTP2FAForExchangesToInternalWallets) => sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets, - requireTOTP2FAForExchangesToInternalWallets)); - - reaction( - (_) => shouldRequireTOTP2FAForExchangesToExternalWallets, - (bool requireTOTP2FAForExchangesToExternalWallets) => sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForExchangesToExternalWallets, - requireTOTP2FAForExchangesToExternalWallets)); - - reaction( - (_) => shouldRequireTOTP2FAForAddingContacts, - (bool requireTOTP2FAForAddingContacts) => sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForAddingContacts, requireTOTP2FAForAddingContacts)); - - reaction( - (_) => shouldRequireTOTP2FAForCreatingNewWallets, - (bool requireTOTP2FAForCreatingNewWallets) => sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets, - requireTOTP2FAForCreatingNewWallets)); - - reaction( - (_) => shouldRequireTOTP2FAForAllSecurityAndBackupSettings, - (bool requireTOTP2FAForAllSecurityAndBackupSettings) => sharedPreferences.setBool( - PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, - requireTOTP2FAForAllSecurityAndBackupSettings)); - - reaction( - (_) => useTOTP2FA, (bool use) => sharedPreferences.setBool(PreferencesKey.useTOTP2FA, use)); - - reaction((_) => totpSecretKey, - (String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey)); - reaction( (_) => numberOfFailedTokenTrials, (int failedTokenTrail) => @@ -403,11 +341,6 @@ abstract class SettingsStoreBase with Store { (SeedPhraseLength seedPhraseWordCount) => sharedPreferences.setInt( PreferencesKey.currentSeedPhraseLength, seedPhraseWordCount.value)); - reaction( - (_) => pinTimeOutDuration, - (PinCodeRequiredDuration pinCodeInterval) => - sharedPreferences.setInt(PreferencesKey.pinTimeOutDuration, pinCodeInterval.value)); - reaction( (_) => balanceDisplayMode, (BalanceDisplayMode mode) => sharedPreferences.setInt( @@ -485,6 +418,84 @@ abstract class SettingsStoreBase with Store { reaction((_) => lookupsENS, (bool looksUpENS) => _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS)); + // secure storage keys: + reaction( + (_) => allowBiometricalAuthentication, + (bool biometricalAuthentication) => secureStorage.write( + key: SecureKey.allowBiometricalAuthenticationKey, + value: biometricalAuthentication.toString())); + + reaction( + (_) => selectedCake2FAPreset, + (Cake2FAPresetsOptions selectedCake2FAPreset) => secureStorage.write( + key: SecureKey.selectedCake2FAPreset, + value: selectedCake2FAPreset.serialize().toString())); + + reaction( + (_) => shouldRequireTOTP2FAForAccessingWallet, + (bool requireTOTP2FAForAccessingWallet) => secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForAccessingWallet, + value: requireTOTP2FAForAccessingWallet.toString())); + + reaction( + (_) => shouldRequireTOTP2FAForSendsToContact, + (bool requireTOTP2FAForSendsToContact) => secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForSendsToContact, + value: requireTOTP2FAForSendsToContact.toString())); + + reaction( + (_) => shouldRequireTOTP2FAForSendsToNonContact, + (bool requireTOTP2FAForSendsToNonContact) => secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForSendsToNonContact, + value: requireTOTP2FAForSendsToNonContact.toString())); + + reaction( + (_) => shouldRequireTOTP2FAForSendsToInternalWallets, + (bool requireTOTP2FAForSendsToInternalWallets) => secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForSendsToInternalWallets, + value: requireTOTP2FAForSendsToInternalWallets.toString())); + + reaction( + (_) => shouldRequireTOTP2FAForExchangesToInternalWallets, + (bool requireTOTP2FAForExchangesToInternalWallets) => secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForExchangesToInternalWallets, + value: requireTOTP2FAForExchangesToInternalWallets.toString())); + + reaction( + (_) => shouldRequireTOTP2FAForExchangesToExternalWallets, + (bool requireTOTP2FAForExchangesToExternalWallets) => secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForExchangesToExternalWallets, + value: requireTOTP2FAForExchangesToExternalWallets.toString())); + + reaction( + (_) => shouldRequireTOTP2FAForAddingContacts, + (bool requireTOTP2FAForAddingContacts) => secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForAddingContacts, + value: requireTOTP2FAForAddingContacts.toString())); + + reaction( + (_) => shouldRequireTOTP2FAForCreatingNewWallets, + (bool requireTOTP2FAForCreatingNewWallets) => secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForCreatingNewWallets, + value: requireTOTP2FAForCreatingNewWallets.toString())); + + reaction( + (_) => shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + (bool requireTOTP2FAForAllSecurityAndBackupSettings) => secureStorage.write( + key: SecureKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + value: requireTOTP2FAForAllSecurityAndBackupSettings.toString())); + + reaction((_) => useTOTP2FA, + (bool use) => secureStorage.write(key: SecureKey.useTOTP2FA, value: use.toString())); + + reaction((_) => totpSecretKey, + (String totpKey) => secureStorage.write(key: SecureKey.totpSecretKey, value: totpKey)); + + reaction( + (_) => pinTimeOutDuration, + (PinCodeRequiredDuration pinCodeInterval) => secureStorage.write( + key: SecureKey.pinTimeOutDuration, value: pinCodeInterval.value.toString())); + this.nodes.observe((change) { if (change.newValue != null && change.key != null) { _saveCurrentNode(change.newValue!, change.key!); @@ -668,6 +679,7 @@ abstract class SettingsStoreBase with Store { String deviceName; + final FlutterSecureStorage _secureStorage; final SharedPreferences _sharedPreferences; final BackgroundTasks _backgroundTasks; @@ -710,6 +722,7 @@ abstract class SettingsStoreBase with Store { BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance, ThemeBase? initialTheme}) async { final sharedPreferences = await getIt.getAsync(); + final secureStorage = await getIt.get(); final backgroundTasks = getIt.get(); final currentFiatCurrency = FiatCurrency.deserialize( raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!); @@ -770,36 +783,6 @@ abstract class SettingsStoreBase with Store { final currentFiatApiMode = FiatApiMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ?? FiatApiMode.enabled.raw); - final allowBiometricalAuthentication = - sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? false; - final selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize( - raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ?? - Cake2FAPresetsOptions.normal.raw); - final shouldRequireTOTP2FAForAccessingWallet = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet) ?? false; - final shouldRequireTOTP2FAForSendsToContact = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToContact) ?? false; - final shouldRequireTOTP2FAForSendsToNonContact = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact) ?? false; - final shouldRequireTOTP2FAForSendsToInternalWallets = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ?? - false; - final shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? - false; - final shouldRequireTOTP2FAForExchangesToExternalWallets = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToExternalWallets) ?? - false; - final shouldRequireTOTP2FAForAddingContacts = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false; - final shouldRequireTOTP2FAForCreatingNewWallets = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ?? - false; - final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? - false; - final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false; - final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? ''; final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0; final shouldShowMarketPlaceInDashboard = sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true; @@ -816,18 +799,14 @@ abstract class SettingsStoreBase with Store { actionListDisplayMode.addAll(deserializeActionlistDisplayModes( sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? defaultActionsMode)); var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength); - final timeOutDuration = sharedPreferences.getInt(PreferencesKey.pinTimeOutDuration); - final seedPhraseCount = sharedPreferences.getInt(PreferencesKey.currentSeedPhraseLength); - final pinCodeTimeOutDuration = timeOutDuration != null - ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration) - : defaultPinCodeTimeOutDuration; - final seedPhraseWordCount = seedPhraseCount != null - ? SeedPhraseLength.deserialize(raw: seedPhraseCount) - : defaultSeedPhraseLength; final sortBalanceBy = SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0]; final pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; + final seedPhraseCount = sharedPreferences.getInt(PreferencesKey.currentSeedPhraseLength); + final seedPhraseWordCount = seedPhraseCount != null + ? SeedPhraseLength.deserialize(raw: seedPhraseCount) + : defaultSeedPhraseLength; final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true; final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? ""; @@ -929,7 +908,101 @@ abstract class SettingsStoreBase with Store { }); final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true; + // migrated to secure: + final timeOutDuration = await SecureKey.getInt( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.pinTimeOutDuration, + ); + + final pinCodeTimeOutDuration = timeOutDuration != null + ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration) + : defaultPinCodeTimeOutDuration; + + final allowBiometricalAuthentication = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.pinTimeOutDuration, + ) ?? + false; + + final selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize( + raw: await SecureKey.getInt( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.selectedCake2FAPreset, + ) ?? + Cake2FAPresetsOptions.normal.raw); + + final shouldRequireTOTP2FAForAccessingWallet = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForAccessingWallet, + ) ?? + false; + final shouldRequireTOTP2FAForSendsToContact = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForSendsToContact, + ) ?? + false; + final shouldRequireTOTP2FAForSendsToNonContact = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForSendsToNonContact, + ) ?? + false; + final shouldRequireTOTP2FAForSendsToInternalWallets = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForSendsToInternalWallets, + ) ?? + false; + final shouldRequireTOTP2FAForExchangesToInternalWallets = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForExchangesToInternalWallets, + ) ?? + false; + final shouldRequireTOTP2FAForExchangesToExternalWallets = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForExchangesToExternalWallets, + ) ?? + false; + final shouldRequireTOTP2FAForAddingContacts = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForAddingContacts, + ) ?? + false; + final shouldRequireTOTP2FAForCreatingNewWallets = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForCreatingNewWallets, + ) ?? + false; + final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + ) ?? + false; + final useTOTP2FA = await SecureKey.getBool( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.useTOTP2FA, + ) ?? + false; + final totpSecretKey = await SecureKey.getString( + secureStorage: secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.useTOTP2FA, + ) ?? + ''; + return SettingsStore( + secureStorage: secureStorage, sharedPreferences: sharedPreferences, initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard, nodes: nodes, @@ -1055,8 +1128,6 @@ abstract class SettingsStoreBase with Store { shouldSaveRecipientAddress = sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? shouldSaveRecipientAddress; - useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA; - totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey; numberOfFailedTokenTrials = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials; isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; @@ -1065,41 +1136,10 @@ abstract class SettingsStoreBase with Store { walletListOrder = WalletListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0]; walletListAscending = sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true; - allowBiometricalAuthentication = - sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? - allowBiometricalAuthentication; - selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize( - raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ?? - Cake2FAPresetsOptions.normal.raw); - shouldRequireTOTP2FAForAccessingWallet = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet) ?? false; - shouldRequireTOTP2FAForSendsToContact = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToContact) ?? false; - shouldRequireTOTP2FAForSendsToNonContact = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact) ?? false; - shouldRequireTOTP2FAForSendsToInternalWallets = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ?? - false; - shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? - false; - shouldRequireTOTP2FAForExchangesToExternalWallets = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToExternalWallets) ?? - false; - shouldRequireTOTP2FAForAddingContacts = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false; - shouldRequireTOTP2FAForCreatingNewWallets = - sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ?? - false; - shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? - false; + shouldShowMarketPlaceInDashboard = sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? shouldShowMarketPlaceInDashboard; - selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize( - raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ?? - Cake2FAPresetsOptions.narrow.raw); exchangeStatus = ExchangeApiMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ?? ExchangeApiMode.enabled.raw); @@ -1188,6 +1228,93 @@ abstract class SettingsStoreBase with Store { if (nanoNode != null) { nodes[WalletType.nano] = nanoNode; } + + // MIGRATED: + + useTOTP2FA = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.useTOTP2FA, + ) ?? + useTOTP2FA; + + totpSecretKey = await SecureKey.getString( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.useTOTP2FA, + ) ?? + totpSecretKey; + + allowBiometricalAuthentication = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.allowBiometricalAuthenticationKey, + ) ?? + allowBiometricalAuthentication; + + selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize( + raw: await SecureKey.getInt( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.selectedCake2FAPreset, + ) ?? + Cake2FAPresetsOptions.normal.raw); + + shouldRequireTOTP2FAForAccessingWallet = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForAccessingWallet, + ) ?? + false; + shouldRequireTOTP2FAForSendsToContact = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForSendsToContact, + ) ?? + false; + + shouldRequireTOTP2FAForSendsToNonContact = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForSendsToNonContact, + ) ?? + false; + shouldRequireTOTP2FAForSendsToInternalWallets = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForSendsToInternalWallets, + ) ?? + false; + shouldRequireTOTP2FAForExchangesToInternalWallets = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForExchangesToInternalWallets, + ) ?? + false; + shouldRequireTOTP2FAForExchangesToExternalWallets = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForExchangesToExternalWallets, + ) ?? + false; + shouldRequireTOTP2FAForAddingContacts = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForAddingContacts, + ) ?? + false; + shouldRequireTOTP2FAForCreatingNewWallets = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForCreatingNewWallets, + ) ?? + false; + shouldRequireTOTP2FAForAllSecurityAndBackupSettings = await SecureKey.getBool( + secureStorage: _secureStorage, + sharedPreferences: sharedPreferences, + key: SecureKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + ) ?? + false; } Future _saveCurrentNode(Node node, WalletType walletType) async { diff --git a/lib/view_model/settings/security_settings_view_model.dart b/lib/view_model/settings/security_settings_view_model.dart index 5ea4dd4ea..965e65af8 100644 --- a/lib/view_model/settings/security_settings_view_model.dart +++ b/lib/view_model/settings/security_settings_view_model.dart @@ -44,5 +44,5 @@ abstract class SecuritySettingsViewModelBase with Store { setPinCodeRequiredDuration(PinCodeRequiredDuration duration) => _settingsStore.pinTimeOutDuration = duration; - bool checkPinCodeRiquired() => _authService.requireAuth(); + Future checkPinCodeRiquired() => _authService.requireAuth(); } 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 407b6d3bc..a7e27cbf6 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -161,7 +161,7 @@ abstract class WalletListViewModelBase with Store { } } - bool checkIfAuthRequired() { + Future checkIfAuthRequired() async { return _authService.requireAuth(); } } From 9754d676012d2d5195ac0d1f56bc8c7f5f06c373 Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 23 Jan 2024 07:15:24 +0200 Subject: [PATCH 4/8] Cw-343-a-list-of-previously-used-addresses (#1248) * add used addresses list * generate new address button * fix wallet type issue * fix addresses button title * update selectButton * show all wallet addresses * add tx amount and balance * fix ui * remove cashAddr format * fix generating new address issue * disable autogenerating * fix cashAddr format * minor fix * add search bar * Update address_cell.dart * fix merge conflict * address labeling feature * review fixes --------- Co-authored-by: Omar Hatem --- cw_bitcoin/lib/bitcoin_address_record.dart | 66 +++++-- cw_bitcoin/lib/bitcoin_wallet.dart | 3 + cw_bitcoin/lib/electrum_wallet.dart | 83 +++++--- cw_bitcoin/lib/electrum_wallet_addresses.dart | 182 ++++++++++-------- cw_bitcoin/lib/litecoin_wallet.dart | 3 + .../lib/src/bitcoin_cash_wallet.dart | 3 + cw_core/lib/amount_converter.dart | 1 + lib/bitcoin/cw_bitcoin.dart | 27 ++- lib/reactions/on_current_wallet_change.dart | 3 +- .../screens/dashboard/pages/address_page.dart | 68 ++----- .../new_wallet/widgets/select_button.dart | 32 +-- lib/src/screens/receive/receive_page.dart | 37 ++-- .../screens/receive/widgets/address_cell.dart | 180 ++++++++++++----- .../screens/receive/widgets/header_tile.dart | 138 +++++++++---- .../screens/receive/widgets/qr_widget.dart | 5 +- .../settings/privacy_settings_view_model.dart | 6 +- ...let_address_edit_or_create_view_model.dart | 20 +- .../wallet_address_list_item.dart | 9 +- .../wallet_address_list_view_model.dart | 104 +++++++--- tool/configure.dart | 24 ++- 20 files changed, 664 insertions(+), 330 deletions(-) diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 392771ab0..f2cd63904 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -1,40 +1,70 @@ import 'dart:convert'; +import 'package:bitbox/bitbox.dart' as bitbox; class BitcoinAddressRecord { - BitcoinAddressRecord(this.address, - {required this.index, this.isHidden = false, bool isUsed = false}) - : _isUsed = isUsed; + BitcoinAddressRecord( + this.address, { + required this.index, + this.isHidden = false, + int txCount = 0, + int balance = 0, + String name = '', + bool isUsed = false, + }) : _txCount = txCount, + _balance = balance, + _name = name, + _isUsed = isUsed; factory BitcoinAddressRecord.fromJSON(String jsonSource) { final decoded = json.decode(jsonSource) as Map; - return BitcoinAddressRecord( - decoded['address'] as String, - index: decoded['index'] as int, - isHidden: decoded['isHidden'] as bool? ?? false, - isUsed: decoded['isUsed'] as bool? ?? false); + return BitcoinAddressRecord(decoded['address'] as String, + index: decoded['index'] as int, + isHidden: decoded['isHidden'] as bool? ?? false, + isUsed: decoded['isUsed'] as bool? ?? false, + txCount: decoded['txCount'] as int? ?? 0, + name: decoded['name'] as String? ?? '', + balance: decoded['balance'] as int? ?? 0); } - @override - bool operator ==(Object o) => - o is BitcoinAddressRecord && address == o.address; - final String address; final bool isHidden; final int index; + int _txCount; + int _balance; + String _name; + bool _isUsed; + + int get txCount => _txCount; + + String get name => _name; + + int get balance => _balance; + + set txCount(int value) => _txCount = value; + + set balance(int value) => _balance = value; + bool get isUsed => _isUsed; + void setAsUsed() => _isUsed = true; + void setNewName(String label) => _name = label; + + @override + bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address; + @override int get hashCode => address.hashCode; - bool _isUsed; + String get cashAddr => bitbox.Address.toCashAddress(address); - void setAsUsed() => _isUsed = true; - - String toJSON() => - json.encode({ + String toJSON() => json.encode({ 'address': address, 'index': index, 'isHidden': isHidden, - 'isUsed': isUsed}); + 'txCount': txCount, + 'name': name, + 'isUsed': isUsed, + 'balance': balance, + }); } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 2c66d02fe..9cdb78f2d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -47,6 +47,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) .derivePath("m/0'/1"), networkType: networkType); + autorun((_) { + this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; + }); } static Future create({ diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 4c5ecc7f2..6be525a1f 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -63,6 +63,7 @@ abstract class ElectrumWalletBase _password = password, _feeRates = [], _isTransactionUpdating = false, + isEnabledAutoGenerateSubaddress = true, unspentCoins = [], _scripthashesUpdateSubject = {}, balance = ObservableMap.of(currency != null @@ -87,6 +88,10 @@ abstract class ElectrumWalletBase final bitcoin.HDWallet hd; final String mnemonic; + @override + @observable + bool isEnabledAutoGenerateSubaddress; + late ElectrumClient electrumClient; Box unspentCoinsInfo; @@ -583,38 +588,66 @@ abstract class ElectrumWalletBase Future> fetchTransactions() async { final addressHashes = {}; final normalizedHistories = >[]; + final newTxCounts = {}; + walletAddresses.addresses.forEach((addressRecord) { final sh = scriptHash(addressRecord.address, networkType: networkType); addressHashes[sh] = addressRecord; + newTxCounts[sh] = 0; }); - final histories = addressHashes.keys.map((scriptHash) => - electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history})); - final historyResults = await Future.wait(histories); - historyResults.forEach((history) { - history.entries.forEach((historyItem) { - if (historyItem.value.isNotEmpty) { - final address = addressHashes[historyItem.key]; - address?.setAsUsed(); - normalizedHistories.addAll(historyItem.value); - } + + try { + final histories = addressHashes.keys.map((scriptHash) => + electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history})); + final historyResults = await Future.wait(histories); + + + + historyResults.forEach((history) { + history.entries.forEach((historyItem) { + if (historyItem.value.isNotEmpty) { + final address = addressHashes[historyItem.key]; + address?.setAsUsed(); + newTxCounts[historyItem.key] = historyItem.value.length; + normalizedHistories.addAll(historyItem.value); + } + }); }); - }); - final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) { - try { - return fetchTransactionInfo( - hash: transaction['tx_hash'] as String, height: transaction['height'] as int); - } catch (_) { - return Future.value(null); + + for (var sh in addressHashes.keys) { + var balanceData = await electrumClient.getBalance(sh); + var addressRecord = addressHashes[sh]; + if (addressRecord != null) { + addressRecord.balance = balanceData['confirmed'] as int? ?? 0; + } } - })); - return historiesWithDetails - .fold>({}, (acc, tx) { - if (tx == null) { + + + addressHashes.forEach((sh, addressRecord) { + addressRecord.txCount = newTxCounts[sh] ?? 0; + }); + + final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) { + try { + return fetchTransactionInfo( + hash: transaction['tx_hash'] as String, height: transaction['height'] as int); + } catch (_) { + return Future.value(null); + } + })); + + return historiesWithDetails.fold>( + {}, (acc, tx) { + if (tx == null) { + return acc; + } + acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx; return acc; - } - acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx; - return acc; - }); + }); + } catch (e) { + print(e.toString()); + return {}; + } } Future updateTransactions() async { diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index c9e4f8200..a60a7e8ec 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -1,5 +1,5 @@ -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitbox/bitbox.dart' as bitbox; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/script_hash.dart'; @@ -10,8 +10,7 @@ import 'package:mobx/mobx.dart'; part 'electrum_wallet_addresses.g.dart'; -class ElectrumWalletAddresses = ElectrumWalletAddressesBase - with _$ElectrumWalletAddresses; +class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses; abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { ElectrumWalletAddressesBase(WalletInfo walletInfo, @@ -22,19 +21,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { List? initialAddresses, int initialRegularAddressIndex = 0, int initialChangeAddressIndex = 0}) - : addresses = ObservableList.of( - (initialAddresses ?? []).toSet()), - receiveAddresses = ObservableList.of( - (initialAddresses ?? []) + : addresses = ObservableList.of((initialAddresses ?? []).toSet()), + receiveAddresses = ObservableList.of((initialAddresses ?? []) .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed) - .toSet()), - changeAddresses = ObservableList.of( - (initialAddresses ?? []) + .toSet()), + changeAddresses = ObservableList.of((initialAddresses ?? []) .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed) - .toSet()), + .toSet()), currentReceiveAddressIndex = initialRegularAddressIndex, currentChangeAddressIndex = initialChangeAddressIndex, - super(walletInfo); + super(walletInfo); static const defaultReceiveAddressesCount = 22; static const defaultChangeAddressesCount = 17; @@ -42,6 +38,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { static String toCashAddr(String address) => bitbox.Address.toCashAddress(address); + static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address); + final ObservableList addresses; final ObservableList receiveAddresses; final ObservableList changeAddresses; @@ -53,41 +51,67 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @override @computed String get address { - if (receiveAddresses.isEmpty) { - final address = generateNewAddress().address; - return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(address) : address; - } - final receiveAddress = receiveAddresses.first.address; + if (isEnabledAutoGenerateSubaddress) { + if (receiveAddresses.isEmpty) { + final newAddress = generateNewAddress().address; + return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress; + } + final receiveAddress = receiveAddresses.first.address; - return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress; + return walletInfo.type == WalletType.bitcoinCash + ? toCashAddr(receiveAddress) + : receiveAddress; + } else { + final receiveAddress = (receiveAddresses.first.address != addresses.first.address && + previousAddressRecord != null) + ? previousAddressRecord!.address + : addresses.first.address; + + return walletInfo.type == WalletType.bitcoinCash + ? toCashAddr(receiveAddress) + : receiveAddress; + } + } + + @observable + bool isEnabledAutoGenerateSubaddress = true; + + @override + set address(String addr) { + if (addr.startsWith('bitcoincash:')) { + addr = toLegacy(addr); + } + final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == addr); + + previousAddressRecord = addressRecord; + receiveAddresses.remove(addressRecord); + receiveAddresses.insert(0, addressRecord); } @override String get primaryAddress => getAddress(index: 0, hd: mainHd); - @override - set address(String addr) => null; - int currentReceiveAddressIndex; int currentChangeAddressIndex; - @computed - int get totalCountOfReceiveAddresses => - addresses.fold(0, (acc, addressRecord) { - if (!addressRecord.isHidden) { - return acc + 1; - } - return acc; - }); + @observable + BitcoinAddressRecord? previousAddressRecord; @computed - int get totalCountOfChangeAddresses => - addresses.fold(0, (acc, addressRecord) { - if (addressRecord.isHidden) { - return acc + 1; - } - return acc; - }); + int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) { + if (!addressRecord.isHidden) { + return acc + 1; + } + return acc; + }); + + @computed + int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) { + if (addressRecord.isHidden) { + return acc + 1; + } + return acc; + }); Future discoverAddresses() async { await _discoverAddresses(mainHd, false); @@ -117,11 +141,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { if (changeAddresses.isEmpty) { final newAddresses = await _createNewAddresses(gap, - hd: sideHd, - startIndex: totalCountOfChangeAddresses > 0 - ? totalCountOfChangeAddresses - 1 - : 0, - isHidden: true); + hd: sideHd, + startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0, + isHidden: true); _addAddresses(newAddresses); } @@ -135,14 +157,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return address; } - BitcoinAddressRecord generateNewAddress( - {bitcoin.HDWallet? hd, bool isHidden = false}) { - currentReceiveAddressIndex += 1; - // FIX-ME: Check logic for whichi HD should be used here ??? - final address = BitcoinAddressRecord( - getAddress(index: currentReceiveAddressIndex, hd: hd ?? sideHd), - index: currentReceiveAddressIndex, - isHidden: isHidden); + BitcoinAddressRecord generateNewAddress({bitcoin.HDWallet? hd, String? label}) { + final isHidden = hd == sideHd; + + final newAddressIndex = addresses.fold( + 0, (int acc, addressRecord) => isHidden == addressRecord.isHidden ? acc + 1 : acc); + + final address = BitcoinAddressRecord(getAddress(index: newAddressIndex, hd: hd ?? sideHd), + index: newAddressIndex, isHidden: isHidden, name: label ?? ''); addresses.add(address); return address; } @@ -160,20 +182,32 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } } + @action + void updateAddress(String address, String label) { + if (address.startsWith('bitcoincash:')) { + address = toLegacy(address); + } + final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == address); + addressRecord.setNewName(label); + final index = addresses.indexOf(addressRecord); + addresses.remove(addressRecord); + addresses.insert(index, addressRecord); + } + @action void updateReceiveAddresses() { receiveAddresses.removeRange(0, receiveAddresses.length); - final newAdresses = addresses - .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed); - receiveAddresses.addAll(newAdresses); + final newAddresses = + addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed); + receiveAddresses.addAll(newAddresses); } @action void updateChangeAddresses() { changeAddresses.removeRange(0, changeAddresses.length); - final newAdresses = addresses - .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed); - changeAddresses.addAll(newAdresses); + final newAddresses = + addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed); + changeAddresses.addAll(newAddresses); } Future _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async { @@ -181,20 +215,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { List addrs; if (addresses.isNotEmpty) { - addrs = addresses - .where((addr) => addr.isHidden == isHidden) - .toList(); + addrs = addresses.where((addr) => addr.isHidden == isHidden).toList(); } else { addrs = await _createNewAddresses( - isHidden - ? defaultChangeAddressesCount - : defaultReceiveAddressesCount, + isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount, startIndex: 0, hd: hd, isHidden: isHidden); } - while(hasAddrUse) { + while (hasAddrUse) { final addr = addrs.last.address; hasAddrUse = await _hasAddressUsed(addr); @@ -204,11 +234,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final start = addrs.length; final count = start + gap; - final batch = await _createNewAddresses( - count, - startIndex: start, - hd: hd, - isHidden: isHidden); + final batch = await _createNewAddresses(count, startIndex: start, hd: hd, isHidden: isHidden); addrs.addAll(batch); } @@ -232,21 +258,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { if (countOfReceiveAddresses < defaultReceiveAddressesCount) { final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses; - final newAddresses = await _createNewAddresses( - addressesCount, - startIndex: countOfReceiveAddresses, - hd: mainHd, - isHidden: false); + final newAddresses = await _createNewAddresses(addressesCount, + startIndex: countOfReceiveAddresses, hd: mainHd, isHidden: false); addresses.addAll(newAddresses); } if (countOfHiddenAddresses < defaultChangeAddressesCount) { final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses; - final newAddresses = await _createNewAddresses( - addressesCount, - startIndex: countOfHiddenAddresses, - hd: sideHd, - isHidden: true); + final newAddresses = await _createNewAddresses(addressesCount, + startIndex: countOfHiddenAddresses, hd: sideHd, isHidden: true); addresses.addAll(newAddresses); } } @@ -256,10 +276,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final list = []; for (var i = startIndex; i < count + startIndex; i++) { - final address = BitcoinAddressRecord( - getAddress(index: i, hd: hd), - index: i, - isHidden: isHidden); + final address = + BitcoinAddressRecord(getAddress(index: i, hd: hd), index: i, isHidden: isHidden); list.add(address); } @@ -278,4 +296,4 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final transactionHistory = await electrumClient.getHistory(sh); return transactionHistory.isNotEmpty; } -} \ No newline at end of file +} diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 6bf1c5735..222e95acc 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -51,6 +51,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { .fromSeed(seedBytes, network: networkType) .derivePath("m/0'/1"), networkType: networkType,); + autorun((_) { + this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; + }); } static Future create({ diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index c23220423..1b87e2231 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -57,6 +57,9 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { sideHd: bitcoin.HDWallet.fromSeed(seedBytes) .derivePath("m/44'/145'/0'/1"), networkType: networkType); + autorun((_) { + this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; + }); } diff --git a/cw_core/lib/amount_converter.dart b/cw_core/lib/amount_converter.dart index 6fd43dd82..249b87bd3 100644 --- a/cw_core/lib/amount_converter.dart +++ b/cw_core/lib/amount_converter.dart @@ -81,6 +81,7 @@ class AmountConverter { return _moneroAmountToString(amount); case CryptoCurrency.btc: case CryptoCurrency.bch: + case CryptoCurrency.ltc: return _bitcoinAmountToString(amount); case CryptoCurrency.xhv: case CryptoCurrency.xag: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index dd713fd15..b6fb6e62d 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -63,9 +63,17 @@ class CWBitcoin extends Bitcoin { } @override - Future generateNewAddress(Object wallet) async { + Future generateNewAddress(Object wallet, String label) async { final bitcoinWallet = wallet as ElectrumWallet; - await bitcoinWallet.walletAddresses.generateNewAddress(); + await bitcoinWallet.walletAddresses.generateNewAddress(label: label); + await wallet.save(); + } + + @override + Future updateAddress(Object wallet,String address, String label) async { + final bitcoinWallet = wallet as ElectrumWallet; + bitcoinWallet.walletAddresses.updateAddress(address, label); + await wallet.save(); } @override @@ -99,6 +107,21 @@ class CWBitcoin extends Bitcoin { .toList(); } + @override + @computed + List getSubAddresses(Object wallet) { + final electrumWallet = wallet as ElectrumWallet; + return electrumWallet.walletAddresses.addresses + .map((BitcoinAddressRecord addr) => ElectrumSubAddress( + id: addr.index, + name: addr.name, + address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address, + txCount: addr.txCount, + balance: addr.balance, + isChange: addr.isHidden)) + .toList(); + } + @override String getAddress(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 42fbd182e..ade9927ff 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -68,7 +68,8 @@ void startCurrentWalletChangeReaction( .get() .setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type)); - if (wallet.type == WalletType.monero) { + if (wallet.type == WalletType.monero || wallet.type == WalletType.bitcoin || + wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash ) { _setAutoGenerateSubaddressStatus(wallet, settingsStore); } diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart index ff21d4aad..b4460edc9 100644 --- a/lib/src/screens/dashboard/pages/address_page.dart +++ b/lib/src/screens/dashboard/pages/address_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; @@ -15,6 +16,7 @@ import 'package:cake_wallet/utils/share_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart'; @@ -155,63 +157,27 @@ class AddressPage extends BasePage { amountController: _amountController, isLight: dashboardViewModel.settingsStore.currentTheme.type == ThemeType.light))), + SizedBox(height: 16), Observer(builder: (_) { if (addressListViewModel.hasAddressList) { - return GestureDetector( - onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled + return SelectButton( + text: addressListViewModel.buttonTitle, + onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled && + (WalletType.monero == addressListViewModel.wallet.type || + WalletType.haven == addressListViewModel.wallet.type) ? await showPopUp( - context: context, builder: (_) => getIt.get()) + context: context, + builder: (_) => getIt.get()) : Navigator.of(context).pushNamed(Routes.receive), - child: Container( - height: 50, - padding: EdgeInsets.only(left: 24, right: 12), - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(25)), - border: Border.all( - color: - Theme.of(context).extension()!.cardBorderColor, - width: 1), - color: Theme.of(context) - .extension()! - .syncedBackgroundColor), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Observer( - builder: (_) { - String label = addressListViewModel.hasAccounts - ? S.of(context).accounts_subaddresses - : S.of(context).addresses; - - if (dashboardViewModel.isAutoGenerateSubaddressesEnabled) { - label = addressListViewModel.hasAccounts - ? S.of(context).accounts - : S.of(context).account; - } - return Text( - label, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context) - .extension()! - .textColor), - ); - }, - ), - Icon( - Icons.arrow_forward_ios, - size: 14, - color: Theme.of(context).extension()!.textColor, - ) - ], - ), - ), + textColor: Theme.of(context).extension()!.textColor, + color: Theme.of(context).extension()!.syncedBackgroundColor, + borderColor: Theme.of(context).extension()!.cardBorderColor, + arrowColor: Theme.of(context).extension()!.textColor, + textSize: 14, + height: 50, ); } else if (dashboardViewModel.isAutoGenerateSubaddressesEnabled || - addressListViewModel.showElectrumAddressDisclaimer) { + addressListViewModel.isElectrumWallet) { return Text(S.of(context).electrum_address_disclaimer, textAlign: TextAlign.center, style: TextStyle( diff --git a/lib/src/screens/new_wallet/widgets/select_button.dart b/lib/src/screens/new_wallet/widgets/select_button.dart index e220b281e..d94c9767d 100644 --- a/lib/src/screens/new_wallet/widgets/select_button.dart +++ b/lib/src/screens/new_wallet/widgets/select_button.dart @@ -11,29 +11,37 @@ class SelectButton extends StatelessWidget { this.isSelected = false, this.showTrailingIcon = true, this.height = 60, + this.textSize = 18, + this.color, + this.textColor, + this.arrowColor, + this.borderColor, }); final Image? image; final String text; + final double textSize; final bool isSelected; final VoidCallback onTap; final bool showTrailingIcon; final double height; + final Color? color; + final Color? textColor; + final Color? arrowColor; + final Color? borderColor; @override Widget build(BuildContext context) { - final color = isSelected - ? Colors.green - : Theme.of(context).cardColor; - final textColor = isSelected + final backgroundColor = color ?? (isSelected ? Colors.green : Theme.of(context).cardColor); + final effectiveTextColor = textColor ?? (isSelected ? Theme.of(context).extension()!.restoreWalletButtonTextColor - : Theme.of(context).extension()!.buttonTextColor; - final arrowColor = isSelected + : Theme.of(context).extension()!.buttonTextColor); + final effectiveArrowColor = arrowColor ?? (isSelected ? Theme.of(context).extension()!.restoreWalletButtonTextColor - : Theme.of(context).extension()!.titlesColor; + : Theme.of(context).extension()!.titlesColor); final selectArrowImage = Image.asset('assets/images/select_arrow.png', - color: arrowColor); + color: effectiveArrowColor); return GestureDetector( onTap: onTap, @@ -44,7 +52,9 @@ class SelectButton extends StatelessWidget { alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(30)), - color: color + color: backgroundColor, + border: borderColor != null ? Border.all(color: borderColor!) : null, + ), child: Row( mainAxisSize: MainAxisSize.max, @@ -63,9 +73,9 @@ class SelectButton extends StatelessWidget { child: Text( text, style: TextStyle( - fontSize: 18, + fontSize: textSize, fontWeight: FontWeight.w500, - color: textColor + color: effectiveTextColor, ), ), ) diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 87b668e34..75719d123 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -49,7 +49,7 @@ class ReceivePage extends BasePage { bool get gradientBackground => true; @override - bool get resizeToAvoidBottomInset => false; + bool get resizeToAvoidBottomInset => true; final FocusNode _cryptoAmountFocus; @@ -99,10 +99,11 @@ class ReceivePage extends BasePage { @override Widget body(BuildContext context) { + final isElectrumWallet = addressListViewModel.isElectrumWallet; return (addressListViewModel.type == WalletType.monero || addressListViewModel.type == WalletType.haven || addressListViewModel.type == WalletType.nano || - addressListViewModel.type == WalletType.banano) + isElectrumWallet) ? KeyboardActions( config: KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.IOS, @@ -140,7 +141,9 @@ class ReceivePage extends BasePage { if (item is WalletAccountListHeader) { cell = HeaderTile( - onTap: () async { + showTrailingButton: true, + walletAddressListViewModel: addressListViewModel, + trailingButtonTap: () async { if (addressListViewModel.type == WalletType.monero || addressListViewModel.type == WalletType.haven) { await showPopUp( @@ -153,7 +156,7 @@ class ReceivePage extends BasePage { } }, title: S.of(context).accounts, - icon: Icon( + trailingIcon: Icon( Icons.arrow_forward_ios, size: 14, color: Theme.of(context).extension()!.iconsColor, @@ -161,16 +164,21 @@ class ReceivePage extends BasePage { } if (item is WalletAddressListHeader) { - cell = HeaderTile( - onTap: () => - Navigator.of(context).pushNamed(Routes.newSubaddress), - title: S.of(context).addresses, - icon: Icon( - Icons.add, - size: 20, - color: Theme.of(context).extension()!.iconsColor, - )); - } + cell = HeaderTile( + title: S.of(context).addresses, + walletAddressListViewModel: addressListViewModel, + showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled, + showSearchButton: true, + trailingButtonTap: () => + Navigator.of(context).pushNamed(Routes.newSubaddress), + trailingIcon: Icon( + Icons.add, + size: 20, + color: Theme.of(context) + .extension()! + .iconsColor, + )); + } if (item is WalletAddressListItem) { cell = Observer(builder: (_) { @@ -185,6 +193,7 @@ class ReceivePage extends BasePage { return AddressCell.fromItem(item, isCurrent: isCurrent, + hasBalance: addressListViewModel.isElectrumWallet, backgroundColor: backgroundColor, textColor: textColor, onTap: (_) => addressListViewModel.setAddress(item), diff --git a/lib/src/screens/receive/widgets/address_cell.dart b/lib/src/screens/receive/widgets/address_cell.dart index 733612e0b..92c870421 100644 --- a/lib/src/screens/receive/widgets/address_cell.dart +++ b/lib/src/screens/receive/widgets/address_cell.dart @@ -1,7 +1,8 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:auto_size_text/auto_size_text.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; class AddressCell extends StatelessWidget { AddressCell( @@ -12,13 +13,18 @@ class AddressCell extends StatelessWidget { required this.backgroundColor, required this.textColor, this.onTap, - this.onEdit}); + this.onEdit, + this.txCount, + this.balance, + this.isChange = false, + this.hasBalance = false}); factory AddressCell.fromItem(WalletAddressListItem item, {required bool isCurrent, required Color backgroundColor, required Color textColor, Function(String)? onTap, + bool hasBalance = false, Function()? onEdit}) => AddressCell( address: item.address, @@ -28,7 +34,11 @@ class AddressCell extends StatelessWidget { backgroundColor: backgroundColor, textColor: textColor, onTap: onTap, - onEdit: onEdit); + onEdit: onEdit, + txCount: item.txCount, + balance: item.balance, + isChange: item.isChange, + hasBalance: hasBalance); final String address; final String name; @@ -38,17 +48,22 @@ class AddressCell extends StatelessWidget { final Color textColor; final Function(String)? onTap; final Function()? onEdit; + final int? txCount; + final String? balance; + final bool isChange; + final bool hasBalance; - String get label { - if (name.isEmpty){ - if(address.length<=16){ - return address; - }else{ - return address.substring(0,8)+'...'+ - address.substring(address.length-8,address.length); - } - }else{ - return name; + static const int addressPreviewLength = 8; + + String get formattedAddress { + final formatIfCashAddr = address.replaceAll('bitcoincash:', ''); + + if (formatIfCashAddr.length <= (name.isNotEmpty ? 16 : 43)) { + return formatIfCashAddr; + } else { + return formatIfCashAddr.substring(0, addressPreviewLength) + + '...' + + formatIfCashAddr.substring(formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length); } } @@ -59,41 +74,114 @@ class AddressCell extends StatelessWidget { child: Container( width: double.infinity, color: backgroundColor, - padding: EdgeInsets.only(left: 24, right: 24, top: 28, bottom: 28), - child: Text( - label, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 14, - color: textColor, - ), + padding: EdgeInsets.only(left: 24, right: 24, top: 20, bottom: 20), + child: Row( + children: [ + Expanded( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + if (isChange) + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Container( + height: 20, + padding: EdgeInsets.all(4), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8.5)), + color: textColor), + alignment: Alignment.center, + child: Text( + S.of(context).unspent_change, + style: TextStyle( + color: backgroundColor, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + if (name.isNotEmpty) + Text( + '$name - ', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: textColor, + ), + ), + AutoSizeText( + formattedAddress, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: isChange ? 10 : 14, + color: textColor, + ), + ), + ], + ), + if (hasBalance) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + 'Balance: $balance', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: textColor, + ), + ), + Text( + '${S.of(context).transactions.toLowerCase()}: $txCount', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: textColor, + ), + ), + ], + ), + ), + ], + ), + ), + ], ), )); - return Semantics( - label: S.of(context).slidable, - selected: isCurrent, - enabled: !isCurrent, - child: Slidable( - key: Key(address), - startActionPane: _actionPane(context), - endActionPane: _actionPane(context), - child: cell, - ), - ); + return onEdit == null + ? cell + : Semantics( + label: S.of(context).slidable, + selected: isCurrent, + enabled: !isCurrent, + child: Slidable( + key: Key(address), + startActionPane: _actionPane(context), + endActionPane: _actionPane(context), + child: cell, + ), + ); } ActionPane _actionPane(BuildContext context) => ActionPane( - motion: const ScrollMotion(), - extentRatio: 0.3, - children: [ - SlidableAction( - onPressed: (_) => onEdit?.call(), - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - icon: Icons.edit, - label: S.of(context).edit, - ), - ], - ); + motion: const ScrollMotion(), + extentRatio: 0.3, + children: [ + SlidableAction( + onPressed: (_) => onEdit?.call(), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + icon: Icons.edit, + label: S.of(context).edit, + ), + ], + ); } diff --git a/lib/src/screens/receive/widgets/header_tile.dart b/lib/src/screens/receive/widgets/header_tile.dart index e9c134ea5..faaa9ed07 100644 --- a/lib/src/screens/receive/widgets/header_tile.dart +++ b/lib/src/screens/receive/widgets/header_tile.dart @@ -1,50 +1,114 @@ -import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; +import 'package:flutter/material.dart'; -class HeaderTile extends StatelessWidget { +class HeaderTile extends StatefulWidget { HeaderTile({ - required this.onTap, required this.title, - required this.icon + required this.walletAddressListViewModel, + this.showSearchButton = false, + this.showTrailingButton = false, + this.trailingButtonTap, + this.trailingIcon, }); - final VoidCallback onTap; final String title; - final Icon icon; + final WalletAddressListViewModel walletAddressListViewModel; + final bool showSearchButton; + final bool showTrailingButton; + final VoidCallback? trailingButtonTap; + final Icon? trailingIcon; + + @override + _HeaderTileState createState() => _HeaderTileState(); +} + +class _HeaderTileState extends State { + bool _isSearchActive = false; @override Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Container( - padding: EdgeInsets.only( - left: 24, - right: 24, - top: 24, - bottom: 24 - ), - color: Theme.of(context).extension()!.tilesBackgroundColor, - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - title, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.tilesTextColor), - ), - Container( - height: 32, - width: 32, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).extension()!.iconsBackgroundColor), - child: icon, - ) - ], - ), + final searchIcon = Image.asset("assets/images/search_icon.png", + color: Theme.of(context).extension()!.iconsColor); + + return Container( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + color: Theme.of(context).extension()!.tilesBackgroundColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _isSearchActive + ? Expanded( + child: TextField( + onChanged: (value) => widget.walletAddressListViewModel.updateSearchText(value), + cursorColor: Theme.of(context).extension()!.tilesTextColor, + cursorWidth: 0.5, + decoration: InputDecoration( + hintText: '${S.of(context).search}...', + isDense: true, + contentPadding: EdgeInsets.zero, + hintStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.tilesTextColor), + border: UnderlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).dividerColor), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).dividerColor), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).dividerColor), + ), + ), + autofocus: true, + ), + ) + : Text( + widget.title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.tilesTextColor), + ), + Row( + children: [ + if (widget.showSearchButton) + GestureDetector( + onTap: () { + setState(() { + _isSearchActive = !_isSearchActive; + widget.walletAddressListViewModel.updateSearchText(''); + }); + }, + child: Container( + height: 32, + width: 32, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context) + .extension()! + .iconsBackgroundColor), + child: searchIcon, + )), + const SizedBox(width: 8), + if (widget.showTrailingButton) + GestureDetector( + onTap: widget.trailingButtonTap, + child: Container( + height: 32, + width: 32, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: + Theme.of(context).extension()!.iconsBackgroundColor), + child: widget.trailingIcon, + ), + ), + ], + ), + ], ), ); } diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index bbfd4d5c1..bedb3b526 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -1,3 +1,4 @@ +import 'package:auto_size_text/auto_size_text.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; import 'package:cake_wallet/routes.dart'; @@ -6,6 +7,7 @@ import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dar import 'package:cake_wallet/utils/brightness_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -144,9 +146,10 @@ class QRWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: Text( + child: AutoSizeText( addressListViewModel.address.address, textAlign: TextAlign.center, + maxLines: addressListViewModel.wallet.type == WalletType.monero ? 2 : 1, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index e4dd86b37..65375b3e7 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -38,7 +38,11 @@ abstract class PrivacySettingsViewModelBase with Store { } } - bool get isAutoGenerateSubaddressesVisible => _wallet.type == WalletType.monero; + bool get isAutoGenerateSubaddressesVisible => + _wallet.type == WalletType.monero || + _wallet.type == WalletType.bitcoin || + _wallet.type == WalletType.litecoin || + _wallet.type == WalletType.bitcoinCash; @computed bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress; diff --git a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart index 9e2aa7187..0b4f969cb 100644 --- a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart @@ -1,6 +1,5 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:mobx/mobx.dart'; -import 'package:flutter/foundation.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/monero/monero.dart'; @@ -33,7 +32,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { state = AddressEditOrCreateStateInitial(), label = item?.name ?? '', _item = item, - _wallet = wallet; + _wallet = wallet; @observable AddressEditOrCreateState state; @@ -46,6 +45,10 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { final WalletAddressListItem? _item; final WalletBase _wallet; + bool get isElectrum => _wallet.type == WalletType.bitcoin || + _wallet.type == WalletType.bitcoinCash || + _wallet.type == WalletType.litecoin; + Future save() async { try { state = AddressIsSaving(); @@ -65,12 +68,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { Future _createNew() async { final wallet = _wallet; - if (wallet.type == WalletType.bitcoin - || wallet.type == WalletType.litecoin - || wallet.type == WalletType.bitcoinCash) { - await bitcoin!.generateNewAddress(wallet); - await wallet.save(); - } + if (isElectrum) await bitcoin!.generateNewAddress(wallet, label); if (wallet.type == WalletType.monero) { await monero @@ -96,10 +94,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { Future _update() async { final wallet = _wallet; - /*if (wallet is BitcoinWallet) { - await wallet.walletAddresses.updateAddress(_item.address as String); - await wallet.save(); - }*/ + if (isElectrum) await bitcoin!.updateAddress(wallet, _item!.address, label); + final index = _item?.id; if (index != null) { if (wallet.type == WalletType.monero) { diff --git a/lib/view_model/wallet_address_list/wallet_address_list_item.dart b/lib/view_model/wallet_address_list/wallet_address_list_item.dart index ba7b93cf9..1152f1404 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_item.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_item.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:cake_wallet/utils/list_item.dart'; class WalletAddressListItem extends ListItem { @@ -6,13 +5,19 @@ class WalletAddressListItem extends ListItem { required this.address, required this.isPrimary, this.id, - this.name}) + this.name, + this.txCount, + this.balance, + this.isChange = false}) : super(); final int? id; final bool isPrimary; final String address; final String? name; + final int? txCount; + final String? balance; + final bool isChange; @override String toString() => name ?? address; diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index ade279124..9270d1d44 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -1,21 +1,25 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; -import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; -import 'package:cw_core/currency.dart'; -import 'package:intl/intl.dart'; -import 'package:mobx/mobx.dart'; import 'package:cake_wallet/utils/list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; +import 'package:cw_core/amount_converter.dart'; +import 'package:cw_core/currency.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/monero/monero.dart'; -import 'package:cake_wallet/haven/haven.dart'; +import 'package:intl/intl.dart'; +import 'package:mobx/mobx.dart'; part 'wallet_address_list_view_model.g.dart'; @@ -110,7 +114,8 @@ class EthereumURI extends PaymentURI { class BitcoinCashURI extends PaymentURI { BitcoinCashURI({required String amount, required String address}) - : super(amount: amount, address: address); + : super(amount: amount, address: address); + @override String toString() { var base = address; @@ -121,9 +126,7 @@ class BitcoinCashURI extends PaymentURI { return base; } - } - - +} class NanoURI extends PaymentURI { NanoURI({required String amount, required String address}) @@ -167,6 +170,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo hasAccounts = appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.haven, amount = '', + _settingsStore = appStore.settingsStore, super(appStore: appStore) { _init(); } @@ -184,12 +188,28 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo final NumberFormat _cryptoNumberFormat; final FiatConversionStore fiatConversionStore; + final SettingsStore _settingsStore; List get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all]; + String get buttonTitle { + if (isElectrumWallet) { + return S.current.addresses; + } + + if (isAutoGenerateSubaddressEnabled) { + return hasAccounts ? S.current.accounts : S.current.account; + } + + return hasAccounts ? S.current.accounts_subaddresses : S.current.addresses; + } + @observable Currency selectedCurrency; + @observable + String searchText = ''; + @computed int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency); @@ -277,14 +297,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo addressList.addAll(addressItems); } - if (wallet.type == WalletType.bitcoin) { - final primaryAddress = bitcoin!.getAddress(wallet); - final bitcoinAddresses = bitcoin!.getAddresses(wallet).map((addr) { - final isPrimary = addr == primaryAddress; + if (isElectrumWallet) { + final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) { + final isPrimary = subaddress.id == 0; - return WalletAddressListItem(isPrimary: isPrimary, name: null, address: addr); + return WalletAddressListItem( + id: subaddress.id, + isPrimary: isPrimary, + name: subaddress.name, + address: subaddress.address, + txCount: subaddress.txCount, + balance: AmountConverter.amountIntToString( + walletTypeToCryptoCurrency(type), subaddress.balance), + isChange: subaddress.isChange); }); - addressList.addAll(bitcoinAddresses); + addressList.addAll(addressItems); } if (wallet.type == WalletType.ethereum) { @@ -299,6 +326,15 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); } + if (searchText.isNotEmpty) { + return ObservableList.of(addressList.where((item) { + if (item is WalletAddressListItem) { + return item.address.toLowerCase().contains(searchText.toLowerCase()); + } + return false; + })); + } + return addressList; } @@ -321,15 +357,23 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo @computed bool get hasAddressList => wallet.type == WalletType.monero || - wallet.type == WalletType.haven;/* || - wallet.type == WalletType.nano || - wallet.type == WalletType.banano;*/// TODO: nano accounts are disabled for now + wallet.type == WalletType.haven || + wallet.type == WalletType.bitcoinCash || + wallet.type == WalletType.bitcoin || + wallet.type == WalletType.litecoin; + + // wallet.type == WalletType.nano || + // wallet.type == WalletType.banano; TODO: nano accounts are disabled for now @computed - bool get showElectrumAddressDisclaimer => + bool get isElectrumWallet => wallet.type == WalletType.bitcoin || - wallet.type == WalletType.litecoin || - wallet.type == WalletType.bitcoinCash; + wallet.type == WalletType.litecoin || + wallet.type == WalletType.bitcoinCash; + + @computed + bool get isAutoGenerateSubaddressEnabled => + _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled; List _baseItems; @@ -343,9 +387,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo _baseItems = []; if (wallet.type == WalletType.monero || - wallet.type == WalletType.haven /*|| + wallet.type == + WalletType + .haven /*|| wallet.type == WalletType.nano || - wallet.type == WalletType.banano*/) { + wallet.type == WalletType.banano*/ + ) { _baseItems.add(WalletAccountListHeader()); } @@ -367,6 +414,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo } } + @action + void updateSearchText(String text) { + searchText = text; + } + void _convertAmountToCrypto() { final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type); try { diff --git a/tool/configure.dart b/tool/configure.dart index 9d99bf49a..ce894d197 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -64,6 +64,7 @@ import 'package:cw_core/output_info.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart';"""; const bitcoinCWHeaders = """ import 'package:cw_bitcoin/electrum_wallet.dart'; @@ -76,9 +77,27 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/litecoin_wallet_service.dart'; +import 'package:mobx/mobx.dart'; """; const bitcoinCwPart = "part 'cw_bitcoin.dart';"; const bitcoinContent = """ + + class ElectrumSubAddress { + ElectrumSubAddress({ + required this.id, + required this.name, + required this.address, + required this.txCount, + required this.balance, + required this.isChange}); + final int id; + final String name; + final String address; + final int txCount; + final int balance; + final bool isChange; +} + abstract class Bitcoin { TransactionPriority getMediumTransactionPriority(); @@ -92,13 +111,16 @@ abstract class Bitcoin { TransactionPriority deserializeBitcoinTransactionPriority(int raw); TransactionPriority deserializeLitecoinTransactionPriority(int raw); int getFeeRate(Object wallet, TransactionPriority priority); - Future generateNewAddress(Object wallet); + Future generateNewAddress(Object wallet, String label); + Future updateAddress(Object wallet,String address, String label); Object createBitcoinTransactionCredentials(List outputs, {required TransactionPriority priority, int? feeRate}); Object createBitcoinTransactionCredentialsRaw(List outputs, {TransactionPriority? priority, required int feeRate}); List getAddresses(Object wallet); String getAddress(Object wallet); + List getSubAddresses(Object wallet); + String formatterBitcoinAmountToString({required int amount}); double formatterBitcoinAmountToDouble({required int amount}); int formatterStringDoubleToBitcoinAmount(String amount); From aed60a7282db2ac839e759cacf4d77c59575dcb0 Mon Sep 17 00:00:00 2001 From: Adegoke David <64401859+Blazebrain@users.noreply.github.com> Date: Fri, 26 Jan 2024 23:48:33 +0100 Subject: [PATCH 5/8] fix: Route to dashboard when exchange is done (#1276) --- lib/src/screens/exchange_trade/exchange_trade_page.dart | 7 ++++++- macos/Podfile.lock | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index 7930c9c82..41fc29713 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'dart:ui'; import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; @@ -343,7 +344,11 @@ class ExchangeTradeState extends State { bottom: 24, child: PrimaryButton( onPressed: () { - Navigator.of(popupContext).pop(); + Navigator.pushNamedAndRemoveUntil( + popupContext, + Routes.dashboard, + (route) => false, + ); RequestReviewHandler.requestReview(); }, text: S.of(popupContext).got_it, diff --git a/macos/Podfile.lock b/macos/Podfile.lock index c158c737e..fcbe1d733 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -106,7 +106,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: connectivity_plus_macos: f6e86fd000e971d361e54b5afcadc8c8fa773308 - cw_monero: ec03de55a19c4a2b174ea687e0f4202edc716fa4 + cw_monero: f8b7f104508efba2591548e76b5c058d05cba3f0 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d From 89fdc0f4d111f4fe2295647fa45cbe11041d3596 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Sat, 27 Jan 2024 00:51:21 +0200 Subject: [PATCH 6/8] Generic fixes (#1274) * Display fees currency as wallet currency not the selected one * remove unused code catch balance network issues * pop send screen when send completes successfully * revert change [skip ci] * Enable restoring haven wallets * verify context is mounted before showing snackbar [skip ci] * Update privacy [skip ci] * Add user consent popup to inapp webview permission request --- PRIVACY.md | 8 +-- cw_ethereum/lib/ethereum_client.dart | 10 ++-- lib/di.dart | 4 +- lib/src/screens/buy/webview_page.dart | 29 ++++++++++- .../desktop_wallet_selection_dropdown.dart | 8 ++- .../new_wallet/new_wallet_type_page.dart | 11 ++-- lib/src/screens/send/widgets/send_card.dart | 2 +- lib/store/settings_store.dart | 52 ++++++++++--------- .../security_settings_view_model.dart | 11 +--- .../wallet_list/wallet_list_view_model.dart | 7 --- res/values/strings_ar.arb | 3 +- res/values/strings_bg.arb | 3 +- res/values/strings_cs.arb | 3 +- res/values/strings_de.arb | 3 +- res/values/strings_en.arb | 3 +- res/values/strings_es.arb | 3 +- res/values/strings_fr.arb | 3 +- res/values/strings_ha.arb | 3 +- res/values/strings_hi.arb | 3 +- res/values/strings_hr.arb | 3 +- res/values/strings_id.arb | 3 +- res/values/strings_it.arb | 3 +- res/values/strings_ja.arb | 3 +- res/values/strings_ko.arb | 3 +- res/values/strings_my.arb | 3 +- res/values/strings_nl.arb | 3 +- res/values/strings_pl.arb | 3 +- res/values/strings_pt.arb | 3 +- res/values/strings_ru.arb | 3 +- res/values/strings_th.arb | 3 +- res/values/strings_tl.arb | 3 +- res/values/strings_tr.arb | 3 +- res/values/strings_uk.arb | 3 +- res/values/strings_ur.arb | 3 +- res/values/strings_yo.arb | 3 +- res/values/strings_zh.arb | 3 +- 36 files changed, 134 insertions(+), 86 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 88f180c5e..76cfcc4d3 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,6 +1,6 @@ Privacy Policy -Last modified: August 9, 2023 +Last modified: January 24, 2024 Introduction ============ @@ -112,12 +112,12 @@ Data Security In any situation, Cake Labs takes no responsibility for interception of personal data by any outside individual, group, corporation, or institution. You should understand this and take any and all appropriate actions to secure your own data. -Links to Other Websites ------------------------ +Other Websites and Third-Party Services +--------------------------------------- The App may contain links to other websites that are not operated by us. If you click on a Third-Party Service link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit. We have no control over and assume no responsibility for the content, privacy policies or practices of any third-party sites or services. - The App includes several optional Third-Party Services, which may not be available to all users. If you use Third-Party Services, you must agree to their respective Privacy Policies. + The App includes several optional Third-Party Services, which may not be available to all users. If you use Third-Party Services, you must agree to their respective Privacy Policies. When using certain optional features in the app such as buying and selling, you may be asked to provide information to a Third-Party Service. You will need to read and accept the privacy policy for that third party. This Third-Party Service may ask for your name, your photo ID, your social security number or other similar number, mailing address, cryptocurrency address, or other information. They may ask you to take a selfie image. Information shared with a Third-Party Service is subject to their respective Privacy Policies. Changes to Our Privacy Policy ----------------------------- diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index fccbf778d..1b3e4bb44 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -190,11 +190,15 @@ I/flutter ( 4474): Gas Used: 53000 Future fetchERC20Balances( EthereumAddress userAddress, String contractAddress) async { final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!); - final balance = await erc20.balanceOf(userAddress); + try { + final balance = await erc20.balanceOf(userAddress); - int exponent = (await erc20.decimals()).toInt(); + int exponent = (await erc20.decimals()).toInt(); - return ERC20Balance(balance, exponent: exponent); + return ERC20Balance(balance, exponent: exponent); + } catch (_) { + return ERC20Balance(BigInt.zero); + } } Future getErc20Token(String contractAddress) async { diff --git a/lib/di.dart b/lib/di.dart index a37c76b34..05019a562 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -612,7 +612,6 @@ Future setup({ _walletInfoSource, getIt.get(), getIt.get(), - getIt.get(), ), ); } else { @@ -623,7 +622,6 @@ Future setup({ _walletInfoSource, getIt.get(), getIt.get(), - getIt.get(), ), ); } @@ -725,7 +723,7 @@ Future setup({ }); getIt.registerFactory(() { - return SecuritySettingsViewModel(getIt.get(), getIt.get()); + return SecuritySettingsViewModel(getIt.get()); }); getIt.registerFactory(() => WalletSeedViewModel(getIt.get().wallet!)); diff --git a/lib/src/screens/buy/webview_page.dart b/lib/src/screens/buy/webview_page.dart index 97387c29a..0af5952c4 100644 --- a/lib/src/screens/buy/webview_page.dart +++ b/lib/src/screens/buy/webview_page.dart @@ -1,4 +1,7 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -14,13 +17,14 @@ class WebViewPage extends BasePage { @override Widget body(BuildContext context) { - return WebViewPageBody(_url); + return WebViewPageBody(_title, _url); } } class WebViewPageBody extends StatefulWidget { - WebViewPageBody(this.uri); + WebViewPageBody(this.title, this.uri); + final String title; final Uri uri; @override @@ -40,6 +44,27 @@ class WebViewPageBodyState extends State { onPermissionRequest: (controller, request) async { bool permissionGranted = await Permission.camera.status == PermissionStatus.granted; if (!permissionGranted) { + final bool userConsent = await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).privacy, + alertContent: S.of(context).camera_consent(widget.title), + rightButtonText: S.of(context).agree, + leftButtonText: S.of(context).cancel, + actionRightButton: () => Navigator.of(context).pop(true), + actionLeftButton: () => Navigator.of(context).pop(false)); + }) ?? + false; + + /// if user did NOT give the consent then return permission denied + if (!userConsent) { + return PermissionResponse( + resources: request.resources, + action: PermissionResponseAction.DENY, + ); + } + permissionGranted = await Permission.camera.request().isGranted; } diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart index f3dd2427d..a81a3f6e4 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -166,12 +166,16 @@ class _DesktopWalletSelectionDropDownState extends State WalletTypeForm( - onTypeSelected: onTypeSelected, - walletImage: currentTheme.type == ThemeType.dark ? walletTypeImage : walletTypeLightImage); + onTypeSelected: onTypeSelected, + walletImage: currentTheme.type == ThemeType.dark ? walletTypeImage : walletTypeLightImage, + isCreate: isCreate, + ); } class WalletTypeForm extends StatefulWidget { - WalletTypeForm({required this.onTypeSelected, required this.walletImage}); + WalletTypeForm({required this.onTypeSelected, required this.walletImage, required this.isCreate}); final void Function(BuildContext, WalletType) onTypeSelected; final Image walletImage; + final bool isCreate; @override WalletTypeFormState createState() => WalletTypeFormState(); @@ -131,7 +134,7 @@ class WalletTypeFormState extends State { throw Exception('Wallet Type is not selected yet.'); } - if (selected == WalletType.haven) { + if (selected == WalletType.haven && widget.isCreate) { return await showPopUp( context: context, builder: (BuildContext context) { diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 65069e903..07fb34cb2 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -478,7 +478,7 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin _settingsStore.allowBiometricalAuthentication; @@ -41,8 +36,6 @@ abstract class SecuritySettingsViewModelBase with Store { _settingsStore.allowBiometricalAuthentication = value; @action - setPinCodeRequiredDuration(PinCodeRequiredDuration duration) => + void setPinCodeRequiredDuration(PinCodeRequiredDuration duration) => _settingsStore.pinTimeOutDuration = duration; - - Future checkPinCodeRiquired() => _authService.requireAuth(); } 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 a7e27cbf6..b31133c7d 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -1,4 +1,3 @@ -import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cake_wallet/entities/wallet_list_order_types.dart'; import 'package:hive/hive.dart'; @@ -18,7 +17,6 @@ abstract class WalletListViewModelBase with Store { this._walletInfoSource, this._appStore, this._walletLoadingService, - this._authService, ) : wallets = ObservableList() { setOrderType(_appStore.settingsStore.walletListOrder); reaction((_) => _appStore.wallet, (_) => updateList()); @@ -39,7 +37,6 @@ abstract class WalletListViewModelBase with Store { final AppStore _appStore; final Box _walletInfoSource; final WalletLoadingService _walletLoadingService; - final AuthService _authService; WalletType get currentWalletType => _appStore.wallet!.type; @@ -160,8 +157,4 @@ abstract class WalletListViewModelBase with Store { break; } } - - Future checkIfAuthRequired() async { - return _authService.requireAuth(); - } } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 8ad9ed45b..8fbcb481c 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -763,5 +763,6 @@ "receivable_balance": "التوازن القادم", "confirmed_tx": "مؤكد", "transaction_details_source_address": "عنوان المصدر", - "pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ" + "pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ", + "camera_consent": ".ﻞﻴﺻﺎﻔﺘﻟﺍ ﻰﻠﻋ ﻝﻮﺼﺤﻠﻟ ﻢﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺔﻴﺻﻮﺼﺨﻟﺍ ﺔﺳﺎﻴﺳ ﻦﻣ ﻖﻘﺤﺘﻟﺍ ﻰﺟﺮﻳ .${provider} ﻝﻮﻠ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 6f1c96af5..1707c3f8c 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -759,5 +759,6 @@ "receivable_balance": "Баланс за вземания", "confirmed_tx": "Потвърдено", "transaction_details_source_address": "Адрес на източника", - "pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза." + "pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.", + "camera_consent": "Вашият фотоапарат ще бъде използван за заснемане на изображение с цел идентификация от ${provider}. Моля, проверете тяхната политика за поверителност за подробности." } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index bb63ea00a..dfae6717c 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -759,5 +759,6 @@ "receivable_balance": "Zůstatek pohledávek", "confirmed_tx": "Potvrzeno", "transaction_details_source_address": "Zdrojová adresa", - "pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena." + "pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.", + "camera_consent": "Váš fotoaparát použije k pořízení snímku pro účely identifikace ${provider}. Podrobnosti najdete v jejich Zásadách ochrany osobních údajů." } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index a0795960a..b96861f4d 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -767,5 +767,6 @@ "receivable_balance": "Forderungsbilanz", "confirmed_tx": "Bestätigt", "transaction_details_source_address": "Quelladresse", - "pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert." + "pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.", + "camera_consent": "Mit Ihrer Kamera wird bis zum ${provider} ein Bild zur Identifizierung aufgenommen. Weitere Informationen finden Sie in deren Datenschutzbestimmungen." } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 20ec41126..10c130a0c 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -768,5 +768,6 @@ "receivable_balance": "Receivable Balance", "confirmed_tx": "Confirmed", "transaction_details_source_address": "Source address", - "pause_wallet_creation": "Ability to create Haven Wallet is currently paused." + "pause_wallet_creation": "Ability to create Haven Wallet is currently paused.", + "camera_consent": "Your camera will be used to capture an image for identification purposes by ${provider}. Please check their Privacy Policy for details." } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index e3783afe0..5bec3b0ae 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -767,5 +767,6 @@ "receivable_balance": "Saldo de cuentas por cobrar", "confirmed_tx": "Confirmado", "transaction_details_source_address": "Dirección de la fuente", - "pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada." + "pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.", + "camera_consent": "Su cámara será utilizada para capturar una imagen con fines de identificación por ${provider}. Consulte su Política de privacidad para obtener más detalles." } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 5ae616bda..9ce607b20 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -767,5 +767,6 @@ "receivable_balance": "Solde de créances", "confirmed_tx": "Confirmé", "transaction_details_source_address": "Adresse source", - "pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue." + "pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.", + "camera_consent": "Votre appareil photo sera utilisé pour capturer une image à des fins d'identification par ${provider}. Veuillez consulter leur politique de confidentialité pour plus de détails." } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index cdbfeb1b6..47426aa8c 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -749,5 +749,6 @@ "receivable_balance": "Daidaituwa da daidaituwa", "confirmed_tx": "Tabbatar", "transaction_details_source_address": "Adireshin Incord", - "pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet." + "pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.", + "camera_consent": "Za a yi amfani da kyamarar ku don ɗaukar hoto don dalilai na tantancewa ta ${provider}. Da fatan za a duba Manufar Sirri don cikakkun bayanai." } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 22519a1c4..91bf40d60 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -767,5 +767,6 @@ "receivable_balance": "प्राप्य शेष", "confirmed_tx": "की पुष्टि", "transaction_details_source_address": "स्रोत पता", - "pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।" + "pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।", + "camera_consent": "आपके कैमरे का उपयोग ${provider} द्वारा पहचान उद्देश्यों के लिए एक छवि कैप्चर करने के लिए किया जाएगा। विवरण के लिए कृपया उनकी गोपनीयता नीति जांचें।" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index c951baed4..b3c49ce78 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -765,5 +765,6 @@ "receivable_balance": "Stanje potraživanja", "confirmed_tx": "Potvrđen", "transaction_details_source_address": "Adresa izvora", - "pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana." + "pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.", + "camera_consent": "Vaš će fotoaparat koristiti za snimanje slike u svrhu identifikacije od strane ${provider}. Pojedinosti potražite u njihovoj politici privatnosti." } diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index d383da478..2a8fd06e8 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -755,5 +755,6 @@ "receivable_balance": "Saldo piutang", "confirmed_tx": "Dikonfirmasi", "transaction_details_source_address": "Alamat sumber", - "pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda." + "pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.", + "camera_consent": "Kamera Anda akan digunakan untuk mengambil gambar untuk tujuan identifikasi oleh ${provider}. Silakan periksa Kebijakan Privasi mereka untuk detailnya." } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 29edecaad..01e88d56f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -767,5 +767,6 @@ "receivable_balance": "Bilanciamento creditizio", "confirmed_tx": "Confermato", "transaction_details_source_address": "Indirizzo di partenza", - "pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa." + "pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa.", + "camera_consent": "La tua fotocamera verrà utilizzata per acquisire un'immagine a scopo identificativo da ${provider}. Si prega di controllare la loro Informativa sulla privacy per i dettagli." } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index d15c79c34..07bf5fca8 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -767,5 +767,6 @@ "receivable_balance": "売掛金残高", "confirmed_tx": "確認済み", "transaction_details_source_address": "ソースアドレス", - "pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。" + "pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。", + "camera_consent": "あなたのカメラは、${provider}_ までに識別目的で画像を撮影するために使用されます。詳細については、プライバシー ポリシーをご確認ください。" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 2a53ecf77..7ea1ea85d 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -765,5 +765,6 @@ "receivable_balance": "채권 잔액", "confirmed_tx": "확인", "transaction_details_source_address": "소스 주소", - "pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다." + "pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다.", + "camera_consent": "귀하의 카메라는 ${provider}의 식별 목적으로 이미지를 캡처하는 데 사용됩니다. 자세한 내용은 해당 개인정보 보호정책을 확인하세요." } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 04986ad5c..9559d224e 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -765,5 +765,6 @@ "receivable_balance": "လက်ကျန်ငွေ", "confirmed_tx": "အတည်ပြုသည်", "transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ", - "pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။" + "pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။", + "camera_consent": "မှတ်ပုံတင်ခြင်းရည်ရွယ်ချက်များအတွက် ${provider} တွင် သင့်ကင်မရာကို အသုံးပြုပါမည်။ အသေးစိတ်အတွက် ၎င်းတို့၏ ကိုယ်ရေးကိုယ်တာမူဝါဒကို စစ်ဆေးပါ။" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 6c2ed2691..23a635a99 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -767,5 +767,6 @@ "receivable_balance": "Het saldo", "confirmed_tx": "Bevestigd", "transaction_details_source_address": "Bron adres", - "pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken." + "pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.", + "camera_consent": "Uw camera wordt gebruikt om vóór ${provider} een beeld vast te leggen voor identificatiedoeleinden. Raadpleeg hun privacybeleid voor meer informatie." } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 5e37e982a..f3e9db35c 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -767,5 +767,6 @@ "receivable_balance": "Saldo należności", "confirmed_tx": "Potwierdzony", "transaction_details_source_address": "Adres źródłowy", - "pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana." + "pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.", + "camera_consent": "Twój aparat zostanie użyty do przechwycenia obrazu w celach identyfikacyjnych przez ${provider}. Aby uzyskać szczegółowe informacje, sprawdź ich Politykę prywatności." } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 66f0dd928..42c4cec28 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -766,5 +766,6 @@ "receivable_balance": "Saldo a receber", "confirmed_tx": "Confirmado", "transaction_details_source_address": "Endereço de Origem", - "pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada." + "pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.", + "camera_consent": "Sua câmera será usada para capturar uma imagem para fins de identificação por ${provider}. Por favor, verifique a Política de Privacidade para obter detalhes." } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 60c7bbc15..edc6993a0 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -767,5 +767,6 @@ "receivable_balance": "Баланс дебиторской задолженности", "confirmed_tx": "Подтвержденный", "transaction_details_source_address": "Адрес источника", - "pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена." + "pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.", + "camera_consent": "Ваша камера будет использоваться для захвата изображения в целях идентификации ${provider}. Пожалуйста, ознакомьтесь с их Политикой конфиденциальности для получения подробной информации." } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index b28964357..80fa3f9c6 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -765,5 +765,6 @@ "receivable_balance": "ยอดลูกหนี้", "confirmed_tx": "ซึ่งยืนยันแล้ว", "transaction_details_source_address": "ที่อยู่แหล่งกำเนิด", - "pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว" + "pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว", + "camera_consent": "กล้องของคุณจะถูกนำมาใช้เพื่อจับภาพเพื่อวัตถุประสงค์ในการระบุตัวตนภายใน ${provider} โปรดตรวจสอบนโยบายความเป็นส่วนตัวเพื่อดูรายละเอียด" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index d30a25ad5..558f26911 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -761,5 +761,6 @@ "receivable_balance": "Natatanggap na balanse", "confirmed_tx": "Nakumpirma", "transaction_details_source_address": "SOURCE ADDRESS", - "pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet." + "pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.", + "camera_consent": "Gagamitin ang iyong camera upang kumuha ng larawan para sa mga layunin ng pagkakakilanlan sa pamamagitan ng ${provider}. Pakisuri ang kanilang Patakaran sa Privacy para sa mga detalye." } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 563ca5332..bbbecaf5c 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -765,5 +765,6 @@ "receivable_balance": "Alacak bakiyesi", "confirmed_tx": "Onaylanmış", "transaction_details_source_address": "Kaynak adresi", - "pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı." + "pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı.", + "camera_consent": "Kameranız ${provider} tarihine kadar tanımlama amacıyla bir görüntü yakalamak için kullanılacaktır. Ayrıntılar için lütfen Gizlilik Politikalarını kontrol edin." } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 9418d6065..a1d08b352 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -767,5 +767,6 @@ "receivable_balance": "Баланс дебіторської заборгованості", "confirmed_tx": "Підтверджений", "transaction_details_source_address": "Адреса джерела", - "pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено." + "pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено.", + "camera_consent": "Ваша камера використовуватиметься для зйомки зображення з метою ідентифікації ${provider}. Будь ласка, ознайомтеся з їхньою політикою конфіденційності, щоб дізнатися більше." } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 61e97d751..517aa9ded 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -759,5 +759,6 @@ "receivable_balance": "قابل وصول توازن", "confirmed_tx": "تصدیق", "transaction_details_source_address": "ماخذ ایڈریس", - "pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ" + "pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ", + "camera_consent": "۔ﮟﯿﮭﮑﯾﺩ ﯽﺴﯿﻟﺎﭘ ﯽﺴﯾﻮﯿﺋﺍﺮﭘ ﯽﮐ ﻥﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ﮯﯿﻟ ﮯﮐ ﺕﻼ${provider}ﯿﺼﻔﺗ ۔ﺎﮔ ﮯﺋﺎﺟ ﺎﯿﮐ ﻝﺎﻤﻌﺘﺳﺍ ﮯﯿﻟ" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index dbff620a1..298c73c67 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -761,5 +761,6 @@ "receivable_balance": "Iwontunws.funfun ti o gba", "confirmed_tx": "Jẹrisi", "transaction_details_source_address": "Adirẹsi orisun", - "pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ." + "pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ.", + "camera_consent": "Kamẹra rẹ yoo ṣee lo lati ya aworan kan fun awọn idi idanimọ nipasẹ ${provider}. Jọwọ ṣayẹwo Ilana Aṣiri wọn fun awọn alaye." } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 249a412c4..20255e76c 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -766,5 +766,6 @@ "receivable_balance": "应收余额", "confirmed_tx": "确认的", "transaction_details_source_address": "源地址", - "pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。" + "pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。", + "camera_consent": "${provider} 将使用您的相机拍摄图像以供识别之用。请查看他们的隐私政策了解详情。" } From fc352a6da3c09d179de41cf967de5258b84a857e Mon Sep 17 00:00:00 2001 From: Serhii Date: Sat, 27 Jan 2024 04:34:38 +0200 Subject: [PATCH 7/8] Cw 428 send to nostr addresses (#1271) * add nostr address resolver * Add Nostr localization --------- Co-authored-by: OmarHatem --- lib/entities/parse_address_from_domain.dart | 28 +++- lib/entities/parsed_address.dart | 27 +++- lib/nostr/nostr_api.dart | 142 ++++++++++++++++++ lib/nostr/nostr_user.dart | 35 +++++ lib/src/screens/exchange/exchange_page.dart | 2 +- .../widgets/extract_address_from_parsed.dart | 5 + lib/view_model/send/output.dart | 2 +- pubspec_base.yaml | 6 + res/values/strings_ar.arb | 6 +- res/values/strings_bg.arb | 6 +- res/values/strings_cs.arb | 6 +- res/values/strings_de.arb | 6 +- res/values/strings_en.arb | 6 +- res/values/strings_es.arb | 6 +- res/values/strings_fr.arb | 6 +- res/values/strings_ha.arb | 6 +- res/values/strings_hi.arb | 6 +- res/values/strings_hr.arb | 6 +- res/values/strings_id.arb | 6 +- res/values/strings_it.arb | 6 +- res/values/strings_ja.arb | 6 +- res/values/strings_ko.arb | 6 +- res/values/strings_my.arb | 6 +- res/values/strings_nl.arb | 6 +- res/values/strings_pl.arb | 6 +- res/values/strings_pt.arb | 6 +- res/values/strings_ru.arb | 6 +- res/values/strings_th.arb | 6 +- res/values/strings_tl.arb | 6 +- res/values/strings_tr.arb | 6 +- res/values/strings_uk.arb | 6 +- res/values/strings_ur.arb | 6 +- res/values/strings_yo.arb | 6 +- res/values/strings_zh.arb | 6 +- 34 files changed, 370 insertions(+), 33 deletions(-) create mode 100644 lib/nostr/nostr_api.dart create mode 100644 lib/nostr/nostr_user.dart diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index e184f5649..52bcc495b 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -6,12 +6,14 @@ 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:cake_wallet/mastodon/mastodon_api.dart'; +import 'package:cake_wallet/nostr/nostr_api.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/twitter/twitter_api.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/entities/fio_address_provider.dart'; +import 'package:flutter/cupertino.dart'; class AddressResolver { AddressResolver({required this.yatService, required this.wallet, required this.settingsStore}) @@ -58,7 +60,16 @@ class AddressResolver { }); } - Future resolve(String text, String ticker) async { + bool isEmailFormat(String address) { + final RegExp emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + caseSensitive: false, + ); + return emailRegex.hasMatch(address); + } + + + Future resolve(BuildContext context, String text, String ticker) async { try { if (text.startsWith('@') && !text.substring(1).contains('@')) { if(settingsStore.lookupsTwitter) { @@ -165,6 +176,21 @@ class AddressResolver { } } } + if (isEmailFormat(text)) { + final nostrProfile = await NostrProfileHandler.queryProfile(context, text); + if (nostrProfile?.relays != null) { + final nostrUserData = + await NostrProfileHandler.processRelays(context, nostrProfile!, text); + + if (nostrUserData != null) { + String? addressFromBio = extractAddressByType( + raw: nostrUserData.about, type: CryptoCurrency.fromString(ticker)); + if (addressFromBio != null) { + return ParsedAddress.nostrAddress(address: addressFromBio, name: text); + } + } + } + } } catch (e) { print(e.toString()); } diff --git a/lib/entities/parsed_address.dart b/lib/entities/parsed_address.dart index df20dd9ee..d414a827d 100644 --- a/lib/entities/parsed_address.dart +++ b/lib/entities/parsed_address.dart @@ -2,7 +2,18 @@ import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/yat_record.dart'; -enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter, ens, contact, mastodon } +enum ParseFrom { + unstoppableDomains, + openAlias, + yatRecord, + fio, + notParsed, + twitter, + ens, + contact, + mastodon, + nostr +} class ParsedAddress { ParsedAddress({ @@ -11,9 +22,9 @@ class ParsedAddress { this.description = '', this.parseFrom = ParseFrom.notParsed, }); - + factory ParsedAddress.fetchEmojiAddress({ - List? addresses, + List? addresses, required String name, }){ if (addresses?.isEmpty ?? true) { @@ -28,7 +39,7 @@ class ParsedAddress { } factory ParsedAddress.fetchUnstoppableDomainAddress({ - String? address, + String? address, required String name, }){ if (address?.isEmpty ?? true) { @@ -94,6 +105,14 @@ class ParsedAddress { ); } + factory ParsedAddress.nostrAddress({required String address, required String name}) { + return ParsedAddress( + addresses: [address], + name: name, + parseFrom: ParseFrom.nostr, + ); + } + final List addresses; final String name; final String description; diff --git a/lib/nostr/nostr_api.dart b/lib/nostr/nostr_api.dart new file mode 100644 index 000000000..7c0eea5ef --- /dev/null +++ b/lib/nostr/nostr_api.dart @@ -0,0 +1,142 @@ +import 'dart:convert'; + +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/nostr/nostr_user.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:flutter/material.dart'; +import 'package:nostr_tools/nostr_tools.dart'; + +class NostrProfileHandler { + static final relayToDomainMap = { + 'relay.snort.social': 'snort.social', + }; + + static Nip05 _nip05 = Nip05(); + + static Future queryProfile(BuildContext context, String nip05Address) async { + var profile = await _nip05.queryProfile(nip05Address); + if (profile?.pubkey != null) { + if (profile?.relays?.isNotEmpty == true) { + return profile; + } else { + await _showErrorDialog(context, S.of(context).no_relays, S.of(context).no_relays_message); + } + } + return null; + } + + static Future processRelays( + BuildContext context, ProfilePointer profile, String nip05Address) async { + String userDomain = _extractDomain(nip05Address); + const int metaData = 0; + + for (String relayUrl in profile.relays ?? []) { + final relayDomain = _getDomainFromRelayUrl(relayUrl); + final formattedRelayDomain = relayToDomainMap[relayDomain] ?? relayDomain; + if (formattedRelayDomain == userDomain) { + final userDomainData = await _fetchInfoFromRelay(relayUrl, profile.pubkey, [metaData]); + if (userDomainData != null) { + return userDomainData; + } + } + } + await _showErrorDialog(context, S.of(context).no_relays, S.of(context).no_relay_on_domain); + + String? chosenRelayUrl = await _showRelayChoiceDialog(context, profile.relays ?? []); + if (chosenRelayUrl != null) { + final userData = await _fetchInfoFromRelay(chosenRelayUrl, profile.pubkey, [metaData]); + if (userData != null) { + return userData; + } + } + + return null; + } + + static Future _fetchInfoFromRelay( + String relayUrl, String userPubKey, List kinds) async { + try { + final relay = RelayApi(relayUrl: relayUrl); + final stream = await relay.connect(); + + relay.sub([ + Filter( + kinds: kinds, + authors: [userPubKey], + ) + ]); + + await for (var message in stream) { + if (message.type == 'EVENT') { + final event = message.message as Event; + + final eventContent = json.decode(event.content) as Map; + + final userMetadata = UserMetadata.fromJson(eventContent); + relay.close(); + return userMetadata; + } + } + + relay.close(); + return null; + } catch (e) { + print('[!] Error with relay $relayUrl: $e'); + return null; + } + } + + static Future _showErrorDialog( + BuildContext context, String title, String errorMessage) async { + if (context.mounted) { + await showPopUp( + context: context, + builder: (BuildContext dialogContext) { + return AlertWithOneAction( + alertTitle: title, + alertContent: errorMessage, + buttonText: S.of(dialogContext).ok, + buttonAction: () => Navigator.of(dialogContext).pop(), + ); + }, + ); + } + } + + static String _extractDomain(String nip05Address) { + var parts = nip05Address.split('@'); + return parts.length == 2 ? parts[1] : ''; + } + + static String _getDomainFromRelayUrl(String relayUrl) { + try { + var uri = Uri.parse(relayUrl); + return uri.host; + } catch (e) { + print('Error parsing URL: $e'); + return ''; + } + } + + static Future _showRelayChoiceDialog(BuildContext context, List relays) async { + String? selectedRelay; + + if (context.mounted) { + await showPopUp( + context: context, + builder: (BuildContext dialogContext) { + return Picker( + selectedAtIndex: 0, + title: S.of(dialogContext).choose_relay, + items: relays, + onItemSelected: (String relay) => selectedRelay = relay, + ); + }, + ); + } + + return selectedRelay; + } +} diff --git a/lib/nostr/nostr_user.dart b/lib/nostr/nostr_user.dart new file mode 100644 index 000000000..4b1694a11 --- /dev/null +++ b/lib/nostr/nostr_user.dart @@ -0,0 +1,35 @@ + +class UserMetadata { + final String name; + final String lnurl; + final String email; + final String picture; + final String about; + final String nip05; + final String banner; + final String website; + + UserMetadata({ + required this.name, + required this.lnurl, + required this.email, + required this.picture, + required this.about, + required this.nip05, + required this.banner, + required this.website, + }); + + factory UserMetadata.fromJson(Map json) { + return UserMetadata( + name: json['name'] as String? ?? '', + lnurl: json['lud06'] as String? ?? '', + email: json['lud16'] as String? ?? '', + picture: json['picture'] as String? ?? '', + about: json['about'] as String? ?? '', + nip05: json['nip05'] as String? ?? '', + banner: json['banner'] as String? ?? '', + website: json['website'] as String? ?? '', + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index b3fbd19a7..94b51301c 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -557,7 +557,7 @@ class ExchangePage extends BasePage { } Future fetchParsedAddress(BuildContext context, String domain, String ticker) async { - final parsedAddress = await getIt.get().resolve(domain, ticker); + final parsedAddress = await getIt.get().resolve(context, domain, ticker); final address = await extractAddressFromParsed(context, parsedAddress); return address; } 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 73bff23c1..bb09d4ca3 100644 --- a/lib/src/screens/send/widgets/extract_address_from_parsed.dart +++ b/lib/src/screens/send/widgets/extract_address_from_parsed.dart @@ -43,6 +43,11 @@ Future extractAddressFromParsed( content = S.of(context).extracted_address_content('${parsedAddress.name} (Mastodon)'); address = parsedAddress.addresses.first; break; + case ParseFrom.nostr: + title = S.of(context).address_detected; + content = S.of(context).extracted_address_content('${parsedAddress.name} (Nostr NIP-05)'); + address = parsedAddress.addresses.first; + break; case ParseFrom.yatRecord: if (parsedAddress.name.isEmpty) { title = S.of(context).yat_error; diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 73fb535f2..e40000139 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -262,7 +262,7 @@ abstract class OutputBase with Store { Future fetchParsedAddress(BuildContext context) async { final domain = address; final ticker = cryptoCurrencyHandler().title.toLowerCase(); - parsedAddress = await getIt.get().resolve(domain, ticker); + parsedAddress = await getIt.get().resolve(context, domain, ticker); extractedAddress = await extractAddressFromParsed(context, parsedAddress); note = parsedAddress.description; } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 45f675043..f38482ec3 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -105,6 +105,7 @@ dependencies: socks5_proxy: ^1.0.4 flutter_svg: ^2.0.9 polyseed: ^0.0.2 + nostr_tools: ^1.0.9 dev_dependencies: flutter_test: @@ -123,6 +124,11 @@ dev_dependencies: url: https://github.com/cake-tech/google-translator.git version: 1.0.0 +dependency_overrides: + bech32: + git: + url: https://github.com/cake-tech/bech32.git + flutter_icons: image_path: "assets/images/app_logo.png" android: true diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 8fbcb481c..f8ff07bd2 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -764,5 +764,9 @@ "confirmed_tx": "مؤكد", "transaction_details_source_address": "عنوان المصدر", "pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ", - "camera_consent": ".ﻞﻴﺻﺎﻔﺘﻟﺍ ﻰﻠﻋ ﻝﻮﺼﺤﻠﻟ ﻢﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺔﻴﺻﻮﺼﺨﻟﺍ ﺔﺳﺎﻴﺳ ﻦﻣ ﻖﻘﺤﺘﻟﺍ ﻰﺟﺮﻳ .${provider} ﻝﻮﻠ" + "camera_consent": ".ﻞﻴﺻﺎﻔﺘﻟﺍ ﻰﻠﻋ ﻝﻮﺼﺤﻠﻟ ﻢﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺔﻴﺻﻮﺼﺨﻟﺍ ﺔﺳﺎﻴﺳ ﻦﻣ ﻖﻘﺤﺘﻟﺍ ﻰﺟﺮﻳ .${provider} ﻝﻮﻠ", + "no_relays": " ﺕﻼﺣﺮﻤﻟﺍ ﻻ", + "choose_relay": " ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ", + "no_relays_message": ".ﻪﺑ ﺹﺎﺨﻟﺍ Nostr ﻞﺠﺳ ﻰﻟﺇ ﺕﻼﺣﺮﻤﻟﺍ ﺔﻓﺎﺿﻹ ﻢﻠﺘﺴﻤﻟﺍ ﺩﺎﺷﺭﺇ ﻰﺟﺮﻳ .ﺕﻼﺣﺮﻣ ﻱﺃ ﻰﻠﻋ ﻱﻮﺘﺤﻳ ﻻ", + "no_relay_on_domain": ".ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ .ﺡﺎﺘﻣ ﺮﻴﻏ ﻞﻴﺣﺮﺘﻟﺍ ﻥﺃ ﻭﺃ ﻡﺪﺨﺘﺴﻤﻟﺍ ﻝﺎﺠﻤﻟ ﻞﻴﺣﺮﺗ ﺪ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 1707c3f8c..8e6a6c1ee 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -760,5 +760,9 @@ "confirmed_tx": "Потвърдено", "transaction_details_source_address": "Адрес на източника", "pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.", - "camera_consent": "Вашият фотоапарат ще бъде използван за заснемане на изображение с цел идентификация от ${provider}. Моля, проверете тяхната политика за поверителност за подробности." + "camera_consent": "Вашият фотоапарат ще бъде използван за заснемане на изображение с цел идентификация от ${provider}. Моля, проверете тяхната политика за поверителност за подробности.", + "no_relays": "Без релета", + "choose_relay": "Моля, изберете реле, което да използвате", + "no_relays_message": "Намерихме запис Nostr NIP-05 за този потребител, но той не съдържа релета. Моля, инструктирайте получателя да добави релета към своя Nostr запис.", + "no_relay_on_domain": "Няма реле за домейна на потребителя или релето не е налично. Моля, изберете реле, което да използвате." } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index dfae6717c..a739fa05b 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -760,5 +760,9 @@ "confirmed_tx": "Potvrzeno", "transaction_details_source_address": "Zdrojová adresa", "pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.", - "camera_consent": "Váš fotoaparát použije k pořízení snímku pro účely identifikace ${provider}. Podrobnosti najdete v jejich Zásadách ochrany osobních údajů." + "camera_consent": "Váš fotoaparát použije k pořízení snímku pro účely identifikace ${provider}. Podrobnosti najdete v jejich Zásadách ochrany osobních údajů.", + "no_relays": "Žádná relé", + "choose_relay": "Vyberte relé, které chcete použít", + "no_relays_message": "Pro tohoto uživatele jsme našli záznam Nostr NIP-05, který však neobsahuje žádná relé. Požádejte příjemce, aby přidal přenosy do svého záznamu Nostr.", + "no_relay_on_domain": "Pro doménu uživatele neexistuje přenos nebo je přenos nedostupný. Vyberte relé, které chcete použít." } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index b96861f4d..872b8bec2 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -768,5 +768,9 @@ "confirmed_tx": "Bestätigt", "transaction_details_source_address": "Quelladresse", "pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.", - "camera_consent": "Mit Ihrer Kamera wird bis zum ${provider} ein Bild zur Identifizierung aufgenommen. Weitere Informationen finden Sie in deren Datenschutzbestimmungen." + "camera_consent": "Mit Ihrer Kamera wird bis zum ${provider} ein Bild zur Identifizierung aufgenommen. Weitere Informationen finden Sie in deren Datenschutzbestimmungen.", + "no_relays": "Keine Relais", + "choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus", + "no_relays_message": "Wir haben einen Nostr NIP-05-Eintrag für diesen Benutzer gefunden, der jedoch keine Relays enthält. Bitte weisen Sie den Empfänger an, Relays zu seinem Nostr-Datensatz hinzuzufügen.", + "no_relay_on_domain": "Es gibt kein Relay für die Domäne des Benutzers oder das Relay ist nicht verfügbar. Bitte wählen Sie ein zu verwendendes Relais aus." } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 10c130a0c..3ec496b6c 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -769,5 +769,9 @@ "confirmed_tx": "Confirmed", "transaction_details_source_address": "Source address", "pause_wallet_creation": "Ability to create Haven Wallet is currently paused.", - "camera_consent": "Your camera will be used to capture an image for identification purposes by ${provider}. Please check their Privacy Policy for details." + "camera_consent": "Your camera will be used to capture an image for identification purposes by ${provider}. Please check their Privacy Policy for details.", + "no_relays": "No relays", + "choose_relay": "Please choose a relay to use", + "no_relays_message": "We found a Nostr NIP-05 record for this user, but it does not contain any relays. Please instruct the recipient to add relays to their Nostr record.", + "no_relay_on_domain": "There isn't a relay for user's domain or the relay is unavailable. Please choose a relay to use." } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 5bec3b0ae..ab97e764d 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -768,5 +768,9 @@ "confirmed_tx": "Confirmado", "transaction_details_source_address": "Dirección de la fuente", "pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.", - "camera_consent": "Su cámara será utilizada para capturar una imagen con fines de identificación por ${provider}. Consulte su Política de privacidad para obtener más detalles." + "camera_consent": "Su cámara será utilizada para capturar una imagen con fines de identificación por ${provider}. Consulte su Política de privacidad para obtener más detalles.", + "no_relays": "Sin relevos", + "choose_relay": "Por favor elija un relé para usar", + "no_relays_message": "Encontramos un registro Nostr NIP-05 para este usuario, pero no contiene ningún relé. Indique al destinatario que agregue retransmisiones a su registro Nostr.", + "no_relay_on_domain": "No hay una retransmisión para el dominio del usuario o la retransmisión no está disponible. Elija un relé para usar." } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 9ce607b20..619c35276 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -768,5 +768,9 @@ "confirmed_tx": "Confirmé", "transaction_details_source_address": "Adresse source", "pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.", - "camera_consent": "Votre appareil photo sera utilisé pour capturer une image à des fins d'identification par ${provider}. Veuillez consulter leur politique de confidentialité pour plus de détails." + "camera_consent": "Votre appareil photo sera utilisé pour capturer une image à des fins d'identification par ${provider}. Veuillez consulter leur politique de confidentialité pour plus de détails.", + "no_relays": "Pas de relais", + "choose_relay": "Veuillez choisir un relais à utiliser", + "no_relays_message": "Nous avons trouvé un enregistrement Nostr NIP-05 pour cet utilisateur, mais il ne contient aucun relais. Veuillez demander au destinataire d'ajouter des relais à son enregistrement Nostr.", + "no_relay_on_domain": "Il n'existe pas de relais pour le domaine de l'utilisateur ou le relais n'est pas disponible. Veuillez choisir un relais à utiliser." } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 47426aa8c..dd38a4ec3 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -750,5 +750,9 @@ "confirmed_tx": "Tabbatar", "transaction_details_source_address": "Adireshin Incord", "pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.", - "camera_consent": "Za a yi amfani da kyamarar ku don ɗaukar hoto don dalilai na tantancewa ta ${provider}. Da fatan za a duba Manufar Sirri don cikakkun bayanai." + "camera_consent": "Za a yi amfani da kyamarar ku don ɗaukar hoto don dalilai na tantancewa ta ${provider}. Da fatan za a duba Manufar Sirri don cikakkun bayanai.", + "no_relays": "Babu relays", + "choose_relay": "Da fatan za a zaɓi gudun ba da sanda don amfani", + "no_relays_message": "Mun sami rikodin Nostr NIP-05 don wannan mai amfani, amma ba ya ƙunshe da kowane relays. Da fatan za a umurci mai karɓa ya ƙara relays zuwa rikodin su na Nostr.", + "no_relay_on_domain": "Babu gudun ba da sanda ga yankin mai amfani ko kuma ba a samu ba. Da fatan za a zaɓi gudun ba da sanda don amfani." } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 91bf40d60..b37aa7c12 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -768,5 +768,9 @@ "confirmed_tx": "की पुष्टि", "transaction_details_source_address": "स्रोत पता", "pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।", - "camera_consent": "आपके कैमरे का उपयोग ${provider} द्वारा पहचान उद्देश्यों के लिए एक छवि कैप्चर करने के लिए किया जाएगा। विवरण के लिए कृपया उनकी गोपनीयता नीति जांचें।" + "camera_consent": "आपके कैमरे का उपयोग ${provider} द्वारा पहचान उद्देश्यों के लिए एक छवि कैप्चर करने के लिए किया जाएगा। विवरण के लिए कृपया उनकी गोपनीयता नीति जांचें।", + "no_relays": "कोई रिले नहीं", + "choose_relay": "कृपया उपयोग करने के लिए एक रिले चुनें", + "no_relays_message": "हमें इस उपयोगकर्ता के लिए एक Nostr NIP-05 रिकॉर्ड मिला, लेकिन इसमें कोई रिले नहीं है। कृपया प्राप्तकर्ता को अपने नॉस्ट्र रिकॉर्ड में रिले जोड़ने का निर्देश दें।", + "no_relay_on_domain": "उपयोगकर्ता के डोमेन के लिए कोई रिले नहीं है या रिले अनुपलब्ध है। कृपया उपयोग करने के लिए एक रिले चुनें।" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index b3c49ce78..c1ba66bb2 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -766,5 +766,9 @@ "confirmed_tx": "Potvrđen", "transaction_details_source_address": "Adresa izvora", "pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.", - "camera_consent": "Vaš će fotoaparat koristiti za snimanje slike u svrhu identifikacije od strane ${provider}. Pojedinosti potražite u njihovoj politici privatnosti." + "camera_consent": "Vaš će fotoaparat koristiti za snimanje slike u svrhu identifikacije od strane ${provider}. Pojedinosti potražite u njihovoj politici privatnosti.", + "no_relays": "Nema releja", + "choose_relay": "Odaberite relej za korištenje", + "no_relays_message": "Pronašli smo zapis Nostr NIP-05 za ovog korisnika, ali on ne sadrži nikakve releje. Uputite primatelja da doda releje u svoj Nostr zapis.", + "no_relay_on_domain": "Ne postoji relej za korisničku domenu ili je relej nedostupan. Odaberite relej za korištenje." } diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 2a8fd06e8..b736f3977 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -756,5 +756,9 @@ "confirmed_tx": "Dikonfirmasi", "transaction_details_source_address": "Alamat sumber", "pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.", - "camera_consent": "Kamera Anda akan digunakan untuk mengambil gambar untuk tujuan identifikasi oleh ${provider}. Silakan periksa Kebijakan Privasi mereka untuk detailnya." + "camera_consent": "Kamera Anda akan digunakan untuk mengambil gambar untuk tujuan identifikasi oleh ${provider}. Silakan periksa Kebijakan Privasi mereka untuk detailnya.", + "no_relays": "Tidak ada relay", + "choose_relay": "Silakan pilih relai yang akan digunakan", + "no_relays_message": "Kami menemukan catatan Nostr NIP-05 untuk pengguna ini, tetapi tidak berisi relay apa pun. Harap instruksikan penerima untuk menambahkan relay ke catatan Nostr mereka.", + "no_relay_on_domain": "Tidak ada relai untuk domain pengguna atau relai tidak tersedia. Silakan pilih relai yang akan digunakan." } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 01e88d56f..adcaa3204 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -768,5 +768,9 @@ "confirmed_tx": "Confermato", "transaction_details_source_address": "Indirizzo di partenza", "pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa.", - "camera_consent": "La tua fotocamera verrà utilizzata per acquisire un'immagine a scopo identificativo da ${provider}. Si prega di controllare la loro Informativa sulla privacy per i dettagli." + "camera_consent": "La tua fotocamera verrà utilizzata per acquisire un'immagine a scopo identificativo da ${provider}. Si prega di controllare la loro Informativa sulla privacy per i dettagli.", + "no_relays": "Nessun relè", + "choose_relay": "Scegli un relè da utilizzare", + "no_relays_message": "Abbiamo trovato un record Nostr NIP-05 per questo utente, ma non contiene alcun relè. Si prega di indicare al destinatario di aggiungere inoltri al proprio record Nostr.", + "no_relay_on_domain": "Non esiste un inoltro per il dominio dell'utente oppure l'inoltro non è disponibile. Scegli un relè da utilizzare." } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 07bf5fca8..9dabebc45 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -768,5 +768,9 @@ "confirmed_tx": "確認済み", "transaction_details_source_address": "ソースアドレス", "pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。", - "camera_consent": "あなたのカメラは、${provider}_ までに識別目的で画像を撮影するために使用されます。詳細については、プライバシー ポリシーをご確認ください。" + "camera_consent": "あなたのカメラは、${provider}_ までに識別目的で画像を撮影するために使用されます。詳細については、プライバシー ポリシーをご確認ください。", + "no_relays": "リレーなし", + "choose_relay": "使用するリレーを選択してください", + "no_relays_message": "このユーザーの Nostr NIP-05 レコードが見つかりましたが、リレーは含まれていません。受信者に Nostr レコードにリレーを追加するよう指示してください。", + "no_relay_on_domain": "ユーザーのドメインのリレーが存在しないか、リレーが使用できません。使用するリレーを選択してください。" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 7ea1ea85d..ee41a369b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -766,5 +766,9 @@ "confirmed_tx": "확인", "transaction_details_source_address": "소스 주소", "pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다.", - "camera_consent": "귀하의 카메라는 ${provider}의 식별 목적으로 이미지를 캡처하는 데 사용됩니다. 자세한 내용은 해당 개인정보 보호정책을 확인하세요." + "camera_consent": "귀하의 카메라는 ${provider}의 식별 목적으로 이미지를 캡처하는 데 사용됩니다. 자세한 내용은 해당 개인정보 보호정책을 확인하세요.", + "no_relays": "릴레이 없음", + "choose_relay": "사용할 릴레이를 선택해주세요", + "no_relays_message": "이 사용자에 대한 Nostr NIP-05 레코드를 찾았지만 릴레이가 포함되어 있지 않습니다. 수신자에게 Nostr 기록에 릴레이를 추가하도록 지시하십시오.", + "no_relay_on_domain": "사용자 도메인에 릴레이가 없거나 릴레이를 사용할 수 없습니다. 사용할 릴레이를 선택해주세요." } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 9559d224e..199d72660 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -766,5 +766,9 @@ "confirmed_tx": "အတည်ပြုသည်", "transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ", "pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။", - "camera_consent": "မှတ်ပုံတင်ခြင်းရည်ရွယ်ချက်များအတွက် ${provider} တွင် သင့်ကင်မရာကို အသုံးပြုပါမည်။ အသေးစိတ်အတွက် ၎င်းတို့၏ ကိုယ်ရေးကိုယ်တာမူဝါဒကို စစ်ဆေးပါ။" + "camera_consent": "မှတ်ပုံတင်ခြင်းရည်ရွယ်ချက်များအတွက် ${provider} တွင် သင့်ကင်မရာကို အသုံးပြုပါမည်။ အသေးစိတ်အတွက် ၎င်းတို့၏ ကိုယ်ရေးကိုယ်တာမူဝါဒကို စစ်ဆေးပါ။", + "no_relays": "Relay မရှိပါ။", + "choose_relay": "အသုံးပြုရန် relay ကိုရွေးချယ်ပါ။", + "no_relays_message": "ဤအသုံးပြုသူအတွက် Nostr NIP-05 မှတ်တမ်းကို ကျွန်ုပ်တို့တွေ့ရှိသော်လည်း ၎င်းတွင် မည်သည့် relays မှ မပါဝင်ပါ။ ကျေးဇူးပြု၍ လက်ခံသူကို ၎င်းတို့၏ Nostr မှတ်တမ်းတွင် ထပ်လောင်းထည့်ရန် ညွှန်ကြားပါ။", + "no_relay_on_domain": "အသုံးပြုသူ၏ဒိုမိန်းအတွက် ထပ်ဆင့်လွှင့်ခြင်း မရှိပါ သို့မဟုတ် ထပ်ဆင့်လွှင့်ခြင်း မရနိုင်ပါ။ အသုံးပြုရန် relay ကိုရွေးချယ်ပါ။" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 23a635a99..9f27236aa 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -768,5 +768,9 @@ "confirmed_tx": "Bevestigd", "transaction_details_source_address": "Bron adres", "pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.", - "camera_consent": "Uw camera wordt gebruikt om vóór ${provider} een beeld vast te leggen voor identificatiedoeleinden. Raadpleeg hun privacybeleid voor meer informatie." + "camera_consent": "Uw camera wordt gebruikt om vóór ${provider} een beeld vast te leggen voor identificatiedoeleinden. Raadpleeg hun privacybeleid voor meer informatie.", + "no_relays": "Geen relais", + "choose_relay": "Kies een relais dat u wilt gebruiken", + "no_relays_message": "We hebben een Nostr NIP-05-record voor deze gebruiker gevonden, maar deze bevat geen relays. Instrueer de ontvanger om relays toe te voegen aan zijn Nostr-record.", + "no_relay_on_domain": "Er is geen relay voor het domein van de gebruiker of de relay is niet beschikbaar. Kies een relais dat u wilt gebruiken." } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index f3e9db35c..c6e4806b1 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -768,5 +768,9 @@ "confirmed_tx": "Potwierdzony", "transaction_details_source_address": "Adres źródłowy", "pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.", - "camera_consent": "Twój aparat zostanie użyty do przechwycenia obrazu w celach identyfikacyjnych przez ${provider}. Aby uzyskać szczegółowe informacje, sprawdź ich Politykę prywatności." + "camera_consent": "Twój aparat zostanie użyty do przechwycenia obrazu w celach identyfikacyjnych przez ${provider}. Aby uzyskać szczegółowe informacje, sprawdź ich Politykę prywatności.", + "no_relays": "Żadnych przekaźników", + "choose_relay": "Wybierz przekaźnik, którego chcesz użyć", + "no_relays_message": "Znaleźliśmy rekord Nostr NIP-05 dla tego użytkownika, ale nie zawiera on żadnych przekaźników. Poinstruuj odbiorcę, aby dodał przekaźniki do swojego rekordu Nostr.", + "no_relay_on_domain": "Brak przekaźnika dla domeny użytkownika lub przekaźnik jest niedostępny. Wybierz przekaźnik, którego chcesz użyć." } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 42c4cec28..b9694d223 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -767,5 +767,9 @@ "confirmed_tx": "Confirmado", "transaction_details_source_address": "Endereço de Origem", "pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.", - "camera_consent": "Sua câmera será usada para capturar uma imagem para fins de identificação por ${provider}. Por favor, verifique a Política de Privacidade para obter detalhes." + "camera_consent": "Sua câmera será usada para capturar uma imagem para fins de identificação por ${provider}. Por favor, verifique a Política de Privacidade para obter detalhes.", + "no_relays": "Sem relés", + "choose_relay": "Escolha um relé para usar", + "no_relays_message": "Encontramos um registro Nostr NIP-05 para este usuário, mas ele não contém nenhum relé. Instrua o destinatário a adicionar retransmissões ao seu registro Nostr.", + "no_relay_on_domain": "Não há uma retransmissão para o domínio do usuário ou a retransmissão está indisponível. Escolha um relé para usar." } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index edc6993a0..3e5d2cec2 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -768,5 +768,9 @@ "confirmed_tx": "Подтвержденный", "transaction_details_source_address": "Адрес источника", "pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.", - "camera_consent": "Ваша камера будет использоваться для захвата изображения в целях идентификации ${provider}. Пожалуйста, ознакомьтесь с их Политикой конфиденциальности для получения подробной информации." + "camera_consent": "Ваша камера будет использоваться для захвата изображения в целях идентификации ${provider}. Пожалуйста, ознакомьтесь с их Политикой конфиденциальности для получения подробной информации.", + "no_relays": "Нет реле", + "choose_relay": "Пожалуйста, выберите реле для использования", + "no_relays_message": "Мы нашли запись Nostr NIP-05 для этого пользователя, но она не содержит никаких реле. Попросите получателя добавить реле в свою запись Nostr.", + "no_relay_on_domain": "Для домена пользователя реле не существует или реле недоступно. Пожалуйста, выберите реле для использования." } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 80fa3f9c6..a9fe67959 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -766,5 +766,9 @@ "confirmed_tx": "ซึ่งยืนยันแล้ว", "transaction_details_source_address": "ที่อยู่แหล่งกำเนิด", "pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว", - "camera_consent": "กล้องของคุณจะถูกนำมาใช้เพื่อจับภาพเพื่อวัตถุประสงค์ในการระบุตัวตนภายใน ${provider} โปรดตรวจสอบนโยบายความเป็นส่วนตัวเพื่อดูรายละเอียด" + "camera_consent": "กล้องของคุณจะถูกนำมาใช้เพื่อจับภาพเพื่อวัตถุประสงค์ในการระบุตัวตนภายใน ${provider} โปรดตรวจสอบนโยบายความเป็นส่วนตัวเพื่อดูรายละเอียด", + "no_relays": "ไม่มีรีเลย์", + "choose_relay": "กรุณาเลือกรีเลย์ที่จะใช้", + "no_relays_message": "เราพบบันทึก Nostr NIP-05 สำหรับผู้ใช้รายนี้ แต่ไม่มีรีเลย์ใดๆ โปรดแนะนำให้ผู้รับเพิ่มรีเลย์ลงในบันทึก Nostr ของตน", + "no_relay_on_domain": "ไม่มีการส่งต่อสำหรับโดเมนของผู้ใช้ หรือการส่งต่อไม่พร้อมใช้งาน กรุณาเลือกรีเลย์ที่จะใช้" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 558f26911..3d05cc700 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -762,5 +762,9 @@ "confirmed_tx": "Nakumpirma", "transaction_details_source_address": "SOURCE ADDRESS", "pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.", - "camera_consent": "Gagamitin ang iyong camera upang kumuha ng larawan para sa mga layunin ng pagkakakilanlan sa pamamagitan ng ${provider}. Pakisuri ang kanilang Patakaran sa Privacy para sa mga detalye." + "camera_consent": "Gagamitin ang iyong camera upang kumuha ng larawan para sa mga layunin ng pagkakakilanlan sa pamamagitan ng ${provider}. Pakisuri ang kanilang Patakaran sa Privacy para sa mga detalye.", + "no_relays": "Walang mga relay", + "choose_relay": "Mangyaring pumili ng relay na gagamitin", + "no_relays_message": "Nakakita kami ng Nostr NIP-05 record para sa user na ito, ngunit hindi ito naglalaman ng anumang mga relay. Mangyaring atasan ang tatanggap na magdagdag ng mga relay sa kanilang Nostr record.", + "no_relay_on_domain": "Walang relay para sa domain ng user o hindi available ang relay. Mangyaring pumili ng relay na gagamitin." } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index bbbecaf5c..62243838d 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -766,5 +766,9 @@ "confirmed_tx": "Onaylanmış", "transaction_details_source_address": "Kaynak adresi", "pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı.", - "camera_consent": "Kameranız ${provider} tarihine kadar tanımlama amacıyla bir görüntü yakalamak için kullanılacaktır. Ayrıntılar için lütfen Gizlilik Politikalarını kontrol edin." + "camera_consent": "Kameranız ${provider} tarihine kadar tanımlama amacıyla bir görüntü yakalamak için kullanılacaktır. Ayrıntılar için lütfen Gizlilik Politikalarını kontrol edin.", + "no_relays": "Röle yok", + "choose_relay": "Lütfen kullanmak için bir röle seçin", + "no_relays_message": "Bu kullanıcı için bir Nostr NIP-05 kaydı bulduk ancak bu kayıt herhangi bir aktarma içermiyor. Lütfen alıcıya Nostr kayıtlarına aktarma eklemesi talimatını verin.", + "no_relay_on_domain": "Kullanıcının alanı için bir geçiş yok veya geçiş kullanılamıyor. Lütfen kullanmak için bir röle seçin." } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index a1d08b352..def086b2e 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -768,5 +768,9 @@ "confirmed_tx": "Підтверджений", "transaction_details_source_address": "Адреса джерела", "pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено.", - "camera_consent": "Ваша камера використовуватиметься для зйомки зображення з метою ідентифікації ${provider}. Будь ласка, ознайомтеся з їхньою політикою конфіденційності, щоб дізнатися більше." + "camera_consent": "Ваша камера використовуватиметься для зйомки зображення з метою ідентифікації ${provider}. Будь ласка, ознайомтеся з їхньою політикою конфіденційності, щоб дізнатися більше.", + "no_relays": "Без реле", + "choose_relay": "Будь ласка, виберіть реле для використання", + "no_relays_message": "Ми знайшли запис Nostr NIP-05 для цього користувача, але він не містить жодних реле. Будь ласка, попросіть одержувача додати реле до свого запису Nostr.", + "no_relay_on_domain": "Немає ретранслятора для домену користувача або ретранслятор недоступний. Будь ласка, виберіть реле для використання." } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 517aa9ded..34b1042ac 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -760,5 +760,9 @@ "confirmed_tx": "تصدیق", "transaction_details_source_address": "ماخذ ایڈریس", "pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ", - "camera_consent": "۔ﮟﯿﮭﮑﯾﺩ ﯽﺴﯿﻟﺎﭘ ﯽﺴﯾﻮﯿﺋﺍﺮﭘ ﯽﮐ ﻥﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ﮯﯿﻟ ﮯﮐ ﺕﻼ${provider}ﯿﺼﻔﺗ ۔ﺎﮔ ﮯﺋﺎﺟ ﺎﯿﮐ ﻝﺎﻤﻌﺘﺳﺍ ﮯﯿﻟ" + "camera_consent": "۔ﮟﯿﮭﮑﯾﺩ ﯽﺴﯿﻟﺎﭘ ﯽﺴﯾﻮﯿﺋﺍﺮﭘ ﯽﮐ ﻥﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ﮯﯿﻟ ﮯﮐ ﺕﻼ${provider}ﯿﺼﻔﺗ ۔ﺎﮔ ﮯﺋﺎﺟ ﺎﯿﮐ ﻝﺎﻤﻌﺘﺳﺍ ﮯﯿﻟ", + "no_relays": " ۔ﮟﯿﮩﻧ ﮯﻠﯾﺭ ﯽﺋﻮﮐ", + "choose_relay": " ۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ", + "no_relays_message": "۔ﮟﯾﺮﮐ ﻞﻣﺎﺷ ﮯﻠﯾﺭ ﮟﯿﻣ ﮈﺭﺎﮑﯾﺭ ﺮﭩﺳﻮﻧ ﮯﻨﭘﺍ ﮦﻭ ﮧﮐ ﮟﯾﺩ ﺖﯾﺍﺪﮨ ﻮﮐ ﮦﺪﻨﻨﮐ ﻝﻮﺻﻭ ﻡﺮﮐ ﮦﺍﺮﺑ ۔", + "no_relay_on_domain": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ۔ﮯﮨ ﮟﯿﮩﻧ ﺏﺎﯿﺘﺳﺩ ﮯﻠﯾﺭ ﺎﯾ ﮯﮨ ﮟ" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 298c73c67..12769ec91 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -762,5 +762,9 @@ "confirmed_tx": "Jẹrisi", "transaction_details_source_address": "Adirẹsi orisun", "pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ.", - "camera_consent": "Kamẹra rẹ yoo ṣee lo lati ya aworan kan fun awọn idi idanimọ nipasẹ ${provider}. Jọwọ ṣayẹwo Ilana Aṣiri wọn fun awọn alaye." + "camera_consent": "Kamẹra rẹ yoo ṣee lo lati ya aworan kan fun awọn idi idanimọ nipasẹ ${provider}. Jọwọ ṣayẹwo Ilana Aṣiri wọn fun awọn alaye.", + "no_relays": "Ko si relays", + "choose_relay": "Jọwọ yan yii lati lo", + "no_relays_message": "A ri igbasilẹ Nostr NIP-05 fun olumulo yii, ṣugbọn ko ni eyikeyi awọn iṣipopada ninu. Jọwọ sọ fun olugba lati ṣafikun awọn isunmọ si igbasilẹ Nostr wọn.", + "no_relay_on_domain": "Ko si iṣipopada fun agbegbe olumulo tabi yiyi ko si. Jọwọ yan yii lati lo." } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 20255e76c..cdffd8141 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -767,5 +767,9 @@ "confirmed_tx": "确认的", "transaction_details_source_address": "源地址", "pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。", - "camera_consent": "${provider} 将使用您的相机拍摄图像以供识别之用。请查看他们的隐私政策了解详情。" + "camera_consent": "${provider} 将使用您的相机拍摄图像以供识别之用。请查看他们的隐私政策了解详情。", + "no_relays": "无继电器", + "choose_relay": "请选择要使用的继电器", + "no_relays_message": "我们找到了该用户的 Nostr NIP-05 记录,但它不包含任何中继。请指示收件人将中继添加到他们的 Nostr 记录中。", + "no_relay_on_domain": "用户域没有中继或中继不可用。请选择要使用的继电器。" } From f68987bfb98aee5593321871f6f74427415a63fc Mon Sep 17 00:00:00 2001 From: Adegoke David <64401859+Blazebrain@users.noreply.github.com> Date: Sat, 27 Jan 2024 04:08:53 +0100 Subject: [PATCH 8/8] CW-566-Cake2FA-Bypass-Bug (#1275) * fix: TOTP 2FA Bug * fix: key switch error * Fix 2fa preset issue --------- Co-authored-by: OmarHatem --- lib/store/settings_store.dart | 4 ++-- lib/view_model/set_up_2fa_viewmodel.dart | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index e792ee3b0..0ffedec90 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -997,7 +997,7 @@ abstract class SettingsStoreBase with Store { final totpSecretKey = await SecureKey.getString( secureStorage: secureStorage, sharedPreferences: sharedPreferences, - key: SecureKey.useTOTP2FA, + key: SecureKey.totpSecretKey, ) ?? ''; @@ -1243,7 +1243,7 @@ abstract class SettingsStoreBase with Store { totpSecretKey = await SecureKey.getString( secureStorage: _secureStorage, sharedPreferences: sharedPreferences, - key: SecureKey.useTOTP2FA, + key: SecureKey.totpSecretKey, ) ?? totpSecretKey; diff --git a/lib/view_model/set_up_2fa_viewmodel.dart b/lib/view_model/set_up_2fa_viewmodel.dart index 9587e3075..7347b32a8 100644 --- a/lib/view_model/set_up_2fa_viewmodel.dart +++ b/lib/view_model/set_up_2fa_viewmodel.dart @@ -27,7 +27,9 @@ abstract class Setup2FAViewModelBase with Store { unhighlightTabs = false, selected2FASettings = ObservableList(), state = InitialExecutionState() { - selectCakePreset(selectedCake2FAPreset); + if (selectedCake2FAPreset != Cake2FAPresetsOptions.none) { + selectCakePreset(selectedCake2FAPreset); + } reaction((_) => state, _saveLastAuthTime); }