From c54b4d39d3fbc1121cbb7de1285371d85656fb88 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Mon, 16 Oct 2023 15:04:27 -0600 Subject: [PATCH] add persistent fusion server prefs --- lib/pages/cashfusion/cashfusion_view.dart | 86 +++++++++++-- .../cashfusion/desktop_cashfusion_view.dart | 115 +++++++++++------- .../mixins/fusion_wallet_interface.dart | 89 ++++++++++++-- lib/utilities/prefs.dart | 33 +++++ 4 files changed, 262 insertions(+), 61 deletions(-) diff --git a/lib/pages/cashfusion/cashfusion_view.dart b/lib/pages/cashfusion/cashfusion_view.dart index f17ddaefc..0b86db791 100644 --- a/lib/pages/cashfusion/cashfusion_view.dart +++ b/lib/pages/cashfusion/cashfusion_view.dart @@ -17,6 +17,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/cashfusion/fusion_rounds_selection_sheet.dart'; import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart'; import 'package:stackwallet/themes/stack_colors.dart'; @@ -49,6 +50,8 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> { late final FocusNode serverFocusNode; late final TextEditingController portController; late final FocusNode portFocusNode; + late final TextEditingController fusionRoundController; + late final FocusNode fusionRoundFocusNode; bool _enableSSLCheckbox = false; bool _enableStartButton = false; @@ -59,11 +62,18 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> { void initState() { serverController = TextEditingController(); portController = TextEditingController(); + fusionRoundController = TextEditingController(); serverFocusNode = FocusNode(); portFocusNode = FocusNode(); + fusionRoundFocusNode = FocusNode(); - // TODO set controller text values to saved info + final info = ref.read(prefsChangeNotifierProvider).fusionServerInfo; + serverController.text = info.host; + portController.text = info.port.toString(); + _enableSSLCheckbox = info.ssl; + _option = info.rounds == 0 ? FusionOption.continuous : FusionOption.custom; + fusionRoundController.text = info.rounds.toString(); _enableStartButton = serverController.text.isNotEmpty && portController.text.isNotEmpty; @@ -75,9 +85,11 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> { void dispose() { serverController.dispose(); portController.dispose(); + fusionRoundController.dispose(); serverFocusNode.dispose(); portFocusNode.dispose(); + fusionRoundFocusNode.dispose(); super.dispose(); } @@ -166,8 +178,9 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> { focusNode: serverFocusNode, onChanged: (value) { setState(() { - _enableStartButton = value.isNotEmpty & - portController.text.isNotEmpty; + _enableStartButton = value.isNotEmpty && + portController.text.isNotEmpty && + fusionRoundController.text.isNotEmpty; }); }, style: STextStyles.field(context), @@ -197,8 +210,9 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> { keyboardType: TextInputType.number, onChanged: (value) { setState(() { - _enableStartButton = value.isNotEmpty & - serverController.text.isNotEmpty; + _enableStartButton = value.isNotEmpty && + serverController.text.isNotEmpty && + fusionRoundController.text.isNotEmpty; }); }, style: STextStyles.field(context), @@ -311,6 +325,40 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> { ), ), ), + if (_option == FusionOption.custom) + const SizedBox( + height: 10, + ), + if (_option == FusionOption.custom) + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: false, + enableSuggestions: false, + controller: fusionRoundController, + focusNode: fusionRoundFocusNode, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], + keyboardType: TextInputType.number, + onChanged: (value) { + setState(() { + _enableStartButton = value.isNotEmpty && + serverController.text.isNotEmpty && + portController.text.isNotEmpty; + }); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Number of fusions", + fusionRoundFocusNode, + context, + ).copyWith( + labelText: "Enter number of fusions.."), + ), + ), const SizedBox( height: 16, ), @@ -336,12 +384,28 @@ class _CashFusionViewState extends ConsumerState<CashFusionView> { } } - unawaited(fusionWallet.fuse( - serverHost: serverController.text, - serverPort: int.parse(portController.text), - serverSsl: _enableSSLCheckbox, - roundCount: 0, // TODO update fusion rounds. - )); + final int rounds = + _option == FusionOption.continuous + ? 0 + : int.parse(fusionRoundController.text); + + final newInfo = FusionInfo( + host: serverController.text, + port: int.parse(portController.text), + ssl: _enableSSLCheckbox, + rounds: rounds, + ); + + // update user prefs (persistent) + ref + .read(prefsChangeNotifierProvider) + .fusionServerInfo = newInfo; + + unawaited( + fusionWallet.fuse( + fusionInfo: newInfo, + ), + ); // TODO: navigate to progress screen }, diff --git a/lib/pages_desktop_specific/cashfusion/desktop_cashfusion_view.dart b/lib/pages_desktop_specific/cashfusion/desktop_cashfusion_view.dart index c5f17cc34..af2ec178e 100644 --- a/lib/pages_desktop_specific/cashfusion/desktop_cashfusion_view.dart +++ b/lib/pages_desktop_specific/cashfusion/desktop_cashfusion_view.dart @@ -20,8 +20,8 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/cashfusion/fusion_rounds_selection_sheet.dart'; import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart'; import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; -import 'package:stackwallet/providers/ui/check_box_state_provider.dart'; import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -58,9 +58,8 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> { late final TextEditingController fusionRoundController; late final FocusNode fusionRoundFocusNode; - String _fusionRoundTerm = ""; - bool _enableStartButton = false; + bool _enableSSLCheckbox = false; FusionOption _roundType = FusionOption.continuous; @@ -74,7 +73,13 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> { portFocusNode = FocusNode(); fusionRoundFocusNode = FocusNode(); - // TODO set controller text values to saved info + final info = ref.read(prefsChangeNotifierProvider).fusionServerInfo; + serverController.text = info.host; + portController.text = info.port.toString(); + _enableSSLCheckbox = info.ssl; + _roundType = + info.rounds == 0 ? FusionOption.continuous : FusionOption.custom; + fusionRoundController.text = info.rounds.toString(); _enableStartButton = serverController.text.isNotEmpty && portController.text.isNotEmpty; @@ -285,25 +290,25 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> { Constants.size.circularBorderRadius, ), child: TextField( - autocorrect: false, - enableSuggestions: false, - controller: serverController, - focusNode: serverFocusNode, - onChanged: (value) { - setState(() { - _enableStartButton = value.isNotEmpty & - portController.text.isNotEmpty; - }); - }, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Server", - serverFocusNode, - context, - desktopMed: true, - ) - // .copyWith(labelStyle: ), - ), + autocorrect: false, + enableSuggestions: false, + controller: serverController, + focusNode: serverFocusNode, + onChanged: (value) { + setState(() { + _enableStartButton = value.isNotEmpty && + portController.text.isNotEmpty && + fusionRoundController.text.isNotEmpty; + }); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Server", + serverFocusNode, + context, + desktopMed: true, + ), + ), ), const SizedBox( height: 12, @@ -322,8 +327,9 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> { ], onChanged: (value) { setState(() { - _enableStartButton = value.isNotEmpty & - serverController.text.isNotEmpty; + _enableStartButton = value.isNotEmpty && + serverController.text.isNotEmpty && + fusionRoundController.text.isNotEmpty; }); }, style: STextStyles.field(context), @@ -340,10 +346,9 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> { ), GestureDetector( onTap: () { - final value = - ref.read(checkBoxStateProvider.state).state; - ref.read(checkBoxStateProvider.state).state = - !value; + setState(() { + _enableSSLCheckbox = !_enableSSLCheckbox; + }); }, child: Container( color: Colors.transparent, @@ -355,13 +360,14 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> { child: Checkbox( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - value: ref - .watch(checkBoxStateProvider.state) - .state, + value: _enableSSLCheckbox, onChanged: (newValue) { - ref - .watch(checkBoxStateProvider.state) - .state = newValue!; + setState( + () { + _enableSSLCheckbox = + !_enableSSLCheckbox; + }, + ); }, ), ), @@ -464,14 +470,21 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> { enableSuggestions: false, controller: fusionRoundController, focusNode: fusionRoundFocusNode, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], onChanged: (value) { setState(() { - _fusionRoundTerm = value; + _enableStartButton = value + .isNotEmpty && + serverController + .text.isNotEmpty && + portController.text.isNotEmpty; }); }, style: STextStyles.field(context), decoration: standardInputDecoration( - "", + "Number of fusions", fusionRoundFocusNode, context, desktopMed: true, @@ -507,12 +520,28 @@ class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> { } } - unawaited(fusionWallet.fuse( - serverHost: serverController.text, - serverPort: int.parse(portController.text), - serverSsl: ref.read(checkBoxStateProvider), - roundCount: 0, // TODO update fusion rounds. - )); + final int rounds = + _roundType == FusionOption.continuous + ? 0 + : int.parse(fusionRoundController.text); + + final newInfo = FusionInfo( + host: serverController.text, + port: int.parse(portController.text), + ssl: _enableSSLCheckbox, + rounds: rounds, + ); + + // update user prefs (persistent) + ref + .read(prefsChangeNotifierProvider) + .fusionServerInfo = newInfo; + + unawaited( + fusionWallet.fuse( + fusionInfo: newInfo, + ), + ); // unawaited(fusionWallet.stepThruUiStates()); await showDialog<void>( diff --git a/lib/services/mixins/fusion_wallet_interface.dart b/lib/services/mixins/fusion_wallet_interface.dart index 8931a45ff..ea12faf70 100644 --- a/lib/services/mixins/fusion_wallet_interface.dart +++ b/lib/services/mixins/fusion_wallet_interface.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; @@ -19,6 +20,77 @@ import 'package:stackwallet/utilities/stack_file_system.dart'; const String kReservedFusionAddress = "reserved_fusion_address"; +class FusionInfo { + final String host; + final int port; + final bool ssl; + + /// set to 0 for continuous + final int rounds; + + const FusionInfo({ + required this.host, + required this.port, + required this.ssl, + required this.rounds, + }) : assert(rounds >= 0); + + // TODO update defaults + static const DEFAULTS = FusionInfo( + host: "cashfusion.stackwallet.com", + port: 8787, + ssl: false, + rounds: 0, // 0 is continuous + ); + + factory FusionInfo.fromJsonString(String jsonString) { + final json = jsonDecode(jsonString); + return FusionInfo( + host: json['host'] as String, + port: json['port'] as int, + ssl: json['ssl'] as bool, + rounds: json['rounds'] as int, + ); + } + + String toJsonString() { + return { + 'host': host, + 'port': port, + 'ssl': ssl, + 'rounds': rounds, + }.toString(); + } + + @override + String toString() { + return toJsonString(); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + return other is FusionInfo && + other.host == host && + other.port == port && + other.ssl == ssl && + other.rounds == rounds; + } + + @override + int get hashCode { + return Object.hash( + host.hashCode, + port.hashCode, + ssl.hashCode, + rounds.hashCode, + ); + } +} + /// A mixin for the BitcoinCashWallet class that adds CashFusion functionality. mixin FusionWalletInterface { // Passed in wallet data. @@ -292,17 +364,20 @@ mixin FusionWalletInterface { /// Fuse the wallet's UTXOs. /// /// This function is called when the user taps the "Fuse" button in the UI. - Future<void> fuse( - {required String serverHost, - required int serverPort, - required bool serverSsl, - required int roundCount}) async { + Future<void> fuse({ + required FusionInfo fusionInfo, + }) async { // Initial attempt for CashFusion integration goes here. // Use server host and port which ultimately come from text fields. - // TODO validate. fusion.FusionParams serverParams = fusion.FusionParams( - serverHost: serverHost, serverPort: serverPort, serverSsl: serverSsl, roundCount: roundCount); + serverHost: fusionInfo.host, + serverPort: fusionInfo.port, + serverSsl: fusionInfo.ssl, + ); + + // TODO use as required. Zero indicates continuous + final roundCount = fusionInfo.rounds; // Instantiate a Fusion object with custom parameters. final mainFusionObject = fusion.Fusion(serverParams); diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 583d9804b..4dcd9a65d 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -12,6 +12,7 @@ import 'package:flutter/cupertino.dart'; import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/services/event_bus/events/global/tor_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart'; import 'package:stackwallet/utilities/amount/amount_unit.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; @@ -64,6 +65,7 @@ class Prefs extends ChangeNotifier { await _setAmountUnits(); await _setMaxDecimals(); _useTor = await _getUseTor(); + _fusionServerInfo = await _getFusionServerInfo(); _initialized = true; } @@ -931,4 +933,35 @@ class Prefs extends ChangeNotifier { ) as bool? ?? false; } + + // fusion server info + + FusionInfo _fusionServerInfo = FusionInfo.DEFAULTS; + + FusionInfo get fusionServerInfo => _fusionServerInfo; + + set fusionServerInfo(FusionInfo fusionServerInfo) { + if (this.fusionServerInfo != fusionServerInfo) { + DB.instance.put<dynamic>( + boxName: DB.boxNamePrefs, + key: "fusionServerInfo", + value: fusionServerInfo.toJsonString(), + ); + _fusionServerInfo = fusionServerInfo; + notifyListeners(); + } + } + + Future<FusionInfo> _getFusionServerInfo() async { + final saved = await DB.instance.get<dynamic>( + boxName: DB.boxNamePrefs, + key: "fusionServerInfo", + ) as String?; + + if (saved == null) { + return FusionInfo.DEFAULTS; + } else { + return FusionInfo.fromJsonString(saved); + } + } }