From ae9efb0ae8ae35e0c33bcfe7ba3473ddc9b4d0ae Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Tue, 20 Apr 2021 20:49:53 +0300 Subject: [PATCH 01/28] CAKE-192 | implemented unstoppable domain address to the app --- android/app/build.gradle | 5 +- .../cakewallet/cake_wallet/MainActivity.java | 46 ++++++++++- android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- ios/Podfile | 3 +- ios/Podfile.lock | 18 ++++- ios/Runner/AppDelegate.swift | 40 +++++++++ lib/entities/unstoppable_domain_address.dart | 22 +++++ lib/src/screens/exchange/exchange_page.dart | 81 +++++++++++++++++-- .../exchange/exchange_template_page.dart | 11 ++- .../exchange/widgets/exchange_card.dart | 10 ++- lib/src/screens/send/send_page.dart | 34 ++++++-- lib/src/screens/send/send_template_page.dart | 4 +- .../unstoppable_domain_address_alert.dart | 17 ++++ lib/src/widgets/address_text_field.dart | 6 +- .../exchange/exchange_view_model.dart | 5 ++ lib/view_model/send/send_view_model.dart | 5 ++ res/values/strings_de.arb | 5 +- res/values/strings_en.arb | 5 +- res/values/strings_es.arb | 5 +- res/values/strings_hi.arb | 5 +- res/values/strings_ja.arb | 5 +- res/values/strings_ko.arb | 5 +- res/values/strings_nl.arb | 5 +- res/values/strings_pl.arb | 5 +- res/values/strings_pt.arb | 5 +- res/values/strings_ru.arb | 5 +- res/values/strings_uk.arb | 5 +- res/values/strings_zh.arb | 5 +- 29 files changed, 327 insertions(+), 46 deletions(-) create mode 100644 lib/entities/unstoppable_domain_address.dart create mode 100644 lib/src/screens/send/widgets/unstoppable_domain_address_alert.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 0c6a46dc4..cef66608c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -81,6 +81,7 @@ flutter { dependencies { testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + implementation 'com.unstoppabledomains:resolution:1.13.0' } diff --git a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java index cf650375f..7efe1606c 100644 --- a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java +++ b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java @@ -5,11 +5,55 @@ import io.flutter.embedding.android.FlutterFragmentActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugins.GeneratedPluginRegistrant; +import com.unstoppabledomains.resolution.DomainResolution; +import com.unstoppabledomains.resolution.Resolution; + +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; public class MainActivity extends FlutterFragmentActivity { - + final String UNSTOPPABLE_DOMAIN_CHANNEL = "com.cakewallet.cake_wallet/unstoppable-domain"; + @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine); + + MethodChannel unstoppableDomainChannel = + new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), + UNSTOPPABLE_DOMAIN_CHANNEL); + + unstoppableDomainChannel.setMethodCallHandler(this::handle); + } + + private void handle(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + try { + if (call.method.equals("getUnstoppableDomainAddress")) { + getUnstoppableDomainAddress(call, result); + } else { + result.notImplemented(); + } + } catch (Exception e) { + result.error("UNCAUGHT_ERROR", e.getMessage(), null); + } + } + + private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + DomainResolution resolution = new Resolution(); + Handler handler = new Handler(Looper.getMainLooper()); + String domain = call.argument("domain"); + String ticker = call.argument("ticker"); + + AsyncTask.execute(() -> { + try { + String address = resolution.getAddress(domain, ticker); + handler.post(() -> result.success(address)); + } catch (Exception e) { + handler.post(() -> result.error("INVALID DOMAIN", e.getMessage(), null)); + } + }); } } diff --git a/android/build.gradle b/android/build.gradle index 0baed694d..fab3c2e17 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.4' + classpath 'com.android.tools.build:gradle:4.1.3' } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 296b146b7..b7ca2e6de 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jun 23 08:50:38 CEST 2017 +#Mon Apr 19 18:19:26 EEST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/ios/Podfile b/ios/Podfile index e26de900c..8248dde48 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '9.0' +platform :ios, '11.0' source 'https://github.com/CocoaPods/Specs.git' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. @@ -36,6 +36,7 @@ target 'Runner' do # Cake Wallet (Legacy) pod 'CryptoSwift' + pod 'UnstoppableDomainsResolution', '~> 0.3.6' end post_install do |installer| diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 03302eab3..f627ed23f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,6 +3,7 @@ PODS: - Flutter - MTBBarcodeScanner - SwiftProtobuf + - BigInt (5.2.0) - connectivity (0.0.1): - Flutter - Reachability @@ -59,6 +60,8 @@ PODS: - SwiftyGif - esys_flutter_share (0.0.1): - Flutter + - EthereumAddress (1.3.0): + - CryptoSwift (~> 1.0) - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter @@ -84,6 +87,10 @@ PODS: - Flutter - SwiftProtobuf (1.12.0) - SwiftyGif (5.3.0) + - UnstoppableDomainsResolution (0.3.6): + - BigInt + - CryptoSwift (~> 1.0) + - EthereumAddress (~> 1.3) - url_launcher (0.0.1): - Flutter - webview_flutter (0.0.1): @@ -105,19 +112,23 @@ DEPENDENCIES: - permission_handler (from `.symlinks/plugins/permission_handler/ios`) - share (from `.symlinks/plugins/share/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - UnstoppableDomainsResolution (~> 0.3.6) - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) SPEC REPOS: https://github.com/CocoaPods/Specs.git: + - BigInt - CryptoSwift - DKImagePickerController - DKPhotoGallery + - EthereumAddress - MTBBarcodeScanner - Reachability - SDWebImage - SwiftProtobuf - SwiftyGif + - UnstoppableDomainsResolution EXTERNAL SOURCES: barcode_scan: @@ -155,6 +166,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479 + BigInt: f668a80089607f521586bbe29513d708491ef2f7 connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060 cw_monero: 2e1f79929880cc2293b5bc1b25e28152e4d84649 @@ -162,6 +174,7 @@ SPEC CHECKSUMS: DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4 + EthereumAddress: 39fe8e11cf04e4e9902b55ae653dbc4e0aee5f30 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec @@ -176,9 +189,10 @@ SPEC CHECKSUMS: shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699 SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40 + UnstoppableDomainsResolution: 63abb84858d3e91eb838a5bfa6f7e3c0e0593f24 url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef webview_flutter: 9f491a9b5a66f2573946a389b2677987b0ff8c0b -PODFILE CHECKSUM: 5b5f101b119a1b6eb857c967d462832a9062dec4 +PODFILE CHECKSUM: 2172f10eb8ff8378f8d6e3e2c1366a0e6cdea018 -COCOAPODS: 1.9.3 +COCOAPODS: 1.10.1 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 01aa14195..8b6264a04 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,8 +1,13 @@ import UIKit import Flutter +import UnstoppableDomainsResolution @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { + lazy var resolution : Resolution? = { + return try? Resolution() + }() + override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? @@ -10,6 +15,8 @@ import Flutter let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let batteryChannel = FlutterMethodChannel(name: "com.cakewallet.cakewallet/legacy_wallet_migration", binaryMessenger: controller.binaryMessenger) + let unstoppableDomainChannel = FlutterMethodChannel(name: "com.cakewallet.cake_wallet/unstoppable-domain", binaryMessenger: controller.binaryMessenger) + batteryChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in @@ -52,6 +59,39 @@ import Flutter } }) + unstoppableDomainChannel.setMethodCallHandler({ [weak self] + (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + switch call.method { + case "getUnstoppableDomainAddress": + guard let args = call.arguments as? Dictionary, + let domain = args["domain"], + let ticker = args["ticker"] else { + result(nil) + return + } + + guard let resolution = self?.resolution else { + result(nil) + return + } + + resolution.addr(domain: domain, ticker: ticker) { addrResult in + var address : String = "" + + switch addrResult { + case .success(let returnValue): + address = returnValue + case .failure(let error): + print("Expected Address, but got \(error)") + } + + result(address) + } + default: + result(FlutterMethodNotImplemented) + } + }) + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } diff --git a/lib/entities/unstoppable_domain_address.dart b/lib/entities/unstoppable_domain_address.dart new file mode 100644 index 000000000..494782d54 --- /dev/null +++ b/lib/entities/unstoppable_domain_address.dart @@ -0,0 +1,22 @@ +import 'package:flutter/services.dart'; + +const channel = MethodChannel('com.cakewallet.cake_wallet/unstoppable-domain'); + +Future fetchUnstoppableDomainAddress(String domain, String ticker) async { + String address; + + try { + address = await channel.invokeMethod( + 'getUnstoppableDomainAddress', + { + 'domain' : domain, + 'ticker' : ticker + } + ); + } catch (e) { + print('Unstoppable domain error: ${e.toString()}'); + address = ''; + } + + return address; +} \ 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 82d7dd80e..853ff9876 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -1,7 +1,7 @@ import 'dart:ui'; import 'package:cake_wallet/entities/sync_status.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; -import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart'; +import 'package:cake_wallet/src/screens/send/widgets/unstoppable_domain_address_alert.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:flutter/cupertino.dart'; @@ -9,8 +9,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/exchange/exchange_provider.dart'; -import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/exchange/exchange_trade_state.dart'; import 'package:cake_wallet/exchange/limits_state.dart'; @@ -23,7 +21,6 @@ import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/entities/crypto_currency.dart'; -import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; @@ -42,7 +39,9 @@ class ExchangePage extends BasePage { final checkBoxKey = GlobalKey(); final _formKey = GlobalKey(); final _depositAmountFocus = FocusNode(); + final _depositAddressFocus = FocusNode(); final _receiveAmountFocus = FocusNode(); + final _receiveAddressFocus = FocusNode(); var _isReactionsSet = false; @override @@ -52,7 +51,7 @@ class ExchangePage extends BasePage { Color get titleColor => Colors.white; @override - bool get resizeToAvoidBottomPadding => false; + bool get resizeToAvoidBottomInset => false; @override bool get extendBodyBehindAppBar => true; @@ -171,6 +170,7 @@ class ExchangePage extends BasePage { .calculateDepositAllAmount() : null, amountFocusNode: _depositAmountFocus, + addressFocusNode: _depositAddressFocus, key: depositKey, title: S.of(context).you_will_send, initialCurrency: @@ -223,6 +223,15 @@ class ExchangePage extends BasePage { type: exchangeViewModel.wallet.type), addressTextFieldValidator: AddressValidator( type: exchangeViewModel.depositCurrency), + onPushPasteButton: (context) async { + final domain = + exchangeViewModel.depositAddress; + final ticker = + exchangeViewModel.depositCurrency.title; + exchangeViewModel.depositAddress = + await applyUnstoppableDomainAddress( + context, domain, ticker); + }, ), ), ), @@ -232,6 +241,7 @@ class ExchangePage extends BasePage { child: Observer( builder: (_) => ExchangeCard( amountFocusNode: _receiveAmountFocus, + addressFocusNode: _receiveAddressFocus, key: receiveKey, title: S.of(context).you_will_get, initialCurrency: @@ -268,6 +278,15 @@ class ExchangePage extends BasePage { AddressValidator( type: exchangeViewModel .receiveCurrency), + onPushPasteButton: (context) async { + final domain = + exchangeViewModel.receiveAddress; + final ticker = + exchangeViewModel.receiveCurrency.title; + exchangeViewModel.receiveAddress = + await applyUnstoppableDomainAddress( + context, domain, ticker); + }, )), ) ], @@ -371,7 +390,7 @@ class ExchangePage extends BasePage { from: template.depositCurrency, to: template.receiveCurrency, onTap: () { - applyTemplate( + applyTemplate(context, exchangeViewModel, template); }, onRemove: () { @@ -472,8 +491,8 @@ class ExchangePage extends BasePage { )); } - void applyTemplate( - ExchangeViewModel exchangeViewModel, ExchangeTemplate template) { + void applyTemplate(BuildContext context, + ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async { exchangeViewModel.changeDepositCurrency( currency: CryptoCurrency.fromString(template.depositCurrency)); exchangeViewModel.changeReceiveCurrency( @@ -491,6 +510,16 @@ class ExchangePage extends BasePage { exchangeViewModel.receiveAddress = template.receiveAddress; exchangeViewModel.isReceiveAmountEntered = false; exchangeViewModel.isFixedRateMode = false; + + var domain = template.depositAddress; + var ticker = template.depositCurrency; + exchangeViewModel.depositAddress = + await applyUnstoppableDomainAddress(context, domain, ticker); + + domain = template.receiveAddress; + ticker = template.receiveCurrency; + exchangeViewModel.receiveAddress = + await applyUnstoppableDomainAddress(context, domain, ticker); } void _setReactions( @@ -656,6 +685,26 @@ class ExchangePage extends BasePage { } }); + _depositAddressFocus.addListener(() async { + if (!_depositAddressFocus.hasFocus && + depositAddressController.text.isNotEmpty) { + final domain = depositAddressController.text; + final ticker = exchangeViewModel.depositCurrency.title; + exchangeViewModel.depositAddress = + await applyUnstoppableDomainAddress(context, domain, ticker); + } + }); + + _receiveAddressFocus.addListener(() async { + if (!_receiveAddressFocus.hasFocus && + receiveAddressController.text.isNotEmpty) { + final domain = receiveAddressController.text; + final ticker = exchangeViewModel.receiveCurrency.title; + exchangeViewModel.receiveAddress = + await applyUnstoppableDomainAddress(context, domain, ticker); + } + }); + _receiveAmountFocus.addListener(() { if (_receiveAmountFocus.hasFocus && !exchangeViewModel.isFixedRateMode) { showPopUp( @@ -726,4 +775,20 @@ class ExchangePage extends BasePage { key.currentState.addressController.text = null; } } + + Future applyUnstoppableDomainAddress(BuildContext context, + String domain, String ticker) async { + try { + final address = + await exchangeViewModel.getUnstoppableDomainAddress(domain, ticker); + if ((address != null)&&address.isNotEmpty) { + unstoppableDomainAddressAlert(context, domain); + return address; + } + } catch (e) { + print(e.toString()); + } + + return domain; + } } diff --git a/lib/src/screens/exchange/exchange_template_page.dart b/lib/src/screens/exchange/exchange_template_page.dart index 5437703ce..1be8d1d6d 100644 --- a/lib/src/screens/exchange/exchange_template_page.dart +++ b/lib/src/screens/exchange/exchange_template_page.dart @@ -37,7 +37,7 @@ class ExchangeTemplatePage extends BasePage { Color get titleColor => Colors.white; @override - bool get resizeToAvoidBottomPadding => false; + bool get resizeToAvoidBottomInset => false; @override bool get extendBodyBehindAppBar => true; @@ -150,9 +150,8 @@ class ExchangeTemplatePage extends BasePage { .color, currencyValueValidator: AmountValidator( type: exchangeViewModel.wallet.type), - addressTextFieldValidator: AddressValidator( - type: - exchangeViewModel.depositCurrency), + //addressTextFieldValidator: AddressValidator( + // type: exchangeViewModel.depositCurrency), ), ), ), @@ -190,8 +189,8 @@ class ExchangeTemplatePage extends BasePage { .decorationColor, currencyValueValidator: AmountValidator( type: exchangeViewModel.wallet.type), - addressTextFieldValidator: AddressValidator( - type: exchangeViewModel.receiveCurrency), + //addressTextFieldValidator: AddressValidator( + // type: exchangeViewModel.receiveCurrency), )), ) ], diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 7af57e28b..f534cccad 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -28,8 +28,10 @@ class ExchangeCard extends StatefulWidget { this.currencyValueValidator, this.addressTextFieldValidator, this.amountFocusNode, + this.addressFocusNode, this.hasAllAmount = false, - this.allAmount}) + this.allAmount, + this.onPushPasteButton}) : super(key: key); final List currencies; @@ -49,8 +51,10 @@ class ExchangeCard extends StatefulWidget { final FormFieldValidator currencyValueValidator; final FormFieldValidator addressTextFieldValidator; final FocusNode amountFocusNode; + final FocusNode addressFocusNode; final bool hasAllAmount; - Function allAmount; + final Function allAmount; + final Function(BuildContext context) onPushPasteButton; @override ExchangeCardState createState() => ExchangeCardState(); @@ -288,6 +292,7 @@ class ExchangeCardState extends State { ? Padding( padding: EdgeInsets.only(top: 20), child: AddressTextField( + focusNode: widget.addressFocusNode, controller: addressController, placeholder: widget.hasRefundAddress ? S.of(context).refund_address @@ -311,6 +316,7 @@ class ExchangeCardState extends State { .decorationColor), buttonColor: widget.addressButtonsColor, validator: widget.addressTextFieldValidator, + onPushPasteButton: widget.onPushPasteButton, ), ) : Padding( diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 9fae0ec23..e40657b1d 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -1,6 +1,6 @@ import 'dart:ui'; -import 'package:cake_wallet/entities/monero_transaction_priority.dart'; import 'package:cake_wallet/entities/transaction_priority.dart'; +import 'package:cake_wallet/src/screens/send/widgets/unstoppable_domain_address_alert.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; @@ -38,9 +38,10 @@ class SendPage extends BasePage { _cryptoAmountFocus = FocusNode(), _fiatAmountFocus = FocusNode(), _addressFocusNode = FocusNode() { - _addressFocusNode.addListener(() { + _addressFocusNode.addListener(() async { if (!_addressFocusNode.hasFocus && _addressController.text.isNotEmpty) { - getOpenaliasRecord(_addressFocusNode.context); + await getOpenaliasRecord(_addressFocusNode.context); + await applyUnstoppableDomainAddress(_addressFocusNode.context); } }); } @@ -67,7 +68,7 @@ class SendPage extends BasePage { Color get titleColor => Colors.white; @override - bool get resizeToAvoidBottomPadding => false; + bool get resizeToAvoidBottomInset => false; @override bool get extendBodyBehindAppBar => true; @@ -173,6 +174,10 @@ class SendPage extends BasePage { .headline .decorationColor), validator: sendViewModel.addressValidator, + onPushPasteButton: (context) async { + await getOpenaliasRecord(context); + await applyUnstoppableDomainAddress(context); + }, ), Observer( builder: (_) => Padding( @@ -513,12 +518,13 @@ class SendPage extends BasePage { to: template.name, amount: template.amount, from: template.cryptoCurrency, - onTap: () { + onTap: () async { _addressController.text = template.address; _cryptoAmountController.text = template.amount; - getOpenaliasRecord(context); + await getOpenaliasRecord(context); + await applyUnstoppableDomainAddress(context); }, onRemove: () { showPopUp( @@ -770,4 +776,20 @@ class SendPage extends BasePage { ), context: context); } + + Future applyUnstoppableDomainAddress(BuildContext context) async { + try { + final address = await sendViewModel + .getUnstoppableDomainAddress( + _addressController.text); + + if ((address != null)&&address.isNotEmpty) { + unstoppableDomainAddressAlert( + context, _addressController.text); + _addressController.text = address; + } + } catch (e) { + print(e.toString()); + } + } } diff --git a/lib/src/screens/send/send_template_page.dart b/lib/src/screens/send/send_template_page.dart index 6d3ac7132..3880bd2fb 100644 --- a/lib/src/screens/send/send_template_page.dart +++ b/lib/src/screens/send/send_template_page.dart @@ -34,7 +34,7 @@ class SendTemplatePage extends BasePage { Color get titleColor => Colors.white; @override - bool get resizeToAvoidBottomPadding => false; + bool get resizeToAvoidBottomInset => false; @override bool get extendBodyBehindAppBar => true; @@ -145,7 +145,7 @@ class SendTemplatePage extends BasePage { .primaryTextTheme .headline .decorationColor), - validator: sendViewModel.addressValidator, + //validator: sendViewModel.addressValidator, ), ), Observer(builder: (_) { diff --git a/lib/src/screens/send/widgets/unstoppable_domain_address_alert.dart b/lib/src/screens/send/widgets/unstoppable_domain_address_alert.dart new file mode 100644 index 000000000..011a36794 --- /dev/null +++ b/lib/src/screens/send/widgets/unstoppable_domain_address_alert.dart @@ -0,0 +1,17 @@ +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +void unstoppableDomainAddressAlert(BuildContext context, String domain) async { + await showPopUp( + context: context, + builder: (BuildContext context) { + + return AlertWithOneAction( + alertTitle: S.of(context).address_detected, + alertContent: S.of(context).address_from_domain(domain), + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); +} \ No newline at end of file diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index 8e2c069f1..130912a2f 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -24,7 +24,8 @@ class AddressTextField extends StatelessWidget { this.iconColor, this.textStyle, this.hintStyle, - this.validator}); + this.validator, + this.onPushPasteButton}); static const prefixIconWidth = 34.0; static const prefixIconHeight = 34.0; @@ -43,6 +44,7 @@ class AddressTextField extends StatelessWidget { final TextStyle textStyle; final TextStyle hintStyle; final FocusNode focusNode; + final Function(BuildContext context) onPushPasteButton; @override Widget build(BuildContext context) { @@ -225,5 +227,7 @@ class AddressTextField extends StatelessWidget { if (address?.isNotEmpty ?? false) { controller.text = address; } + + onPushPasteButton?.call(context); } } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index fc860e011..2bcec3565 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/entities/sync_status.dart'; +import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/exchange/limits.dart'; @@ -417,4 +418,8 @@ abstract class ExchangeViewModelBase with Store { }*/ isReceiveAmountEditable = false; } + + Future getUnstoppableDomainAddress(String domain, String ticker) async { + return await fetchUnstoppableDomainAddress(domain, ticker.toLowerCase()); + } } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index b92672ff8..f36261f73 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/entities/transaction_priority.dart'; +import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/monero/monero_amount_format.dart'; import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; import 'package:hive/hive.dart'; @@ -267,6 +268,10 @@ abstract class SendViewModelBase with Store { return record.name != name ? record : null; } + Future getUnstoppableDomainAddress(String domain) async { + return await fetchUnstoppableDomainAddress(domain, currency.title.toLowerCase()); + } + @action void _updateFiatAmount() { try { diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 6d1d66b84..34cef6078 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -469,5 +469,8 @@ "unconfirmed" : "Unbestätigt", "displayable" : "Anzeigebar", - "submit_request" : "Einen Antrag stellen" + "submit_request" : "Einen Antrag stellen", + + "address_detected" : "Adresse erkannt", + "address_from_domain" : "Sie haben die Adresse von der unaufhaltsamen Domain ${domain} erhalten" } \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index b0d98b7c9..a641fabf1 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -469,5 +469,8 @@ "unconfirmed" : "Unconfirmed", "displayable" : "Displayable", - "submit_request" : "submit a request" + "submit_request" : "submit a request", + + "address_detected" : "Address detected", + "address_from_domain" : "You got address from unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 190ad4304..95a34e230 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -469,5 +469,8 @@ "unconfirmed" : "Inconfirmado", "displayable" : "Visualizable", - "submit_request" : "presentar una solicitud" + "submit_request" : "presentar una solicitud", + + "address_detected" : "Dirección detectada", + "address_from_domain" : "Tienes la dirección de unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 6eac14e99..0db4e7216 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -469,5 +469,8 @@ "unconfirmed" : "अपुष्ट", "displayable" : "प्रदर्शन योग्य", - "submit_request" : "एक अनुरोध सबमिट करें" + "submit_request" : "एक अनुरोध सबमिट करें", + + "address_detected" : "पता लग गया", + "address_from_domain" : "आपको अजेय डोमेन ${domain} से पता मिला है" } \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 291d5afd3..89556602a 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -469,5 +469,8 @@ "unconfirmed" : "未確認", "displayable" : "表示可能", - "submit_request" : "リクエストを送信する" + "submit_request" : "リクエストを送信する", + + "address_detected" : "アドレスが検出されました", + "address_from_domain" : "あなたはからアドレスを得ました unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 781df8d8a..9433a4189 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -469,5 +469,8 @@ "unconfirmed" : "미확인", "displayable" : "표시 가능", - "submit_request" : "요청을 제출" + "submit_request" : "요청을 제출", + + "address_detected" : "주소 감지", + "address_from_domain" : "주소는 unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 9a6183efb..8b8bef453 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -469,5 +469,8 @@ "unconfirmed" : "Niet bevestigd", "displayable" : "Weer te geven", - "submit_request" : "een verzoek indienen" + "submit_request" : "een verzoek indienen", + + "address_detected" : "Adres gedetecteerd", + "address_from_domain" : "Je adres is van unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 6db66958e..b5291e227 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -469,5 +469,8 @@ "unconfirmed" : "Niepotwierdzony", "displayable" : "Wyświetlane", - "submit_request" : "złożyć wniosek" + "submit_request" : "złożyć wniosek", + + "address_detected" : "Wykryto adres", + "address_from_domain" : "Dostałeś adres od unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 631be8f42..8e955310f 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -469,5 +469,8 @@ "unconfirmed" : "Não confirmado", "displayable" : "Exibível", - "submit_request" : "enviar um pedido" + "submit_request" : "enviar um pedido", + + "address_detected" : "Endereço detectado", + "address_from_domain" : "Você obteve o endereço de unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 333a823d8..142ae0dd1 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -469,5 +469,8 @@ "unconfirmed" : "Неподтвержденный", "displayable" : "Отображаемый", - "submit_request" : "отправить запрос" + "submit_request" : "отправить запрос", + + "address_detected" : "Обнаружен адрес", + "address_from_domain" : "Вы получили адрес из unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index cc10a2d89..6c659bcea 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -469,5 +469,8 @@ "unconfirmed" : "Непідтверджений", "displayable" : "Відображуваний", - "submit_request" : "надіслати запит" + "submit_request" : "надіслати запит", + + "address_detected" : "Виявлено адресу", + "address_from_domain" : "Ви отримали адресу від unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 639babcbc..f1560941e 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -469,5 +469,8 @@ "unconfirmed" : "未经证实", "displayable" : "可显示", - "submit_request" : "提交請求" + "submit_request" : "提交請求", + + "address_detected" : "檢測到地址", + "address_from_domain" : "您有以下地址 unstoppable domain ${domain}" } \ No newline at end of file From 2e22dc814a5177838169a07657b812557515a08b Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Wed, 21 Apr 2021 20:05:38 +0300 Subject: [PATCH 02/28] CAKE-192 | configured build.gradle --- android/app/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index cef66608c..e8d8aa315 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -67,7 +67,8 @@ android { release { signingConfig signingConfigs.release - minifyEnabled true + shrinkResources false + minifyEnabled false useProguard true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' From 9f1f4575ddaaa9db3f6ab70d887f8e69daf7fd5e Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Thu, 22 Apr 2021 19:16:47 +0300 Subject: [PATCH 03/28] CAKE-192 | configured build.gradle --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index e8d8aa315..8c89aee52 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -69,7 +69,7 @@ android { shrinkResources false minifyEnabled false - useProguard true + useProguard false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } From 4d6cc87529cb25f860d227992a962ea70c623cd7 Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Mon, 24 May 2021 21:06:12 +0300 Subject: [PATCH 04/28] CAKE-192 | merged main branch into current; fixed call unstoppable domains api for different sdk versions; fixed italian and croatian locales --- .../cakewallet/cake_wallet/MainActivity.java | 68 +++++++--------- ios/Runner/AppDelegate.swift | 78 +++++++++---------- pubspec.lock | 6 +- res/values/strings_de.arb | 2 +- res/values/strings_en.arb | 2 +- res/values/strings_es.arb | 2 +- res/values/strings_hi.arb | 2 +- res/values/strings_hr.arb | 5 +- res/values/strings_it.arb | 5 +- res/values/strings_ja.arb | 2 +- res/values/strings_ko.arb | 2 +- res/values/strings_zh.arb | 1 + 12 files changed, 85 insertions(+), 90 deletions(-) diff --git a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java index 074ce247f..f796e5417 100644 --- a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java +++ b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java @@ -10,29 +10,29 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import android.os.AsyncTask; +import android.os.Build; import android.os.Handler; import android.os.Looper; -import java.security.SecureRandom; import com.unstoppabledomains.resolution.DomainResolution; import com.unstoppabledomains.resolution.Resolution; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.Looper; - -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; +import java.security.SecureRandom; public class MainActivity extends FlutterFragmentActivity { final String UTILS_CHANNEL = "com.cake_wallet/native_utils"; - final String UNSTOPPABLE_DOMAIN_CHANNEL = "com.cakewallet.cake_wallet/unstoppable-domain"; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine); + MethodChannel utilsChannel = + new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), + UTILS_CHANNEL); + + utilsChannel.setMethodCallHandler(this::handle); + MethodChannel unstoppableDomainChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), UNSTOPPABLE_DOMAIN_CHANNEL); @@ -41,14 +41,30 @@ public class MainActivity extends FlutterFragmentActivity { } private void handle(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + Handler handler = new Handler(Looper.getMainLooper()); + try { - if (call.method.equals("getUnstoppableDomainAddress")) { - getUnstoppableDomainAddress(call, result); - } else { - result.notImplemented(); + switch (call.method) { + case "sec_random": + int count = call.argument("count"); + SecureRandom random = new SecureRandom(); + byte bytes[] = new byte[count]; + random.nextBytes(bytes); + handler.post(() -> result.success(bytes)); + break; + case "getUnstoppableDomainAddress": + int version = Build.VERSION.SDK_INT; + if (version >= 24) { + getUnstoppableDomainAddress(call, result); + } else { + handler.post(() -> result.success("")); + } + break; + default: + handler.post(() -> result.notImplemented()); } } catch (Exception e) { - result.error("UNCAUGHT_ERROR", e.getMessage(), null); + handler.post(() -> result.error("UNCAUGHT_ERROR", e.getMessage(), null)); } } @@ -66,29 +82,5 @@ public class MainActivity extends FlutterFragmentActivity { handler.post(() -> result.error("INVALID DOMAIN", e.getMessage(), null)); } }); - - MethodChannel utilsChannel = - new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), - UTILS_CHANNEL); - - utilsChannel.setMethodCallHandler(this::handle); } - - private void handle(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - Handler handler = new Handler(Looper.getMainLooper()); - - try { - if (call.method.equals("sec_random")) { - int count = call.argument("count"); - SecureRandom random = new SecureRandom(); - byte bytes[] = new byte[count]; - random.nextBytes(bytes); - handler.post(() -> result.success(bytes)); - } else { - handler.post(() -> result.notImplemented()); - } - } catch (Exception e) { - handler.post(() -> result.error("UNCAUGHT_ERROR", e.getMessage(), null)); - } - } -} +} \ No newline at end of file diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index dbd935316..5a50892ec 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -5,9 +5,9 @@ import UnstoppableDomainsResolution @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { lazy var resolution : Resolution? = { - return try? Resolution() - }() - + return try? Resolution() + }() + override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? @@ -17,11 +17,6 @@ import UnstoppableDomainsResolution name: "com.cakewallet.cakewallet/legacy_wallet_migration", binaryMessenger: controller.binaryMessenger) legacyMigrationChannel.setMethodCallHandler({ - let batteryChannel = FlutterMethodChannel(name: "com.cakewallet.cakewallet/legacy_wallet_migration", - binaryMessenger: controller.binaryMessenger) - let unstoppableDomainChannel = FlutterMethodChannel(name: "com.cakewallet.cake_wallet/unstoppable-domain", binaryMessenger: controller.binaryMessenger) - - batteryChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in switch call.method { @@ -62,7 +57,7 @@ import UnstoppableDomainsResolution result(FlutterMethodNotImplemented) } }) - + let utilsChannel = FlutterMethodChannel( name: "com.cake_wallet/native_utils", binaryMessenger: controller.binaryMessenger) @@ -80,40 +75,41 @@ import UnstoppableDomainsResolution result(FlutterMethodNotImplemented) } }) - + + let unstoppableDomainChannel = FlutterMethodChannel(name: "com.cakewallet.cake_wallet/unstoppable-domain", binaryMessenger: controller.binaryMessenger) unstoppableDomainChannel.setMethodCallHandler({ [weak self] - (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in - switch call.method { - case "getUnstoppableDomainAddress": - guard let args = call.arguments as? Dictionary, - let domain = args["domain"], - let ticker = args["ticker"] else { - result(nil) - return - } - - guard let resolution = self?.resolution else { - result(nil) - return - } - - resolution.addr(domain: domain, ticker: ticker) { addrResult in - var address : String = "" - - switch addrResult { - case .success(let returnValue): - address = returnValue - case .failure(let error): - print("Expected Address, but got \(error)") + (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + switch call.method { + case "getUnstoppableDomainAddress": + guard let args = call.arguments as? Dictionary, + let domain = args["domain"], + let ticker = args["ticker"] else { + result(nil) + return + } + + guard let resolution = self?.resolution else { + result(nil) + return + } + + resolution.addr(domain: domain, ticker: ticker) { addrResult in + var address : String = "" + + switch addrResult { + case .success(let returnValue): + address = returnValue + case .failure(let error): + print("Expected Address, but got \(error)") + } + + result(address) + } + default: + result(FlutterMethodNotImplemented) } - - result(address) - } - default: - result(FlutterMethodNotImplemented) - } - }) - + }) + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } diff --git a/pubspec.lock b/pubspec.lock index 3ff596074..207f4b18a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -70,7 +70,7 @@ packages: path: "." ref: cake resolved-ref: "02fef082f20af13de00b4e64efb93a2c1e5e1cf2" - url: "git@github.com:cake-tech/bech32.git" + url: "https://github.com/cake-tech/bech32.git" source: git version: "0.2.0" bip32: @@ -92,8 +92,8 @@ packages: description: path: "." ref: cake - resolved-ref: b3ab2926c665f0e68b74a4a5f31059f7fcd817b7 - url: "git@github.com:cake-tech/bitcoin_flutter.git" + resolved-ref: cbabfd87b6ce3cae6051a3e86ddb56e7a934e188 + url: "https://github.com/cake-tech/bitcoin_flutter.git" source: git version: "2.0.2" boolean_selector: diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index da4d4975b..fe64bc855 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -474,7 +474,7 @@ "buy_alert_content" : "Derzeit unterstützen wir nur den Kauf von Bitcoin. Um Bitcoin zu kaufen, erstellen Sie bitte Ihre Bitcoin-Brieftasche oder wechseln Sie zu dieser", "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.", - "understand" : "I understand" + "understand" : "I understand", "address_detected" : "Adresse erkannt", "address_from_domain" : "Sie haben die Adresse von der unaufhaltsamen Domain ${domain} erhalten" diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index e43ec7318..e0e254762 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -474,7 +474,7 @@ "buy_alert_content" : "Currently we only support the purchase of Bitcoin. To buy Bitcoin, please create or switch to your Bitcoin wallet", "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.", - "understand" : "I understand" + "understand" : "I understand", "address_detected" : "Address detected", "address_from_domain" : "You got address from unstoppable domain ${domain}" diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 09dae1e32..a1efe9c9b 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -474,7 +474,7 @@ "buy_alert_content" : "Actualmente solo apoyamos la compra de Bitcoin. Para comprar Bitcoin, cree o cambie a su billetera Bitcoin", "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.", - "understand" : "I understand" + "understand" : "I understand", "address_detected" : "Dirección detectada", "address_from_domain" : "Tienes la dirección de unstoppable domain ${domain}" diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index b9a873265..899a6d9a8 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -474,7 +474,7 @@ "buy_alert_content" : "वर्तमान में हम केवल बिटकॉइन की खरीद का समर्थन करते हैं। बिटकॉइन खरीदने के लिए, कृपया अपना बिटकॉइन वॉलेट बनाएं या स्विच करें", "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.", - "understand" : "I understand" + "understand" : "I understand", "address_detected" : "पता लग गया", "address_from_domain" : "आपको अजेय डोमेन ${domain} से पता मिला है" diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index d449fca1b..857bb7905 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -474,5 +474,8 @@ "buy_alert_content" : "Currently we only support the purchase of Bitcoin. To buy Bitcoin, please create or switch to your Bitcoin wallet", "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.", - "understand" : "I understand" + "understand" : "I understand", + + "address_detected" : "Adresa je otkrivena", + "address_from_domain" : "Dobili ste adresu od unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index e8c4da26d..7d5fd783f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -474,5 +474,8 @@ "buy_alert_content" : "Currently we only support the purchase of Bitcoin. To buy Bitcoin, please create or switch to your Bitcoin wallet", "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.", - "understand" : "I understand" + "understand" : "I understand", + + "address_detected" : "Indirizzo rilevato", + "address_from_domain" : "Hai l'indirizzo da unstoppable domain ${domain}" } \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 5fb875578..94ddea3de 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -474,7 +474,7 @@ "buy_alert_content" : "現在、ビットコインの購入のみをサポートしています。 ビットコインを購入するには、ビットコインウォレットを作成するか切り替えてください", "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.", - "understand" : "I understand" + "understand" : "I understand", "address_detected" : "アドレスが検出されました", "address_from_domain" : "あなたはからアドレスを得ました unstoppable domain ${domain}" diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 0d64ea4fc..836e36d52 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -474,7 +474,7 @@ "buy_alert_content" : "현재 우리는 비트 코인 구매 만 지원합니다. 비트 코인을 구매하려면 비트 코인 지갑을 생성하거나 전환하십시오", "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.", - "understand" : "I understand" + "understand" : "I understand", "address_detected" : "주소 감지", "address_from_domain" : "주소는 unstoppable domain ${domain}" diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index ce831a4b3..274108fc4 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -469,6 +469,7 @@ "displayable" : "可显示", "submit_request" : "提交请求", + "buy_alert_content" : "目前,我們僅支持購買比特幣。 要購買比特幣,請創建或切換到您的比特幣錢包", "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.", From f324d6599d1e1c4fa634f0fc80ed17ac45365ce4 Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Tue, 25 May 2021 16:53:35 +0300 Subject: [PATCH 05/28] CAKE-192 | added final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK to MainActivity.java --- .../src/main/java/com/cakewallet/cake_wallet/MainActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java index f796e5417..fa6930a05 100644 --- a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java +++ b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java @@ -22,6 +22,7 @@ import java.security.SecureRandom; public class MainActivity extends FlutterFragmentActivity { final String UTILS_CHANNEL = "com.cake_wallet/native_utils"; final String UNSTOPPABLE_DOMAIN_CHANNEL = "com.cakewallet.cake_wallet/unstoppable-domain"; + final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { @@ -54,7 +55,7 @@ public class MainActivity extends FlutterFragmentActivity { break; case "getUnstoppableDomainAddress": int version = Build.VERSION.SDK_INT; - if (version >= 24) { + if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) { getUnstoppableDomainAddress(call, result); } else { handler.post(() -> result.success("")); From aca8e8a292d413782821ba4bb48038384955a9ba Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Tue, 25 May 2021 20:32:36 +0300 Subject: [PATCH 06/28] CAKE-329 | added unspent coins list page to the app --- lib/di.dart | 8 ++ lib/router.dart | 5 ++ lib/routes.dart | 1 + lib/src/screens/send/send_page.dart | 41 ++++++---- .../unspent_coins_list_page.dart | 38 +++++++++ .../widgets/unspent_coins_list_item.dart | 77 +++++++++++++++++++ .../unspent_coins/unspent_coins_item.dart | 14 ++++ .../unspent_coins_list_view_model.dart | 47 +++++++++++ pubspec.lock | 6 +- 9 files changed, 220 insertions(+), 17 deletions(-) create mode 100644 lib/src/screens/unspent_coins/unspent_coins_list_page.dart create mode 100644 lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart create mode 100644 lib/view_model/unspent_coins/unspent_coins_item.dart create mode 100644 lib/view_model/unspent_coins/unspent_coins_list_view_model.dart diff --git a/lib/di.dart b/lib/di.dart index cae3a068f..211bf5708 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -40,6 +40,7 @@ import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart'; +import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart'; @@ -76,6 +77,7 @@ import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart'; import 'package:cake_wallet/view_model/support_view_model.dart'; import 'package:cake_wallet/view_model/transaction_details_view_model.dart'; import 'package:cake_wallet/view_model/trade_details_view_model.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/auth_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; @@ -561,5 +563,11 @@ Future setup( getIt.registerFactory(() => SupportPage(getIt.get())); + getIt.registerFactory(() => UnspentCoinsListViewModel()); + + getIt.registerFactory(() => UnspentCoinsListPage( + unspentCoinsListViewModel: getIt.get() + )); + _isSetupFinished = true; } diff --git a/lib/router.dart b/lib/router.dart index 19a6b760f..b8967645e 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; import 'package:cake_wallet/src/screens/support/support_page.dart'; +import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/wyre/wyre_page.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; @@ -355,6 +356,10 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute( builder: (_) => getIt.get()); + case Routes.unspentCoinsList: + return MaterialPageRoute( + builder: (_) => getIt.get()); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index 2bf8ee7b7..84a48d5ea 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -54,4 +54,5 @@ class Routes { static const support = '/support'; static const orderDetails = '/order_details'; static const wyre = '/wyre'; + static const unspentCoinsList = '/unspent_coins_list'; } \ No newline at end of file diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 9fae0ec23..1fd74e130 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -558,22 +558,35 @@ class SendPage extends BasePage { ), bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), - bottomSection: Observer(builder: (_) { - return LoadingPrimaryButton( - onPressed: () async { - if (_formKey.currentState.validate()) { - await sendViewModel.createTransaction(); - } - }, - text: S.of(context).send, - color: Theme.of(context).accentTextTheme.body2.color, - textColor: Colors.white, - isLoading: sendViewModel.state is IsExecutingState || - sendViewModel.state is TransactionCommitting, - isDisabled: + bottomSection: Column( + children: [ + Observer(builder: (_) { + return LoadingPrimaryButton( + onPressed: () async { + if (_formKey.currentState.validate()) { + await sendViewModel.createTransaction(); + } + }, + text: S.of(context).send, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + isLoading: sendViewModel.state is IsExecutingState || + sendViewModel.state is TransactionCommitting, + isDisabled: false // FIXME !(syncStore.status is SyncedSyncStatus), ); - })), + }), + Padding( + padding: EdgeInsets.only(top: 12), + child: PrimaryButton( + onPressed: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList), + text: 'Unspent coins', + color: Colors.green, + textColor: Colors.white, + ) + ) + ], + )), )); } diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart new file mode 100644 index 000000000..44a6a34b8 --- /dev/null +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -0,0 +1,38 @@ +import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart'; +import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class UnspentCoinsListPage extends BasePage { + UnspentCoinsListPage({this.unspentCoinsListViewModel}); + + @override + String get title => 'Unspent coins'; + + final UnspentCoinsListViewModel unspentCoinsListViewModel; + + @override + Widget body(BuildContext context) { + return SectionStandardList( + sectionCount: 1, + itemCounter: (int _) => unspentCoinsListViewModel.items.length, + itemBuilder: (_, __, index) { + final item = unspentCoinsListViewModel.items[index]; + + return GestureDetector( + onTap: () {print('Item taped');}, + child: UnspentCoinsListItem( + address: item.address, + amount: item.amount, + isFrozen: item.isFrozen, + note: item.note, + isSending: item.isSending, + onCheckBoxTap: (value) {print('CheckBox taped');}, + ) + ); + }); + } + +} \ No newline at end of file diff --git a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart new file mode 100644 index 000000000..eb8ba33c0 --- /dev/null +++ b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart @@ -0,0 +1,77 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; + +class UnspentCoinsListItem extends StatelessWidget { + UnspentCoinsListItem({ + @required this.address, + @required this.amount, + @required this.isFrozen, + @required this.note, + @required this.isSending, + @required this.onCheckBoxTap, +}); + + final String address; + final String amount; + final bool isFrozen; + final String note; + final bool isSending; + final Function(bool) onCheckBoxTap; + + @override + Widget build(BuildContext context) { + final textStyle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .primaryTextTheme + .title + .color); + + return Container( + padding: EdgeInsets.fromLTRB(24, 16, 24, 16), + color: Theme.of(context).backgroundColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + address ?? 'Address', + style: textStyle, + ), + Padding( + padding: EdgeInsets.only(top: 12), + child: Text( + amount ?? 'Amount', + style: textStyle, + ) + ), + if (isFrozen ?? false) Padding( + padding: EdgeInsets.only(top: 12), + child: Text( + 'Freeze', + style: textStyle, + ) + ), + if (note?.isNotEmpty ?? false) Padding( + padding: EdgeInsets.only(top: 12), + child: AutoSizeText( + note, + style: textStyle, + maxLines: 1 + ) + ), + Padding( + padding: EdgeInsets.only(top: 12), + child: StandardCheckbox( + value: isSending, + caption: 'Sending', + onChanged: onCheckBoxTap + ) + ) + ], + ) + ); + } +} \ No newline at end of file diff --git a/lib/view_model/unspent_coins/unspent_coins_item.dart b/lib/view_model/unspent_coins/unspent_coins_item.dart new file mode 100644 index 000000000..94b7d55c6 --- /dev/null +++ b/lib/view_model/unspent_coins/unspent_coins_item.dart @@ -0,0 +1,14 @@ +class UnspentCoinsItem { + UnspentCoinsItem({ + this.address, + this.amount, + this.isFrozen, + this.note, + this.isSending = true}); + + final String address; + final String amount; + final bool isFrozen; + final String note; + final bool isSending; +} \ No newline at end of file diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart new file mode 100644 index 000000000..4b765d55e --- /dev/null +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -0,0 +1,47 @@ +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; +import 'package:mobx/mobx.dart'; + +part 'unspent_coins_list_view_model.g.dart'; + +const List> unspentCoinsMap = [ + { + "address" : "11111111111111121111132432432432432432432443124324324234324324324324332424", + "amount" : "222", + "isFrozen" : true, + "note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"}, + { + "address" : "444", + "amount" : "555", + "note" : "sfjskf"}, + { + "address" : "777", + "amount" : "888", + "isFrozen" : false}, + { + "address" : "11111111111111121111132432432432432432432443124324324234324324324324332424", + "amount" : "222", + "isFrozen" : true, + "note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"}, + { + "address" : "444", + "amount" : "555", + "note" : "sffsfsdsgs"}, + { + "address" : "777", + "amount" : "888", + "isFrozen" : false}, + {}, +]; + +class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel; + +abstract class UnspentCoinsListViewModelBase with Store { + @computed + List get items => unspentCoinsMap.map((elem) => + UnspentCoinsItem( + address: elem["address"] as String, + amount: elem["amount"] as String, + isFrozen: elem["isFrozen"] as bool, + note: elem["note"] as String + )).toList(); +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 3ff596074..207f4b18a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -70,7 +70,7 @@ packages: path: "." ref: cake resolved-ref: "02fef082f20af13de00b4e64efb93a2c1e5e1cf2" - url: "git@github.com:cake-tech/bech32.git" + url: "https://github.com/cake-tech/bech32.git" source: git version: "0.2.0" bip32: @@ -92,8 +92,8 @@ packages: description: path: "." ref: cake - resolved-ref: b3ab2926c665f0e68b74a4a5f31059f7fcd817b7 - url: "git@github.com:cake-tech/bitcoin_flutter.git" + resolved-ref: cbabfd87b6ce3cae6051a3e86ddb56e7a934e188 + url: "https://github.com/cake-tech/bitcoin_flutter.git" source: git version: "2.0.2" boolean_selector: From b71e85b11259738b70ef5970cc605c8d9a3b7f17 Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Wed, 26 May 2021 19:10:46 +0300 Subject: [PATCH 07/28] CAKE-329 | applied observable list to items in the unspent_coins_list_view_model.dart --- .../unspent_coins_list_page.dart | 24 +++++++++---------- .../unspent_coins_list_view_model.dart | 15 ++++++------ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart index 44a6a34b8..cc175eb01 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_mod import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; class UnspentCoinsListPage extends BasePage { UnspentCoinsListPage({this.unspentCoinsListViewModel}); @@ -15,24 +16,23 @@ class UnspentCoinsListPage extends BasePage { @override Widget body(BuildContext context) { - return SectionStandardList( + return Observer(builder: (_) => SectionStandardList( sectionCount: 1, itemCounter: (int _) => unspentCoinsListViewModel.items.length, itemBuilder: (_, __, index) { final item = unspentCoinsListViewModel.items[index]; return GestureDetector( - onTap: () {print('Item taped');}, - child: UnspentCoinsListItem( - address: item.address, - amount: item.amount, - isFrozen: item.isFrozen, - note: item.note, - isSending: item.isSending, - onCheckBoxTap: (value) {print('CheckBox taped');}, - ) - ); - }); + onTap: () {print('Item taped');}, + child: UnspentCoinsListItem( + address: item.address, + amount: item.amount, + isFrozen: item.isFrozen, + note: item.note, + isSending: item.isSending, + onCheckBoxTap: (value) {print('CheckBox taped');}, + )); + })); } } \ No newline at end of file diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart index 4b765d55e..5f631e52a 100644 --- a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -37,11 +37,12 @@ class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCo abstract class UnspentCoinsListViewModelBase with Store { @computed - List get items => unspentCoinsMap.map((elem) => - UnspentCoinsItem( - address: elem["address"] as String, - amount: elem["amount"] as String, - isFrozen: elem["isFrozen"] as bool, - note: elem["note"] as String - )).toList(); + ObservableList get items => + ObservableList.of(unspentCoinsMap.map((elem) => + UnspentCoinsItem( + address: elem["address"] as String, + amount: elem["amount"] as String, + isFrozen: elem["isFrozen"] as bool, + note: elem["note"] as String + ))); } \ No newline at end of file From e36c07b0da6377849f363c5fb94bd4f01bfd5af4 Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Thu, 27 May 2021 20:57:51 +0300 Subject: [PATCH 08/28] CAKE-330 | added unspent coins details page to the app --- lib/di.dart | 12 ++++ lib/router.dart | 8 +++ lib/routes.dart | 1 + .../unspent_coins_details_page.dart | 52 ++++++++++++++++++ .../unspent_coins_list_page.dart | 5 +- .../widgets/unspent_coins_switch_row.dart | 45 +++++++++++++++ .../unspent_coins_details_view_model.dart | 55 +++++++++++++++++++ .../unspent_coins_switch_item.dart | 12 ++++ 8 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 lib/src/screens/unspent_coins/unspent_coins_details_page.dart create mode 100644 lib/src/screens/unspent_coins/widgets/unspent_coins_switch_row.dart create mode 100644 lib/view_model/unspent_coins/unspent_coins_details_view_model.dart create mode 100644 lib/view_model/unspent_coins/unspent_coins_switch_item.dart diff --git a/lib/di.dart b/lib/di.dart index 211bf5708..70f5b4a51 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -40,6 +40,7 @@ import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart'; +import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; @@ -77,6 +78,8 @@ import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart'; import 'package:cake_wallet/view_model/support_view_model.dart'; import 'package:cake_wallet/view_model/transaction_details_view_model.dart'; import 'package:cake_wallet/view_model/trade_details_view_model.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_model.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/auth_view_model.dart'; @@ -569,5 +572,14 @@ Future setup( unspentCoinsListViewModel: getIt.get() )); + getIt.registerFactoryParam((item, _) => + UnspentCoinsDetailsViewModel(unspentCoinsItem: item)); + + getIt.registerFactoryParam((UnspentCoinsItem item, _) => + UnspentCoinsDetailsPage(unspentCoinsDetailsViewModel: + getIt.get(param1: item))); + _isSetupFinished = true; } diff --git a/lib/router.dart b/lib/router.dart index b8967645e..7ecb95026 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -9,10 +9,12 @@ import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; import 'package:cake_wallet/src/screens/support/support_page.dart'; +import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/wyre/wyre_page.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -360,6 +362,12 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute( builder: (_) => getIt.get()); + case Routes.unspentCoinsDetails: + return MaterialPageRoute( + builder: (_) => + getIt.get( + param1: settings.arguments as UnspentCoinsItem)); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index 84a48d5ea..65f8253a8 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -55,4 +55,5 @@ class Routes { static const orderDetails = '/order_details'; static const wyre = '/wyre'; static const unspentCoinsList = '/unspent_coins_list'; + static const unspentCoinsDetails = '/unspent_coins_details'; } \ No newline at end of file diff --git a/lib/src/screens/unspent_coins/unspent_coins_details_page.dart b/lib/src/screens/unspent_coins/unspent_coins_details_page.dart new file mode 100644 index 000000000..9f73eaaa3 --- /dev/null +++ b/lib/src/screens/unspent_coins/unspent_coins_details_page.dart @@ -0,0 +1,52 @@ +import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart'; +import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_switch_row.dart'; +import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_model.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/widgets/standart_list_row.dart'; +import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class UnspentCoinsDetailsPage extends BasePage { + UnspentCoinsDetailsPage({this.unspentCoinsDetailsViewModel}); + + @override + String get title => 'Unspent coins details'; + + final UnspentCoinsDetailsViewModel unspentCoinsDetailsViewModel; + + @override + Widget body(BuildContext context) { + return SectionStandardList( + sectionCount: 1, + itemCounter: (int _) => unspentCoinsDetailsViewModel.items.length, + itemBuilder: (_, __, index) { + final item = unspentCoinsDetailsViewModel.items[index]; + + if (item is StandartListItem) { + return StandartListRow(title: '${item.title}:', value: item.value); + } + + if (item is TextFieldListItem) { + return TextFieldListRow( + title: item.title, + value: item.value, + onSubmitted: item.onSubmitted, + ); + } + + if (item is UnspentCoinsSwitchItem) { + return Observer(builder: (_) => UnspentCoinsSwitchRow( + title: item.title, + switchValue: item.switchValue(), + onSwitchValueChange: item.onSwitchValueChange + )); + } + + return null; + }); + } +} \ No newline at end of file diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart index cc175eb01..8a7b7de6c 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; @@ -23,7 +24,9 @@ class UnspentCoinsListPage extends BasePage { final item = unspentCoinsListViewModel.items[index]; return GestureDetector( - onTap: () {print('Item taped');}, + onTap: () => + Navigator.of(context).pushNamed(Routes.unspentCoinsDetails, + arguments: item), child: UnspentCoinsListItem( address: item.address, amount: item.amount, diff --git a/lib/src/screens/unspent_coins/widgets/unspent_coins_switch_row.dart b/lib/src/screens/unspent_coins/widgets/unspent_coins_switch_row.dart new file mode 100644 index 000000000..c7c1e81b8 --- /dev/null +++ b/lib/src/screens/unspent_coins/widgets/unspent_coins_switch_row.dart @@ -0,0 +1,45 @@ +import 'package:cake_wallet/src/widgets/standart_switch.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class UnspentCoinsSwitchRow extends StatelessWidget { + UnspentCoinsSwitchRow( + {this.title, + this.titleFontSize = 14, + this.switchValue, + this.onSwitchValueChange}); + + final String title; + final double titleFontSize; + final bool switchValue; + final void Function(bool value) onSwitchValueChange; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + color: Theme.of(context).backgroundColor, + child: Padding( + padding: + const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, + style: TextStyle( + fontSize: titleFontSize, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .primaryTextTheme.overline.color), + textAlign: TextAlign.left), + Padding( + padding: EdgeInsets.only(top: 12), + child: StandartSwitch( + value: switchValue, + onTaped: () => onSwitchValueChange(!switchValue)) + ) + ]), + ), + ); + } +} \ No newline at end of file diff --git a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart new file mode 100644 index 000000000..33db96eba --- /dev/null +++ b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart @@ -0,0 +1,55 @@ +import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart'; +import 'package:mobx/mobx.dart'; + +part 'unspent_coins_details_view_model.g.dart'; + +class UnspentCoinsDetailsViewModel = UnspentCoinsDetailsViewModelBase + with _$UnspentCoinsDetailsViewModel; + +abstract class UnspentCoinsDetailsViewModelBase with Store { + UnspentCoinsDetailsViewModelBase({this.unspentCoinsItem}) { + final amount = unspentCoinsItem.amount ?? ''; + final address = unspentCoinsItem.address ?? ''; + isFrozen = unspentCoinsItem.isFrozen ?? false; + note = unspentCoinsItem.note ?? ''; + + items = [ + StandartListItem( + title: 'Amount', + value: amount + ), + StandartListItem( + title: 'Address', + value: address + ), + TextFieldListItem( + title: S.current.note_tap_to_change, + value: note, + onSubmitted: (value) { + note = value; + }), + UnspentCoinsSwitchItem( + title: 'Freeze', + value: '', + switchValue: () => isFrozen, + onSwitchValueChange: (value) { + isFrozen = value; + } + ) + ]; + } + + @observable + bool isFrozen; + + @observable + String note; + + final UnspentCoinsItem unspentCoinsItem; + List items; +} \ No newline at end of file diff --git a/lib/view_model/unspent_coins/unspent_coins_switch_item.dart b/lib/view_model/unspent_coins/unspent_coins_switch_item.dart new file mode 100644 index 000000000..925f8776e --- /dev/null +++ b/lib/view_model/unspent_coins/unspent_coins_switch_item.dart @@ -0,0 +1,12 @@ +import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; + +class UnspentCoinsSwitchItem extends TransactionDetailsListItem { + UnspentCoinsSwitchItem({ + String title, + String value, + this.switchValue, + this.onSwitchValueChange}) : super(title: title, value: value); + + final bool Function() switchValue; + final void Function(bool value) onSwitchValueChange; +} \ No newline at end of file From b1add664ad5b762618190c3ad8e9e5f4c24600bf Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Mon, 31 May 2021 21:04:00 +0300 Subject: [PATCH 09/28] CAKE-329 | reworked unspent_coins_list_page.dart and unspent_coins_list_item.dart; fixed standard_checkbox.dart; applied unspent coins control only to btc wallet --- lib/palette.dart | 1 + lib/src/screens/send/send_page.dart | 71 +++++----- .../unspent_coins_list_page.dart | 70 +++++++--- .../widgets/unspent_coins_list_item.dart | 121 +++++++++++------- lib/src/widgets/standard_checkbox.dart | 22 ++-- lib/view_model/send/send_view_model.dart | 5 + .../unspent_coins_list_view_model.dart | 37 ++++-- 7 files changed, 209 insertions(+), 118 deletions(-) diff --git a/lib/palette.dart b/lib/palette.dart index 336f7b7f9..301669a24 100644 --- a/lib/palette.dart +++ b/lib/palette.dart @@ -44,6 +44,7 @@ class Palette { static const Color dullGray = Color.fromRGBO(98, 98, 98, 1.0); static const Color protectiveBlue = Color.fromRGBO(33, 148, 255, 1.0); static const Color darkBlue = Color.fromRGBO(109, 128, 178, 1.0); + static const Color paleCornflowerBlue = Color.fromRGBO(185, 196, 237, 1.0); } class PaletteDark { diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 1fd74e130..22ba76836 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -425,7 +425,31 @@ class SendPage extends BasePage { ], ), ), - )) + )), + if (sendViewModel.isBitcoinWallet) Padding( + padding: EdgeInsets.only(top: 6), + child: GestureDetector( + onTap: () => Navigator.of(context) + .pushNamed(Routes.unspentCoinsList), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Coin control (optional)', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.white)), + Icon( + Icons.arrow_forward_ios, + size: 12, + color: Colors.white, + ) + ], + ) + ) + ) ], ), ) @@ -558,35 +582,22 @@ class SendPage extends BasePage { ), bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), - bottomSection: Column( - children: [ - Observer(builder: (_) { - return LoadingPrimaryButton( - onPressed: () async { - if (_formKey.currentState.validate()) { - await sendViewModel.createTransaction(); - } - }, - text: S.of(context).send, - color: Theme.of(context).accentTextTheme.body2.color, - textColor: Colors.white, - isLoading: sendViewModel.state is IsExecutingState || - sendViewModel.state is TransactionCommitting, - isDisabled: - false // FIXME !(syncStore.status is SyncedSyncStatus), - ); - }), - Padding( - padding: EdgeInsets.only(top: 12), - child: PrimaryButton( - onPressed: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList), - text: 'Unspent coins', - color: Colors.green, - textColor: Colors.white, - ) - ) - ], - )), + bottomSection: Observer(builder: (_) { + return LoadingPrimaryButton( + onPressed: () async { + if (_formKey.currentState.validate()) { + await sendViewModel.createTransaction(); + } + }, + text: S.of(context).send, + color: Theme.of(context).accentTextTheme.body2.color, + textColor: Colors.white, + isLoading: sendViewModel.state is IsExecutingState || + sendViewModel.state is TransactionCommitting, + isDisabled: + false // FIXME !(syncStore.status is SyncedSyncStatus), + ); + })), )); } diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart index cc175eb01..81dbaf104 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -1,10 +1,12 @@ import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/generated/i18n.dart'; class UnspentCoinsListPage extends BasePage { UnspentCoinsListPage({this.unspentCoinsListViewModel}); @@ -12,27 +14,59 @@ class UnspentCoinsListPage extends BasePage { @override String get title => 'Unspent coins'; + @override + Widget trailing(BuildContext context) { + final questionImage = Image.asset('assets/images/question_mark.png', + color: Theme.of(context).primaryTextTheme.title.color); + + return SizedBox( + height: 20.0, + width: 20.0, + child: ButtonTheme( + minWidth: double.minPositive, + child: FlatButton( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + padding: EdgeInsets.all(0), + onPressed: () => showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: '', + alertContent: 'Information about unspent coins', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }), + child: questionImage), + ), + ); + } + final UnspentCoinsListViewModel unspentCoinsListViewModel; @override Widget body(BuildContext context) { - return Observer(builder: (_) => SectionStandardList( - sectionCount: 1, - itemCounter: (int _) => unspentCoinsListViewModel.items.length, - itemBuilder: (_, __, index) { - final item = unspentCoinsListViewModel.items[index]; + return Container( + padding: EdgeInsets.fromLTRB(24, 12, 24, 24), + child: Observer( + builder: (_) => ListView.separated( + itemCount: unspentCoinsListViewModel.items.length, + separatorBuilder: (_, __) => + SizedBox(height: 15), + itemBuilder: (_, int index) { + final item = unspentCoinsListViewModel.items[index]; - return GestureDetector( - onTap: () {print('Item taped');}, - child: UnspentCoinsListItem( - address: item.address, - amount: item.amount, - isFrozen: item.isFrozen, - note: item.note, - isSending: item.isSending, - onCheckBoxTap: (value) {print('CheckBox taped');}, - )); - })); + return GestureDetector( + onTap: () {print('Item taped');}, + child: UnspentCoinsListItem( + address: item.address, + amount: item.amount, + isSending: item.isSending, + onCheckBoxTap: (value) {}, + )); + } + ) + ) + ); } - } \ No newline at end of file diff --git a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart index eb8ba33c0..790e19c77 100644 --- a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart +++ b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart @@ -1,74 +1,101 @@ import 'package:auto_size_text/auto_size_text.dart'; +import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; -class UnspentCoinsListItem extends StatelessWidget { +class UnspentCoinsListItem extends StatefulWidget { UnspentCoinsListItem({ @required this.address, @required this.amount, - @required this.isFrozen, - @required this.note, @required this.isSending, @required this.onCheckBoxTap, -}); + }); final String address; final String amount; - final bool isFrozen; - final String note; final bool isSending; final Function(bool) onCheckBoxTap; + @override UnspentCoinsListItemState createState() => + UnspentCoinsListItemState( + address: address, + amount: amount, + isSending: isSending, + onCheckBoxTap: onCheckBoxTap + ); + +} + +class UnspentCoinsListItemState extends State { + UnspentCoinsListItemState({ + @required this.address, + @required this.amount, + @required this.isSending, + @required this.onCheckBoxTap, + }) : checkBoxValue = isSending; + + static const amountColor = Palette.darkBlueCraiola; + static const addressColor = Palette.darkGray; + static const selectedItemColor = Palette.paleCornflowerBlue; + static const unselectedItemColor = Palette.moderateLavender; + + final String address; + final String amount; + final bool isSending; + final Function(bool) onCheckBoxTap; + + bool checkBoxValue; + @override Widget build(BuildContext context) { - final textStyle = TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context) - .primaryTextTheme - .title - .color); + final itemColor = checkBoxValue? selectedItemColor : unselectedItemColor; return Container( - padding: EdgeInsets.fromLTRB(24, 16, 24, 16), - color: Theme.of(context).backgroundColor, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + height: 62, + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(12)), + color: itemColor), + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - address ?? 'Address', - style: textStyle, - ), Padding( - padding: EdgeInsets.only(top: 12), - child: Text( - amount ?? 'Amount', - style: textStyle, - ) - ), - if (isFrozen ?? false) Padding( - padding: EdgeInsets.only(top: 12), - child: Text( - 'Freeze', - style: textStyle, - ) - ), - if (note?.isNotEmpty ?? false) Padding( - padding: EdgeInsets.only(top: 12), - child: AutoSizeText( - note, - style: textStyle, - maxLines: 1 - ) - ), - Padding( - padding: EdgeInsets.only(top: 12), + padding: EdgeInsets.only(right: 12), child: StandardCheckbox( - value: isSending, - caption: 'Sending', - onChanged: onCheckBoxTap + value: checkBoxValue, + onChanged: (value) { + onCheckBoxTap(value); + checkBoxValue = value; + setState(() {}); + } ) + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AutoSizeText( + amount ?? 'Amount', + style: TextStyle( + color: amountColor, + fontSize: 16, + fontWeight: FontWeight.w600 + ), + maxLines: 1, + ), + AutoSizeText( + address ?? 'Address', + style: TextStyle( + color: addressColor, + fontSize: 12, + ), + maxLines: 1, + ) + ] + ) ) ], ) diff --git a/lib/src/widgets/standard_checkbox.dart b/lib/src/widgets/standard_checkbox.dart index 64ae95acc..9deec340b 100644 --- a/lib/src/widgets/standard_checkbox.dart +++ b/lib/src/widgets/standard_checkbox.dart @@ -44,9 +44,6 @@ class StandardCheckboxState extends State { Container( height: 24.0, width: 24.0, - margin: EdgeInsets.only( - right: 10.0, - ), decoration: BoxDecoration( border: Border.all( color: Theme.of(context) @@ -65,14 +62,17 @@ class StandardCheckboxState extends State { ) : Offstage(), ), - Text( - caption, - style: TextStyle( - fontSize: 16.0, - color: Theme.of(context) - .primaryTextTheme - .title - .color), + if (caption.isNotEmpty) Padding( + padding: EdgeInsets.only(left: 10), + child: Text( + caption, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context) + .primaryTextTheme + .title + .color), + ) ) ], ), diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 689032223..a9e6aaf93 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -55,6 +55,8 @@ abstract class SendViewModelBase with Store { _settingsStore.priority[_wallet.type] = priorities.first; } + isBitcoinWallet = _wallet is BitcoinWallet; + _setCryptoNumMaximumFractionDigits(); } @@ -182,6 +184,9 @@ abstract class SendViewModelBase with Store { @observable PendingTransaction pendingTransaction; + @observable + bool isBitcoinWallet; + @computed String get balance => _wallet.balance.formattedAvailableBalance ?? '0.0'; diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart index 5f631e52a..25167f785 100644 --- a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -5,32 +5,45 @@ part 'unspent_coins_list_view_model.g.dart'; const List> unspentCoinsMap = [ { - "address" : "11111111111111121111132432432432432432432443124324324234324324324324332424", - "amount" : "222", + "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr", + "amount" : "0.00358 BTC", "isFrozen" : true, "note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"}, { - "address" : "444", - "amount" : "555", + "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr", + "amount" : "0.00567894 BTC", "note" : "sfjskf"}, { - "address" : "777", - "amount" : "888", + "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr", + "amount" : "0.00087 BTC", "isFrozen" : false}, { - "address" : "11111111111111121111132432432432432432432443124324324234324324324324332424", - "amount" : "222", + "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr", + "amount" : "0.00012 BTC", "isFrozen" : true, "note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"}, { - "address" : "444", - "amount" : "555", + "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr", + "amount" : "0.00574 BTC", "note" : "sffsfsdsgs"}, { - "address" : "777", - "amount" : "888", + "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr", + "amount" : "0.000482 BTC", "isFrozen" : false}, {}, + { + "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr", + "amount" : "0.00012 BTC", + "isFrozen" : true, + "note" : "333cvgf23132132132132131321321314rwrtdggfdddewq ewqasfdxgdhgfgfszczcxgbhhhbcgbc"}, + { + "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr", + "amount" : "0.00574 BTC", + "note" : "sffsfsdsgs"}, + { + "address" : "bc1qm80mu5p3mf04a7cj7teymasf04dwpc3av2fwtr", + "amount" : "0.000482 BTC", + "isFrozen" : false}, ]; class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel; From 61375468a5ff43f964de4d77650faa92b7030143 Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Wed, 2 Jun 2021 17:36:12 +0300 Subject: [PATCH 10/28] CAKE-329 | show unspent coins alert when unspent coins list page is started --- .../unspent_coins_list_page.dart | 92 +++++++++++++------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart index 81dbaf104..d5c5eff05 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -28,15 +28,7 @@ class UnspentCoinsListPage extends BasePage { highlightColor: Colors.transparent, splashColor: Colors.transparent, padding: EdgeInsets.all(0), - onPressed: () => showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: '', - alertContent: 'Information about unspent coins', - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }), + onPressed: () => showUnspentCoinsAlert(context), child: questionImage), ), ); @@ -45,28 +37,70 @@ class UnspentCoinsListPage extends BasePage { final UnspentCoinsListViewModel unspentCoinsListViewModel; @override - Widget body(BuildContext context) { - return Container( - padding: EdgeInsets.fromLTRB(24, 12, 24, 24), - child: Observer( - builder: (_) => ListView.separated( - itemCount: unspentCoinsListViewModel.items.length, - separatorBuilder: (_, __) => - SizedBox(height: 15), - itemBuilder: (_, int index) { - final item = unspentCoinsListViewModel.items[index]; + Widget body(BuildContext context) => + UnspentCoinsListForm(unspentCoinsListViewModel); +} - return GestureDetector( - onTap: () {print('Item taped');}, - child: UnspentCoinsListItem( - address: item.address, - amount: item.amount, - isSending: item.isSending, - onCheckBoxTap: (value) {}, - )); - } +class UnspentCoinsListForm extends StatefulWidget { + UnspentCoinsListForm(this.unspentCoinsListViewModel); + + final UnspentCoinsListViewModel unspentCoinsListViewModel; + + @override + UnspentCoinsListFormState createState() => + UnspentCoinsListFormState(unspentCoinsListViewModel); +} + +class UnspentCoinsListFormState extends State { + UnspentCoinsListFormState(this.unspentCoinsListViewModel); + + final UnspentCoinsListViewModel unspentCoinsListViewModel; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback(afterLayout); + } + + void afterLayout(dynamic _) { + showUnspentCoinsAlert(context); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.fromLTRB(24, 12, 24, 24), + child: Observer( + builder: (_) => ListView.separated( + itemCount: unspentCoinsListViewModel.items.length, + separatorBuilder: (_, __) => + SizedBox(height: 15), + itemBuilder: (_, int index) { + final item = unspentCoinsListViewModel.items[index]; + + return GestureDetector( + onTap: () {print('Item taped');}, + child: UnspentCoinsListItem( + address: item.address, + amount: item.amount, + isSending: item.isSending, + onCheckBoxTap: (value) {}, + )); + } + ) ) - ) ); } +} + +void showUnspentCoinsAlert(BuildContext context) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: '', + alertContent: 'Information about unspent coins', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); } \ No newline at end of file From 52b1abcbdb47be080b31b615685589a19191126a Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Wed, 2 Jun 2021 19:54:25 +0300 Subject: [PATCH 11/28] CAKE-330 | merged CAKE-329 branch into current --- lib/src/screens/unspent_coins/unspent_coins_details_page.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/screens/unspent_coins/unspent_coins_details_page.dart b/lib/src/screens/unspent_coins/unspent_coins_details_page.dart index 9f73eaaa3..f5e0f8991 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_details_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_details_page.dart @@ -27,7 +27,9 @@ class UnspentCoinsDetailsPage extends BasePage { final item = unspentCoinsDetailsViewModel.items[index]; if (item is StandartListItem) { - return StandartListRow(title: '${item.title}:', value: item.value); + return StandartListRow( + title: '${item.title}:', + value: item.value); } if (item is TextFieldListItem) { From 7ddbc6b8f420cea82b10291b7408ffdfaf617fdc Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Tue, 15 Jun 2021 22:43:50 +0300 Subject: [PATCH 12/28] CAKE-192 | added verification of open alias and unstoppable domains in exchange_page.dart and send_page.dart --- lib/src/screens/exchange/exchange_page.dart | 23 +++++++++------ lib/src/screens/send/send_page.dart | 31 +++++++++++++++------ res/values/strings_de.arb | 2 +- res/values/strings_en.arb | 2 +- 4 files changed, 39 insertions(+), 19 deletions(-) diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 853ff9876..7b57c251f 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -778,15 +778,22 @@ class ExchangePage extends BasePage { Future applyUnstoppableDomainAddress(BuildContext context, String domain, String ticker) async { - try { - final address = - await exchangeViewModel.getUnstoppableDomainAddress(domain, ticker); - if ((address != null)&&address.isNotEmpty) { - unstoppableDomainAddressAlert(context, domain); - return address; + const topLevelDomain = 'crypto'; + + if (domain.contains('.')) { + final name = domain.split('.').last; + if (name.isNotEmpty && (name == topLevelDomain)) { + try { + final address = + await exchangeViewModel.getUnstoppableDomainAddress(domain, ticker); + if ((address != null)&&address.isNotEmpty) { + unstoppableDomainAddressAlert(context, domain); + return address; + } + } catch (e) { + print(e.toString()); + } } - } catch (e) { - print(e.toString()); } return domain; diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index e40657b1d..c3072b725 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -38,10 +38,9 @@ class SendPage extends BasePage { _cryptoAmountFocus = FocusNode(), _fiatAmountFocus = FocusNode(), _addressFocusNode = FocusNode() { - _addressFocusNode.addListener(() async { + _addressFocusNode.addListener(() { if (!_addressFocusNode.hasFocus && _addressController.text.isNotEmpty) { - await getOpenaliasRecord(_addressFocusNode.context); - await applyUnstoppableDomainAddress(_addressFocusNode.context); + applyOpenaliasOrUnstoppableDomains(_addressFocusNode.context); } }); } @@ -174,9 +173,8 @@ class SendPage extends BasePage { .headline .decorationColor), validator: sendViewModel.addressValidator, - onPushPasteButton: (context) async { - await getOpenaliasRecord(context); - await applyUnstoppableDomainAddress(context); + onPushPasteButton: (context) { + applyOpenaliasOrUnstoppableDomains(context); }, ), Observer( @@ -518,13 +516,12 @@ class SendPage extends BasePage { to: template.name, amount: template.amount, from: template.cryptoCurrency, - onTap: () async { + onTap: () { _addressController.text = template.address; _cryptoAmountController.text = template.amount; - await getOpenaliasRecord(context); - await applyUnstoppableDomainAddress(context); + applyOpenaliasOrUnstoppableDomains(context); }, onRemove: () { showPopUp( @@ -792,4 +789,20 @@ class SendPage extends BasePage { print(e.toString()); } } + + void applyOpenaliasOrUnstoppableDomains(BuildContext context) async { + const topLevelDomain = 'crypto'; + final address = _addressController.text; + + if (address.contains('.')) { + final name = address.split('.').last; + if (name.isNotEmpty) { + if (name == topLevelDomain) { + await applyUnstoppableDomainAddress(context); + } else { + await getOpenaliasRecord(context); + } + } + } + } } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 5df364ff8..99c6f53b9 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -480,7 +480,7 @@ "moonpay_alert_text" : "Der Wert des Betrags muss größer oder gleich sein ${minAmount} ${fiatCurrency}", "outdated_electrum_wallet_receive_warning": "Wenn diese Brieftasche einen 12-Wort-Seed hat und in Cake erstellt wurde, zahlen Sie KEINE Bitcoins in diese Brieftasche ein. Alle auf diese Wallet übertragenen BTC können verloren gehen. Erstellen Sie eine neue 24-Wort-Wallet (tippen Sie auf das Menü oben rechts, wählen Sie Wallets, wählen Sie Create New Wallet und dann Bitcoin) und verschieben Sie Ihre BTC SOFORT dorthin. Neue (24 Wörter) BTC-Wallets von Cake sind sicher", - "do_not_show_me": "Zeig mir das nicht noch einmal" + "do_not_show_me": "Zeig mir das nicht noch einmal", "outdated_electrum_wallet_description" : "Neue Bitcoin-Geldbörsen, die in Cake erstellt wurden, haben jetzt einen Startwert von 24 Wörtern. Es ist obligatorisch, dass Sie eine neue Bitcoin-Brieftasche erstellen, Ihr gesamtes Geld in die neue 24-Wörter-Brieftasche überweisen und keine Brieftaschen mit einem 12-Wörter-Startwert mehr verwenden. Bitte tun Sie dies sofort, um Ihr Geld zu sichern.", "understand" : "Ich verstehe", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 3dd03a13c..922e00f6a 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -483,7 +483,7 @@ "moonpay_alert_text" : "Value of the amount must be more or equal to ${minAmount} ${fiatCurrency}", "outdated_electrum_wallet_receive_warning": "If this wallet has a 12-word seed and was created in Cake, DO NOT deposit Bitcoin into this wallet. Any BTC transferred to this wallet may be lost. Create a new 24-word wallet (tap the menu at the top right, select Wallets, choose Create New Wallet, then select Bitcoin) and IMMEDIATELY move your BTC there. New (24-word) BTC wallets from Cake are secure", - "do_not_show_me": "Do not show me this again" + "do_not_show_me": "Do not show me this again", "address_detected" : "Address detected", "address_from_domain" : "You got address from unstoppable domain ${domain}" From 3a5932f3dfc629209536004f7fae26d2d2546268 Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Wed, 16 Jun 2021 11:44:20 +0300 Subject: [PATCH 13/28] CAKE-192 | renamed applyUnstoppableDomainAddress() to getUnstoppableDomainAddress() in the send_page.dart; fixed applyOpenaliasOrUnstoppableDomains() (send_page.dart) and applyUnstoppableDomainAddress() (exchange_page.dart) --- lib/src/screens/exchange/exchange_page.dart | 2 +- lib/src/screens/send/send_page.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 7b57c251f..aa81bd1d4 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -782,7 +782,7 @@ class ExchangePage extends BasePage { if (domain.contains('.')) { final name = domain.split('.').last; - if (name.isNotEmpty && (name == topLevelDomain)) { + if (name == topLevelDomain) { try { final address = await exchangeViewModel.getUnstoppableDomainAddress(domain, ticker); diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index c3072b725..5a755f26d 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -774,7 +774,7 @@ class SendPage extends BasePage { context: context); } - Future applyUnstoppableDomainAddress(BuildContext context) async { + Future getUnstoppableDomainAddress(BuildContext context) async { try { final address = await sendViewModel .getUnstoppableDomainAddress( @@ -798,7 +798,7 @@ class SendPage extends BasePage { final name = address.split('.').last; if (name.isNotEmpty) { if (name == topLevelDomain) { - await applyUnstoppableDomainAddress(context); + await getUnstoppableDomainAddress(context); } else { await getOpenaliasRecord(context); } From 6709b7779a9692cfafd8186439b7e9ca1dfdfde0 Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Fri, 18 Jun 2021 15:36:49 +0300 Subject: [PATCH 14/28] CAKE-343 | applied subaddresses for contact book; added addresses field to wallet_info.dart; added updateAddressesInfo() to wallet_base.dart; applied updateAddressesInfo() to wallet services and view models --- lib/bitcoin/bitcoin_wallet_service.dart | 1 + lib/bitcoin/electrum_wallet.dart | 2 ++ lib/bitcoin/litecoin_wallet_service.dart | 1 + lib/core/wallet_base.dart | 9 +++++++ lib/di.dart | 1 + lib/entities/wallet_info.dart | 3 +++ lib/monero/monero_wallet.dart | 22 +++++++++++++++++ lib/monero/monero_wallet_service.dart | 1 + .../screens/contact/contact_list_page.dart | 24 ++++++++++--------- .../contact_list/contact_list_view_model.dart | 22 +++++++++++++---- ...ero_account_edit_or_create_view_model.dart | 10 ++++++-- ...let_address_edit_or_create_view_model.dart | 2 ++ lib/view_model/wallet_creation_vm.dart | 1 + 13 files changed, 81 insertions(+), 18 deletions(-) diff --git a/lib/bitcoin/bitcoin_wallet_service.dart b/lib/bitcoin/bitcoin_wallet_service.dart index aefe0fadf..2cd3310fd 100644 --- a/lib/bitcoin/bitcoin_wallet_service.dart +++ b/lib/bitcoin/bitcoin_wallet_service.dart @@ -44,6 +44,7 @@ class BitcoinWalletService extends WalletService< final wallet = await BitcoinWalletBase.open( password: password, name: name, walletInfo: walletInfo); await wallet.init(); + await wallet.updateAddressesInfo(); return wallet; } diff --git a/lib/bitcoin/electrum_wallet.dart b/lib/bitcoin/electrum_wallet.dart index bf3dfd542..bb0ee3c3e 100644 --- a/lib/bitcoin/electrum_wallet.dart +++ b/lib/bitcoin/electrum_wallet.dart @@ -125,6 +125,8 @@ abstract class ElectrumWalletBase extends WalletBase rescan({int height}); void close(); + + Future updateAddressesInfo() async { + try { + walletInfo.address = address; + await walletInfo.save(); + } catch (e) { + print(e.toString()); + } + } } diff --git a/lib/di.dart b/lib/di.dart index 37b6c5968..1f5350370 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -362,6 +362,7 @@ Future setup( AccountListItem, void>( (AccountListItem account, _) => MoneroAccountEditOrCreateViewModel( (getIt.get().wallet as MoneroWallet).accountList, + wallet: getIt.get().wallet, accountListItem: account)); getIt.registerFactoryParam addresses; + DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp); } diff --git a/lib/monero/monero_wallet.dart b/lib/monero/monero_wallet.dart index e902f1080..753dff53b 100644 --- a/lib/monero/monero_wallet.dart +++ b/lib/monero/monero_wallet.dart @@ -445,4 +445,26 @@ abstract class MoneroWalletBase extends WalletBase updateAddressesInfo() async { + final Map _addresses = {}; + final _subaddressList = MoneroSubaddressList(); + + accountList.accounts.forEach((account) { + _subaddressList.update(accountIndex: account.id); + _subaddressList.subaddresses.forEach((subaddress) { + _addresses.addAll({subaddress.address:subaddress.label}); + }); + }); + + try { + walletInfo.addresses = _addresses; + walletInfo.address = address; + + await walletInfo.save(); + } catch (e) { + print(e.toString()); + } + } } diff --git a/lib/monero/monero_wallet_service.dart b/lib/monero/monero_wallet_service.dart index 7795b8700..17b02dcbb 100644 --- a/lib/monero/monero_wallet_service.dart +++ b/lib/monero/monero_wallet_service.dart @@ -126,6 +126,7 @@ class MoneroWalletService extends WalletService< } await wallet.init(); + await wallet.updateAddressesInfo(); return wallet; } catch (e) { diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index bf2b593ee..fd126981a 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -151,17 +151,19 @@ class ContactListPage extends BasePage { crossAxisAlignment: CrossAxisAlignment.center, children: [ image ?? Offstage(), - Padding( - padding: image != null - ? EdgeInsets.only(left: 12) - : EdgeInsets.only(left: 0), - child: Text( - contact.name, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context).primaryTextTheme.title.color), - ), + Expanded( + child: Padding( + padding: image != null + ? EdgeInsets.only(left: 12) + : EdgeInsets.only(left: 0), + child: Text( + contact.name, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).primaryTextTheme.title.color), + ), + ) ) ], ), diff --git a/lib/view_model/contact_list/contact_list_view_model.dart b/lib/view_model/contact_list/contact_list_view_model.dart index 17083efe2..fd6196c89 100644 --- a/lib/view_model/contact_list/contact_list_view_model.dart +++ b/lib/view_model/contact_list/contact_list_view_model.dart @@ -16,11 +16,23 @@ class ContactListViewModel = ContactListViewModelBase abstract class ContactListViewModelBase with Store { ContactListViewModelBase(this.contactSource, this.walletInfoSource) : contacts = ObservableList(), - walletContacts = walletInfoSource.values - .where((info) => info.address?.isNotEmpty ?? false) - .map((info) => WalletContact( - info.address, info.name, walletTypeToCryptoCurrency(info.type))) - .toList() { + walletContacts = [] { + walletInfoSource.values.forEach((info) { + if (info.address?.isNotEmpty ?? false) { + if (info.addresses != null) { + info.addresses.forEach((address, label) { + walletContacts.add(WalletContact( + address, + info.name + ' ($label)', + walletTypeToCryptoCurrency(info.type))); + }); + } else { + walletContacts.add(WalletContact(info.address, info.name, + walletTypeToCryptoCurrency(info.type))); + } + } + }); + _subscription = contactSource.bindToListWithTransform( contacts, (Contact contact) => ContactRecord(contactSource, contact), initialFire: true); diff --git a/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart b/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart index d32f8eadd..cf999e414 100644 --- a/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart +++ b/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/core/wallet_base.dart'; +import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/monero/monero_account_list.dart'; @@ -10,11 +12,12 @@ class MoneroAccountEditOrCreateViewModel = MoneroAccountEditOrCreateViewModelBas abstract class MoneroAccountEditOrCreateViewModelBase with Store { MoneroAccountEditOrCreateViewModelBase(this._moneroAccountList, - {AccountListItem accountListItem}) + {@required WalletBase wallet, AccountListItem accountListItem}) : state = InitialExecutionState(), isEdit = accountListItem != null, label = accountListItem?.label??'', - _accountListItem = accountListItem; + _accountListItem = accountListItem, + _wallet = wallet; final bool isEdit; @@ -26,6 +29,7 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store { final MoneroAccountList _moneroAccountList; final AccountListItem _accountListItem; + final WalletBase _wallet; Future save() async { try { @@ -38,6 +42,8 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store { await _moneroAccountList.addAccount(label: label); } + await _wallet.updateAddressesInfo(); + state = ExecutedSuccessfullyState(); } catch (e) { state = FailureState(e.toString()); 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 b018c542d..6b6873cd5 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 @@ -54,6 +54,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { await _createNew(); } + await _wallet.updateAddressesInfo(); + state = AddressSavedSuccessfully(); } catch (e) { state = AddressEditOrCreateStateFailure(error: e.toString()); diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index 58ee24087..54cad29c9 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -54,6 +54,7 @@ abstract class WalletCreationVMBase with Store { await _walletInfoSource.add(walletInfo); _appStore.changeCurrentWallet(wallet); _appStore.authenticationStore.allowed(); + await wallet.updateAddressesInfo(); state = ExecutedSuccessfullyState(); } catch (e) { state = FailureState(e.toString()); From bb219e4da27b9cbf5bcd6198be6e1e653b5fc9ac Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Tue, 22 Jun 2021 11:29:30 +0300 Subject: [PATCH 15/28] CAKE-192 | created parse_address_from_domain.dart; applied parseAddressFromDomain() to send_page.dart and exchange_page.dart; deleted unstoppable_domain_address_alert.dart --- lib/src/screens/exchange/exchange_page.dart | 53 +++++------------- .../send/parse_address_from_domain.dart | 53 ++++++++++++++++++ lib/src/screens/send/send_page.dart | 55 ++----------------- .../unstoppable_domain_address_alert.dart | 17 ------ .../exchange/exchange_view_model.dart | 5 -- lib/view_model/send/send_view_model.dart | 15 ----- res/values/strings_zh.arb | 8 +-- 7 files changed, 77 insertions(+), 129 deletions(-) create mode 100644 lib/src/screens/send/parse_address_from_domain.dart delete mode 100644 lib/src/screens/send/widgets/unstoppable_domain_address_alert.dart diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index aa81bd1d4..fa69d5da2 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -1,7 +1,7 @@ import 'dart:ui'; import 'package:cake_wallet/entities/sync_status.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; -import 'package:cake_wallet/src/screens/send/widgets/unstoppable_domain_address_alert.dart'; +import 'package:cake_wallet/src/screens/send/parse_address_from_domain.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:flutter/cupertino.dart'; @@ -226,10 +226,10 @@ class ExchangePage extends BasePage { onPushPasteButton: (context) async { final domain = exchangeViewModel.depositAddress; - final ticker = - exchangeViewModel.depositCurrency.title; + final ticker = exchangeViewModel + .depositCurrency.title.toLowerCase(); exchangeViewModel.depositAddress = - await applyUnstoppableDomainAddress( + await parseAddressFromDomain( context, domain, ticker); }, ), @@ -281,10 +281,10 @@ class ExchangePage extends BasePage { onPushPasteButton: (context) async { final domain = exchangeViewModel.receiveAddress; - final ticker = - exchangeViewModel.receiveCurrency.title; + final ticker = exchangeViewModel + .receiveCurrency.title.toLowerCase(); exchangeViewModel.receiveAddress = - await applyUnstoppableDomainAddress( + await parseAddressFromDomain( context, domain, ticker); }, )), @@ -512,14 +512,14 @@ class ExchangePage extends BasePage { exchangeViewModel.isFixedRateMode = false; var domain = template.depositAddress; - var ticker = template.depositCurrency; + var ticker = template.depositCurrency.toLowerCase(); exchangeViewModel.depositAddress = - await applyUnstoppableDomainAddress(context, domain, ticker); + await parseAddressFromDomain(context, domain, ticker); domain = template.receiveAddress; - ticker = template.receiveCurrency; + ticker = template.receiveCurrency.toLowerCase(); exchangeViewModel.receiveAddress = - await applyUnstoppableDomainAddress(context, domain, ticker); + await parseAddressFromDomain(context, domain, ticker); } void _setReactions( @@ -689,9 +689,9 @@ class ExchangePage extends BasePage { if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) { final domain = depositAddressController.text; - final ticker = exchangeViewModel.depositCurrency.title; + final ticker = exchangeViewModel.depositCurrency.title.toLowerCase(); exchangeViewModel.depositAddress = - await applyUnstoppableDomainAddress(context, domain, ticker); + await parseAddressFromDomain(context, domain, ticker); } }); @@ -699,9 +699,9 @@ class ExchangePage extends BasePage { if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) { final domain = receiveAddressController.text; - final ticker = exchangeViewModel.receiveCurrency.title; + final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase(); exchangeViewModel.receiveAddress = - await applyUnstoppableDomainAddress(context, domain, ticker); + await parseAddressFromDomain(context, domain, ticker); } }); @@ -775,27 +775,4 @@ class ExchangePage extends BasePage { key.currentState.addressController.text = null; } } - - Future applyUnstoppableDomainAddress(BuildContext context, - String domain, String ticker) async { - const topLevelDomain = 'crypto'; - - if (domain.contains('.')) { - final name = domain.split('.').last; - if (name == topLevelDomain) { - try { - final address = - await exchangeViewModel.getUnstoppableDomainAddress(domain, ticker); - if ((address != null)&&address.isNotEmpty) { - unstoppableDomainAddressAlert(context, domain); - return address; - } - } catch (e) { - print(e.toString()); - } - } - } - - return domain; - } } diff --git a/lib/src/screens/send/parse_address_from_domain.dart b/lib/src/screens/send/parse_address_from_domain.dart new file mode 100644 index 000000000..b114ef88f --- /dev/null +++ b/lib/src/screens/send/parse_address_from_domain.dart @@ -0,0 +1,53 @@ +import 'package:cake_wallet/entities/openalias_record.dart'; +import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +const topLevelDomain = 'crypto'; + +Future parseAddressFromDomain( + BuildContext context, String domain, String ticker) async { + try { + final name = domain.split('.').last; + + if (name.contains(topLevelDomain)) { + final address = await fetchUnstoppableDomainAddress(domain, ticker); + if (address.isNotEmpty) { + showAddressAlert( + context, + S.of(context).address_detected, + S.of(context).address_from_domain(domain)); + return address; + } + } else if (name.isNotEmpty) { + final record = await OpenaliasRecord.fetchAddressAndName( + OpenaliasRecord.formatDomainName(domain)); + if (record.name != null && record.name != domain) { + showAddressAlert( + context, + S.of(context).openalias_alert_title, + S.of(context).openalias_alert_content(domain)); + return record.address; + } + } + } catch (e) { + print(e.toString()); + } + + return domain; +} + +void showAddressAlert(BuildContext context, String title, String content) async { + await showPopUp( + context: context, + builder: (BuildContext context) { + + return AlertWithOneAction( + alertTitle: title, + alertContent: content, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); +} \ No newline at end of file diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 5a755f26d..92b6c54d1 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -1,6 +1,6 @@ import 'dart:ui'; import 'package:cake_wallet/entities/transaction_priority.dart'; -import 'package:cake_wallet/src/screens/send/widgets/unstoppable_domain_address_alert.dart'; +import 'package:cake_wallet/src/screens/send/parse_address_from_domain.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; @@ -736,26 +736,6 @@ class SendPage extends BasePage { _effectsInstalled = true; } - Future getOpenaliasRecord(BuildContext context) async { - final record = - await sendViewModel.decodeOpenaliasRecord(_addressController.text); - - if (record != null) { - _addressController.text = record.address; - - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).openalias_alert_title, - alertContent: - S.of(context).openalias_alert_content(record.name), - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); - } - } - Future _setTransactionPriority(BuildContext context) async { final items = priorityForWalletType(sendViewModel.walletType); final selectedItem = items.indexOf(sendViewModel.transactionPriority); @@ -774,35 +754,10 @@ class SendPage extends BasePage { context: context); } - Future getUnstoppableDomainAddress(BuildContext context) async { - try { - final address = await sendViewModel - .getUnstoppableDomainAddress( - _addressController.text); - - if ((address != null)&&address.isNotEmpty) { - unstoppableDomainAddressAlert( - context, _addressController.text); - _addressController.text = address; - } - } catch (e) { - print(e.toString()); - } - } - void applyOpenaliasOrUnstoppableDomains(BuildContext context) async { - const topLevelDomain = 'crypto'; - final address = _addressController.text; - - if (address.contains('.')) { - final name = address.split('.').last; - if (name.isNotEmpty) { - if (name == topLevelDomain) { - await getUnstoppableDomainAddress(context); - } else { - await getOpenaliasRecord(context); - } - } - } + final domain = _addressController.text; + final ticker = sendViewModel.currency.title.toLowerCase(); + _addressController.text = + await parseAddressFromDomain(context, domain, ticker); } } diff --git a/lib/src/screens/send/widgets/unstoppable_domain_address_alert.dart b/lib/src/screens/send/widgets/unstoppable_domain_address_alert.dart deleted file mode 100644 index 011a36794..000000000 --- a/lib/src/screens/send/widgets/unstoppable_domain_address_alert.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:flutter/material.dart'; -import 'package:cake_wallet/generated/i18n.dart'; - -void unstoppableDomainAddressAlert(BuildContext context, String domain) async { - await showPopUp( - context: context, - builder: (BuildContext context) { - - return AlertWithOneAction( - alertTitle: S.of(context).address_detected, - alertContent: S.of(context).address_from_domain(domain), - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); -} \ No newline at end of file diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 7f0f601ab..f3f00cd77 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -4,7 +4,6 @@ import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/entities/sync_status.dart'; -import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/exchange/limits.dart'; @@ -423,8 +422,4 @@ abstract class ExchangeViewModelBase with Store { }*/ isReceiveAmountEditable = false; } - - Future getUnstoppableDomainAddress(String domain, String ticker) async { - return await fetchUnstoppableDomainAddress(domain, ticker.toLowerCase()); - } } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 850ec0b3b..3fdcbc526 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -1,17 +1,14 @@ import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; import 'package:cake_wallet/bitcoin/electrum_wallet.dart'; -import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/entities/transaction_priority.dart'; -import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/monero/monero_amount_format.dart'; import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; import 'package:hive/hive.dart'; import 'package:intl/intl.dart'; import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/store/templates/send_template_store.dart'; import 'package:cake_wallet/core/template_validator.dart'; @@ -21,7 +18,6 @@ import 'package:cake_wallet/core/pending_transaction.dart'; import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/execution_state.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cake_wallet/monero/monero_wallet.dart'; import 'package:cake_wallet/monero/monero_transaction_creation_credentials.dart'; @@ -265,17 +261,6 @@ abstract class SendViewModelBase with Store { void setTransactionPriority(TransactionPriority priority) => _settingsStore.priority[_wallet.type] = priority; - Future decodeOpenaliasRecord(String name) async { - final record = await OpenaliasRecord.fetchAddressAndName( - OpenaliasRecord.formatDomainName(name)); - - return record.name != name ? record : null; - } - - Future getUnstoppableDomainAddress(String domain) async { - return await fetchUnstoppableDomainAddress(domain, currency.title.toLowerCase()); - } - @action void _updateFiatAmount() { try { diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index b5a76f9a4..d7867ca4f 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -482,9 +482,9 @@ "buy_with" : "一起购买", "moonpay_alert_text" : "金额的价值必须大于或等于 ${minAmount} ${fiatCurrency}", - "address_detected" : "檢測到地址", - "address_from_domain" : "您有以下地址 unstoppable domain ${domain}", - "outdated_electrum_wallet_receive_warning": "如果这个钱包有一个 12 字的种子并且是在 Cake 中创建的,不要将比特币存入这个钱包。 任何转移到此钱包的 BTC 都可能丢失。 创建一个新的 24 字钱包(点击右上角的菜单,选择钱包,选择创建新钱包,然后选择比特币)并立即将您的 BTC 移到那里。 Cake 的新(24 字)BTC 钱包是安全的", - "do_not_show_me": "不再提示" + "do_not_show_me": "不再提示", + + "address_detected" : "檢測到地址", + "address_from_domain" : "您有以下地址 unstoppable domain ${domain}" } \ No newline at end of file From dbf4897ac8d6db21bc817254bf6af60c9342ef82 Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Tue, 29 Jun 2021 13:52:22 +0300 Subject: [PATCH 16/28] CAKE-344 | added MoonPay contact to support page; applied icon color change according to theme --- assets/images/moonpay.png | Bin 0 -> 1392 bytes .../widgets/settings_link_provider_cell.dart | 4 +++- lib/src/screens/support/support_page.dart | 1 + lib/view_model/settings/link_list_item.dart | 5 ++++- lib/view_model/support_view_model.dart | 17 ++++++++++++++++- 5 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 assets/images/moonpay.png diff --git a/assets/images/moonpay.png b/assets/images/moonpay.png new file mode 100644 index 0000000000000000000000000000000000000000..b02af6c00d107b4f68d30a5d5d6b7ee86c81f509 GIT binary patch literal 1392 zcmeAS@N?(olHy`uVBq!ia0vp^;y^6H!3HD+Ytl-A6id3JuOkD)#(wTUiL5}rLb6AY zF9SoB8UsT^3j@P1pisjL28L1t28LG&3=CE?fMyiT*%fF5l&DCJ@J#ddWzYh$IT%l<#Nv5>Skcg59UmvUF{9L`nl>DSry^7od zkS+$B3M(KpH?<^Dp&~aYuh^=>Rtc=a3djZt>nkaMm6T-LDnNc-DA*LGq*(>IxIwi8dA3R!B_#z``ugSN<$C4Ddih1^`i7R4mih)p`bI{&Koz>h zm3bwJ6}oxF$`C_f=D4I5Cl_TFlw{`TDS*sPOv*1Uu~kw6Sp)|Vca~(PA#BPkhI$L= zL4A;nzM-ChJ~nNs6`44+fn*@s!2W_*X9F_K%D*Tx73g4)v+N9Qz!sp0A)E(MACy|0 zpHm7_9-5a~VrK-^f+mcl3uL!dKxRd1PNYj_ZfagJ$R=Ym8-1)2ST#lgGGVyZpUS#4^KmOT)on|-+^iNgQtsQ zhzIZ0se8Sg9R*t7n_lc_T`-M}MS9~UCy_O}wG11#Y~Q$L(hdb>%CV`(^O|I!11l9{%%(Ngec}WY&JVI zx}u)l=5yTt)c2A1YqgS4w%rFz+wL=1^>IZ^61tbUCA3-PsI9=PGQ}56d*=$Il=Pkt#V$_5brG_s_9#UlhK; z@7=+v6F0x#vUbs|zO`&N_MZ=OS(o4EI^piH>}bil%*h3ZCmlEW=90{6Izz3jM{(!T z59|kK$N6&1w^x4gSN5Z}!?)>6;&1ZpV9s1U(Sjv;Ry%+B`=xXK3xAG0acZM?Ilu9d z*9nXsZv+6e1p86qv YGw80q^r|1g;*f#C)78&qol`;+0McLD?*IS* literal 0 HcmV?d00001 diff --git a/lib/src/screens/settings/widgets/settings_link_provider_cell.dart b/lib/src/screens/settings/widgets/settings_link_provider_cell.dart index 362d5ac46..0dbd5703f 100644 --- a/lib/src/screens/settings/widgets/settings_link_provider_cell.dart +++ b/lib/src/screens/settings/widgets/settings_link_provider_cell.dart @@ -7,6 +7,7 @@ class SettingsLinkProviderCell extends StandardListRow { SettingsLinkProviderCell( {@required String title, @required this.icon, + this.iconColor, @required this.link, @required this.linkTitle}) : super(title: title, isSelected: false, onTap: (BuildContext context) => _launchUrl(link) ); @@ -14,10 +15,11 @@ class SettingsLinkProviderCell extends StandardListRow { final String icon; final String link; final String linkTitle; + final Color iconColor; @override Widget buildLeading(BuildContext context) => - icon != null ? Image.asset(icon) : null; + icon != null ? Image.asset(icon, color: iconColor) : null; @override Widget buildTrailing(BuildContext context) => Text(linkTitle, diff --git a/lib/src/screens/support/support_page.dart b/lib/src/screens/support/support_page.dart index 2c5d7ef9a..9405214c6 100644 --- a/lib/src/screens/support/support_page.dart +++ b/lib/src/screens/support/support_page.dart @@ -34,6 +34,7 @@ class SupportPage extends BasePage { return SettingsLinkProviderCell( title: item.title, icon: item.icon, + iconColor: item.iconColor, link: item.link, linkTitle: item.linkTitle); } diff --git a/lib/view_model/settings/link_list_item.dart b/lib/view_model/settings/link_list_item.dart index bf282d2ca..cfcc601c1 100644 --- a/lib/view_model/settings/link_list_item.dart +++ b/lib/view_model/settings/link_list_item.dart @@ -1,15 +1,18 @@ import 'package:flutter/foundation.dart'; import 'package:cake_wallet/view_model/settings/settings_list_item.dart'; +import 'package:flutter/material.dart'; class LinkListItem extends SettingsListItem { LinkListItem( {@required String title, @required this.link, @required this.linkTitle, - this.icon}) + this.icon, + this.iconColor}) : super(title); final String icon; final String link; final String linkTitle; + final Color iconColor; } \ No newline at end of file diff --git a/lib/view_model/support_view_model.dart b/lib/view_model/support_view_model.dart index 1937b90ff..caa360339 100644 --- a/lib/view_model/support_view_model.dart +++ b/lib/view_model/support_view_model.dart @@ -6,6 +6,9 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:mobx/mobx.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; part 'support_view_model.g.dart'; @@ -13,6 +16,11 @@ class SupportViewModel = SupportViewModelBase with _$SupportViewModel; abstract class SupportViewModelBase with Store { SupportViewModelBase() { + final currentTheme = getIt.get().currentTheme; + final iconColor = currentTheme.type == ThemeType.dark + ? Colors.white + : Colors.black; + items = [ RegularListItem( title: S.current.faq, @@ -31,6 +39,7 @@ abstract class SupportViewModelBase with Store { LinkListItem( title: 'GitHub', icon: 'assets/images/github.png', + iconColor: iconColor, linkTitle: S.current.apk_update, link: 'https://github.com/cake-tech/cake_wallet/releases'), LinkListItem( @@ -52,7 +61,13 @@ abstract class SupportViewModelBase with Store { title: 'Wyre', icon: 'assets/images/wyre.png', linkTitle: S.current.submit_request, - link: 'https://wyre-support.zendesk.com/hc/en-us/requests/new') + link: 'https://wyre-support.zendesk.com/hc/en-us/requests/new'), + LinkListItem( + title: 'MoonPay', + icon: 'assets/images/moonpay.png', + iconColor: iconColor, + linkTitle: S.current.submit_request, + link: 'https://support.moonpay.com/hc/en-gb/requests/new') ]; } static const url = 'https://cakewallet.com/guide/'; From 20e0c830cf6d6703fc00d2faeec23f4404a04321 Mon Sep 17 00:00:00 2001 From: OleksandrSobol Date: Mon, 5 Jul 2021 16:52:24 +0300 Subject: [PATCH 17/28] CAKE-334 | applied unspent coins control to the app; added unspent_coins_info.dart; reworked createTransaction(), calculateEstimatedFee() and updateUnspent() methods in the electrum_wallet.dart; fixed unspent_coins_list_view_model.dart, unspent_coins_details_view_model.dart, unspent_coins_list_item.dart, unspent_coins_list_page.dart and unspent_coins_details_page.dart; fixed bitcoin_transaction_wrong_balance_exception.dart; added properties to bitcoin_unspent.dart; applied localization to unspent coins pages --- ...n_transaction_wrong_balance_exception.dart | 8 +- lib/bitcoin/bitcoin_unspent.dart | 8 +- lib/bitcoin/bitcoin_wallet.dart | 6 + lib/bitcoin/bitcoin_wallet_service.dart | 13 +- lib/bitcoin/electrum_wallet.dart | 185 +++++++++++++----- lib/bitcoin/litecoin_wallet.dart | 6 + lib/bitcoin/litecoin_wallet_service.dart | 13 +- lib/bitcoin/unspent_coins_info.dart | 32 +++ lib/di.dart | 41 +++- lib/main.dart | 12 +- lib/router.dart | 4 +- lib/src/screens/send/send_page.dart | 4 +- .../unspent_coins_details_page.dart | 3 +- .../unspent_coins_list_page.dart | 31 +-- .../widgets/unspent_coins_list_item.dart | 138 ++++++------- lib/src/widgets/standart_switch.dart | 1 - lib/view_model/send/send_view_model.dart | 6 +- .../unspent_coins_details_view_model.dart | 22 ++- .../unspent_coins/unspent_coins_item.dart | 35 +++- .../unspent_coins_list_view_model.dart | 97 +++++---- res/values/strings_de.arb | 7 +- res/values/strings_en.arb | 7 +- res/values/strings_es.arb | 7 +- res/values/strings_hi.arb | 7 +- res/values/strings_hr.arb | 7 +- res/values/strings_it.arb | 7 +- res/values/strings_ja.arb | 7 +- res/values/strings_ko.arb | 7 +- res/values/strings_nl.arb | 7 +- res/values/strings_pl.arb | 7 +- res/values/strings_pt.arb | 7 +- res/values/strings_ru.arb | 7 +- res/values/strings_uk.arb | 7 +- res/values/strings_zh.arb | 7 +- 34 files changed, 525 insertions(+), 238 deletions(-) create mode 100644 lib/bitcoin/unspent_coins_info.dart diff --git a/lib/bitcoin/bitcoin_transaction_wrong_balance_exception.dart b/lib/bitcoin/bitcoin_transaction_wrong_balance_exception.dart index 9d1401818..b699ade29 100644 --- a/lib/bitcoin/bitcoin_transaction_wrong_balance_exception.dart +++ b/lib/bitcoin/bitcoin_transaction_wrong_balance_exception.dart @@ -1,4 +1,10 @@ +import 'package:cake_wallet/entities/crypto_currency.dart'; + class BitcoinTransactionWrongBalanceException implements Exception { + BitcoinTransactionWrongBalanceException(this.currency); + + final CryptoCurrency currency; + @override - String toString() => 'Wrong balance. Not enough BTC on your balance.'; + String toString() => 'Wrong balance. Not enough ${currency.title} on your balance.'; } \ No newline at end of file diff --git a/lib/bitcoin/bitcoin_unspent.dart b/lib/bitcoin/bitcoin_unspent.dart index 846eb8c7d..b95ed9bc3 100644 --- a/lib/bitcoin/bitcoin_unspent.dart +++ b/lib/bitcoin/bitcoin_unspent.dart @@ -1,7 +1,10 @@ import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart'; class BitcoinUnspent { - BitcoinUnspent(this.address, this.hash, this.value, this.vout); + BitcoinUnspent(this.address, this.hash, this.value, this.vout) + : isSending = true, + isFrozen = false, + note = ''; factory BitcoinUnspent.fromJSON( BitcoinAddressRecord address, Map json) => @@ -15,4 +18,7 @@ class BitcoinUnspent { bool get isP2wpkh => address.address.startsWith('bc') || address.address.startsWith('ltc'); + bool isSending; + bool isFrozen; + String note; } diff --git a/lib/bitcoin/bitcoin_wallet.dart b/lib/bitcoin/bitcoin_wallet.dart index fd8402887..022ed478d 100644 --- a/lib/bitcoin/bitcoin_wallet.dart +++ b/lib/bitcoin/bitcoin_wallet.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/bitcoin/unspent_coins_info.dart'; +import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter/foundation.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; @@ -17,6 +19,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { {@required String mnemonic, @required String password, @required WalletInfo walletInfo, + @required Box unspentCoinsInfo, List initialAddresses, ElectrumBalance initialBalance, int accountIndex = 0}) @@ -24,6 +27,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { mnemonic: mnemonic, password: password, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, networkType: bitcoin.bitcoin, initialAddresses: initialAddresses, initialBalance: initialBalance, @@ -32,6 +36,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { static Future open({ @required String name, @required WalletInfo walletInfo, + @required Box unspentCoinsInfo, @required String password, }) async { final snp = ElectrumWallletSnapshot(name, walletInfo.type, password); @@ -40,6 +45,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { mnemonic: snp.mnemonic, password: password, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp.addresses, initialBalance: snp.balance, accountIndex: snp.accountIndex); diff --git a/lib/bitcoin/bitcoin_wallet_service.dart b/lib/bitcoin/bitcoin_wallet_service.dart index aefe0fadf..8e5b31930 100644 --- a/lib/bitcoin/bitcoin_wallet_service.dart +++ b/lib/bitcoin/bitcoin_wallet_service.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart'; +import 'package:cake_wallet/bitcoin/unspent_coins_info.dart'; import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_service.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; @@ -14,9 +15,10 @@ class BitcoinWalletService extends WalletService< BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> { - BitcoinWalletService(this.walletInfoSource); + BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); final Box walletInfoSource; + final Box unspentCoinsInfoSource; @override WalletType getType() => WalletType.bitcoin; @@ -26,7 +28,8 @@ class BitcoinWalletService extends WalletService< final wallet = BitcoinWallet( mnemonic: await generateMnemonic(), password: credentials.password, - walletInfo: credentials.walletInfo); + walletInfo: credentials.walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); return wallet; @@ -42,7 +45,8 @@ class BitcoinWalletService extends WalletService< (info) => info.id == WalletBase.idFor(name, getType()), orElse: () => null); final wallet = await BitcoinWalletBase.open( - password: password, name: name, walletInfo: walletInfo); + password: password, name: name, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); return wallet; } @@ -67,7 +71,8 @@ class BitcoinWalletService extends WalletService< final wallet = BitcoinWallet( password: credentials.password, mnemonic: credentials.mnemonic, - walletInfo: credentials.walletInfo); + walletInfo: credentials.walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); return wallet; diff --git a/lib/bitcoin/electrum_wallet.dart b/lib/bitcoin/electrum_wallet.dart index bf3dfd542..43c5cd179 100644 --- a/lib/bitcoin/electrum_wallet.dart +++ b/lib/bitcoin/electrum_wallet.dart @@ -1,5 +1,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:cake_wallet/bitcoin/unspent_coins_info.dart'; +import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:flutter/foundation.dart'; @@ -38,6 +40,7 @@ abstract class ElectrumWalletBase extends WalletBase unspentCoinsInfo, @required List initialAddresses, @required this.networkType, @required this.mnemonic, @@ -59,9 +62,10 @@ abstract class ElectrumWalletBase extends WalletBase unspentCoinsInfo; @override @observable @@ -103,7 +108,7 @@ abstract class ElectrumWalletBase extends WalletBase _unspent; + List unspentCoins; List _feeRates; int _accountIndex; Map> _scripthashesUpdateSubject; @@ -178,10 +183,10 @@ abstract class ElectrumWalletBase extends WalletBase startSync() async { try { syncStatus = StartingSyncStatus(); - updateTransactions(); + await updateTransactions(); _subscribeForUpdates(); await _updateBalance(); - await _updateUnspent(); + await updateUnspent(); _feeRates = await electrumClient.feeRates(); Timer.periodic(const Duration(minutes: 1), @@ -218,33 +223,16 @@ abstract class ElectrumWalletBase extends WalletBase[]; - final allAmountFee = - calculateEstimatedFee(transactionCredentials.priority, null); - final allAmount = balance.confirmed - allAmountFee; - var fee = 0; - final credentialsAmount = transactionCredentials.amount != null - ? stringDoubleToBitcoinAmount(transactionCredentials.amount) - : 0; - final amount = transactionCredentials.amount == null || - allAmount - credentialsAmount < minAmount - ? allAmount - : credentialsAmount; - final txb = bitcoin.TransactionBuilder(network: networkType); - final changeAddress = address; - var leftAmount = amount; - var totalInputAmount = 0; + var allInputsAmount = 0; - if (_unspent.isEmpty) { - await _updateUnspent(); + if (unspentCoins.isEmpty) { + await updateUnspent(); } - for (final utx in _unspent) { - leftAmount = leftAmount - utx.value; - totalInputAmount += utx.value; - inputs.add(utx); - - if (leftAmount <= 0) { - break; + for (final utx in unspentCoins) { + if (utx.isSending) { + allInputsAmount += utx.value; + inputs.add(utx); } } @@ -252,18 +240,57 @@ abstract class ElectrumWalletBase extends WalletBase balance.confirmed) { - throw BitcoinTransactionWrongBalanceException(); + final credentialsAmount = transactionCredentials.amount != null + ? stringDoubleToBitcoinAmount(transactionCredentials.amount) + : 0; + final amount = transactionCredentials.amount == null || + allAmount - credentialsAmount < minAmount + ? allAmount + : credentialsAmount; + final fee = transactionCredentials.amount == null || amount == allAmount + ? allAmountFee + : calculateEstimatedFee(transactionCredentials.priority, amount); + + if (fee == 0) { + throw BitcoinTransactionWrongBalanceException(currency); } - if (amount <= 0 || totalInputAmount < amount) { - throw BitcoinTransactionWrongBalanceException(); + final totalAmount = amount + fee; + + if (totalAmount > balance.confirmed || totalAmount > allInputsAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + final txb = bitcoin.TransactionBuilder(network: networkType); + final changeAddress = address; + + var leftAmount = totalAmount; + var totalInputAmount = 0; + + inputs.clear(); + + for (final utx in unspentCoins) { + if (utx.isSending) { + leftAmount = leftAmount - utx.value; + totalInputAmount += utx.value; + inputs.add(utx); + + if (leftAmount <= 0) { + break; + } + } + } + + if (inputs.isEmpty) { + throw BitcoinTransactionNoInputsException(); + } + + if (amount <= 0 || totalInputAmount < totalAmount) { + throw BitcoinTransactionWrongBalanceException(currency); } txb.setVersion(1); @@ -338,17 +365,26 @@ abstract class ElectrumWalletBase extends WalletBase= amount) { break; } - totalValue += input.value; - inputsCount += 1; + if (input.isSending) { + totalValue += input.value; + inputsCount += 1; + } } + + if (totalValue < amount) return 0; } else { - inputsCount = _unspent.length; + for (final input in unspentCoins) { + if (input.isSending) { + inputsCount += 1; + } + } } + // If send all, then we have no change value return feeAmountForPriority( priority, inputsCount, amount != null ? 2 : 1); @@ -382,12 +418,73 @@ abstract class ElectrumWalletBase extends WalletBase makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - Future _updateUnspent() async { + Future updateUnspent() async { final unspent = await Future.wait(addresses.map((address) => electrumClient .getListUnspentWithAddress(address.address, networkType) .then((unspent) => unspent .map((unspent) => BitcoinUnspent.fromJSON(address, unspent))))); - _unspent = unspent.expand((e) => e).toList(); + unspentCoins = unspent.expand((e) => e).toList(); + + if (unspentCoinsInfo.isEmpty) { + unspentCoins.forEach((coin) => _addCoinInfo(coin)); + return; + } + + if (unspentCoins.isNotEmpty) { + unspentCoins.forEach((coin) { + final coinInfoList = unspentCoinsInfo.values.where((element) => + element.walletId.contains(id) && element.hash.contains(coin.hash)); + + if (coinInfoList.isNotEmpty) { + final coinInfo = coinInfoList.first; + + coin.isFrozen = coinInfo.isFrozen; + coin.isSending = coinInfo.isSending; + coin.note = coinInfo.note; + } else { + _addCoinInfo(coin); + } + }); + } + + await _refreshUnspentCoinsInfo(); + } + + Future _addCoinInfo(BitcoinUnspent coin) async { + final newInfo = UnspentCoinsInfo( + walletId: id, + hash: coin.hash, + isFrozen: coin.isFrozen, + isSending: coin.isSending, + note: coin.note + ); + + await unspentCoinsInfo.add(newInfo); + } + + Future _refreshUnspentCoinsInfo() async { + try { + final List keys = []; + final currentWalletUnspentCoins = unspentCoinsInfo.values + .where((element) => element.walletId.contains(id)); + + if (currentWalletUnspentCoins.isNotEmpty) { + currentWalletUnspentCoins.forEach((element) { + final existUnspentCoins = unspentCoins + ?.where((coin) => element.hash.contains(coin?.hash)); + + if (existUnspentCoins?.isEmpty ?? true) { + keys.add(element.key); + } + }); + } + + if (keys.isNotEmpty) { + await unspentCoinsInfo.deleteAll(keys); + } + } catch (e) { + print(e.toString()); + } } Future fetchTransactionInfo( @@ -438,7 +535,7 @@ abstract class ElectrumWalletBase extends WalletBase unspentCoinsInfo, List initialAddresses, ElectrumBalance initialBalance, int accountIndex = 0}) @@ -28,6 +31,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { mnemonic: mnemonic, password: password, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, networkType: litecoinNetwork, initialAddresses: initialAddresses, initialBalance: initialBalance, @@ -36,6 +40,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { static Future open({ @required String name, @required WalletInfo walletInfo, + @required Box unspentCoinsInfo, @required String password, }) async { final snp = ElectrumWallletSnapshot(name, walletInfo.type, password); @@ -44,6 +49,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { mnemonic: snp.mnemonic, password: password, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, initialAddresses: snp.addresses, initialBalance: snp.balance, accountIndex: snp.accountIndex); diff --git a/lib/bitcoin/litecoin_wallet_service.dart b/lib/bitcoin/litecoin_wallet_service.dart index 053fd785f..dc5081e12 100644 --- a/lib/bitcoin/litecoin_wallet_service.dart +++ b/lib/bitcoin/litecoin_wallet_service.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:cake_wallet/bitcoin/unspent_coins_info.dart'; import 'package:hive/hive.dart'; import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart'; @@ -14,9 +15,10 @@ class LitecoinWalletService extends WalletService< BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> { - LitecoinWalletService(this.walletInfoSource); + LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); final Box walletInfoSource; + final Box unspentCoinsInfoSource; @override WalletType getType() => WalletType.litecoin; @@ -26,7 +28,8 @@ class LitecoinWalletService extends WalletService< final wallet = LitecoinWallet( mnemonic: await generateMnemonic(), password: credentials.password, - walletInfo: credentials.walletInfo); + walletInfo: credentials.walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); @@ -43,7 +46,8 @@ class LitecoinWalletService extends WalletService< (info) => info.id == WalletBase.idFor(name, getType()), orElse: () => null); final wallet = await LitecoinWalletBase.open( - password: password, name: name, walletInfo: walletInfo); + password: password, name: name, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); return wallet; } @@ -68,7 +72,8 @@ class LitecoinWalletService extends WalletService< final wallet = LitecoinWallet( password: credentials.password, mnemonic: credentials.mnemonic, - walletInfo: credentials.walletInfo); + walletInfo: credentials.walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); return wallet; diff --git a/lib/bitcoin/unspent_coins_info.dart b/lib/bitcoin/unspent_coins_info.dart new file mode 100644 index 000000000..6ce647dce --- /dev/null +++ b/lib/bitcoin/unspent_coins_info.dart @@ -0,0 +1,32 @@ +import 'package:hive/hive.dart'; + +part 'unspent_coins_info.g.dart'; + +@HiveType(typeId: UnspentCoinsInfo.typeId) +class UnspentCoinsInfo extends HiveObject { + UnspentCoinsInfo({ + this.walletId, + this.hash, + this.isFrozen, + this.isSending, + this.note}); + + static const typeId = 9; + static const boxName = 'Unspent'; + static const boxKey = 'unspentBoxKey'; + + @HiveField(0) + String walletId; + + @HiveField(1) + String hash; + + @HiveField(2) + bool isFrozen; + + @HiveField(3) + bool isSending; + + @HiveField(4) + String note; +} \ No newline at end of file diff --git a/lib/di.dart b/lib/di.dart index d887d8832..82ac29b6d 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart'; import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart'; +import 'package:cake_wallet/bitcoin/unspent_coins_info.dart'; import 'package:cake_wallet/core/backup_service.dart'; import 'package:cake_wallet/core/wallet_service.dart'; import 'package:cake_wallet/entities/biometric_auth.dart'; @@ -130,6 +131,7 @@ Box