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);
+    }
+  }
 }