diff --git a/assets/svg/enabled-button.svg b/assets/svg/enabled-button.svg
new file mode 100644
index 000000000..a26359e81
--- /dev/null
+++ b/assets/svg/enabled-button.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/svg/framed-address-book.svg b/assets/svg/framed-address-book.svg
new file mode 100644
index 000000000..157117097
--- /dev/null
+++ b/assets/svg/framed-address-book.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/svg/framed-gear.svg b/assets/svg/framed-gear.svg
new file mode 100644
index 000000000..749d9803d
--- /dev/null
+++ b/assets/svg/framed-gear.svg
@@ -0,0 +1,4 @@
+
diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero
index 51f74f05d..2da774385 160000
--- a/crypto_plugins/flutter_libmonero
+++ b/crypto_plugins/flutter_libmonero
@@ -1 +1 @@
-Subproject commit 51f74f05d465a92e0118cf7c2bcfb049df21af42
+Subproject commit 2da77438527732dfaa5398aa391eab5253dabe19
diff --git a/lib/main.dart b/lib/main.dart
index 58a287b31..77a8b1441 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -30,7 +30,8 @@ import 'package:stackwallet/pages/loading_view.dart';
import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart';
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart';
-import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
+import 'package:stackwallet/pages_desktop_specific/desktop_login_view.dart';
+import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart';
import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
import 'package:stackwallet/providers/global/base_currencies_provider.dart';
// import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart';
@@ -207,6 +208,7 @@ class _MaterialAppWithThemeState extends ConsumerState
late final Completer loadingCompleter;
bool didLoad = false;
+ bool _desktopHasPassword = false;
Future load() async {
try {
@@ -218,6 +220,11 @@ class _MaterialAppWithThemeState extends ConsumerState
await DB.instance.init();
await _prefs.init();
+ if (Util.isDesktop) {
+ _desktopHasPassword =
+ await ref.read(storageCryptoHandlerProvider).hasPassword();
+ }
+
_notificationsService = ref.read(notificationsProvider);
_nodeService = ref.read(nodeServiceChangeNotifierProvider);
_tradesService = ref.read(tradesServiceProvider);
@@ -545,21 +552,23 @@ class _MaterialAppWithThemeState extends ConsumerState
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// FlutterNativeSplash.remove();
- if (_wallets.hasWallets || _prefs.hasPin) {
- // return HomeView();
-
+ if (Util.isDesktop &&
+ (_wallets.hasWallets || _desktopHasPassword)) {
String? startupWalletId;
if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) {
startupWalletId =
ref.read(prefsChangeNotifierProvider).startupWalletId;
}
- // TODO proper desktop auth view
- if (Util.isDesktop) {
- Future.delayed(Duration.zero).then((value) =>
- Navigator.of(context).pushNamedAndRemoveUntil(
- DesktopHomeView.routeName, (route) => false));
- return Container();
+ return DesktopLoginView(startupWalletId: startupWalletId);
+ } else if (!Util.isDesktop &&
+ (_wallets.hasWallets || _prefs.hasPin)) {
+ // return HomeView();
+
+ String? startupWalletId;
+ if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) {
+ startupWalletId =
+ ref.read(prefsChangeNotifierProvider).startupWalletId;
}
return LockscreenView(
diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
index 76e74fa14..1ce5d713a 100644
--- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
+++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
@@ -252,7 +252,11 @@ class _RestoreOptionsViewState extends ConsumerState {
SizedBox(
height: isDesktop ? 40 : 24,
),
- if (coin == Coin.monero || coin == Coin.epicCash)
+ if (coin == Coin.monero ||
+ coin == Coin.epicCash ||
+ (coin == Coin.wownero &&
+ ref.watch(mnemonicWordCountStateProvider.state).state ==
+ 25))
Text(
"Choose start date",
style: isDesktop
@@ -264,11 +268,19 @@ class _RestoreOptionsViewState extends ConsumerState {
: STextStyles.smallMed12(context),
textAlign: TextAlign.left,
),
- if (coin == Coin.monero || coin == Coin.epicCash)
+ if (coin == Coin.monero ||
+ coin == Coin.epicCash ||
+ (coin == Coin.wownero &&
+ ref.watch(mnemonicWordCountStateProvider.state).state ==
+ 25))
SizedBox(
height: isDesktop ? 16 : 8,
),
- if (coin == Coin.monero || coin == Coin.epicCash)
+ if (coin == Coin.monero ||
+ coin == Coin.epicCash ||
+ (coin == Coin.wownero &&
+ ref.watch(mnemonicWordCountStateProvider.state).state ==
+ 25))
// if (!isDesktop)
RestoreFromDatePicker(
@@ -278,11 +290,19 @@ class _RestoreOptionsViewState extends ConsumerState {
// if (isDesktop)
// // TODO desktop date picker
- if (coin == Coin.monero || coin == Coin.epicCash)
+ if (coin == Coin.monero ||
+ coin == Coin.epicCash ||
+ (coin == Coin.wownero &&
+ ref.watch(mnemonicWordCountStateProvider.state).state ==
+ 25))
const SizedBox(
height: 8,
),
- if (coin == Coin.monero || coin == Coin.epicCash)
+ if (coin == Coin.monero ||
+ coin == Coin.epicCash ||
+ (coin == Coin.wownero &&
+ ref.watch(mnemonicWordCountStateProvider.state).state ==
+ 25))
RoundedWhiteContainer(
child: Center(
child: Text(
@@ -299,7 +319,11 @@ class _RestoreOptionsViewState extends ConsumerState {
),
),
),
- if (coin == Coin.monero || coin == Coin.epicCash)
+ if (coin == Coin.monero ||
+ coin == Coin.epicCash ||
+ (coin == Coin.wownero &&
+ ref.watch(mnemonicWordCountStateProvider.state).state ==
+ 25))
SizedBox(
height: isDesktop ? 24 : 16,
),
diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart
index def0724b5..6bac38371 100644
--- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart
+++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart
@@ -8,6 +8,7 @@ import 'package:bip39/src/wordlists/english.dart' as bip39wordlist;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_libmonero/monero/monero.dart';
+import 'package:flutter_libmonero/wownero/wownero.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
@@ -149,12 +150,18 @@ class _RestoreWalletViewState extends ConsumerState {
super.dispose();
}
+ // TODO: check for wownero wordlist?
bool _isValidMnemonicWord(String word) {
// TODO: get the actual language
if (widget.coin == Coin.monero) {
var moneroWordList = monero.getMoneroWordList("English");
return moneroWordList.contains(word);
}
+ if (widget.coin == Coin.wownero) {
+ var wowneroWordList = wownero.getWowneroWordList("English",
+ seedWordsLength: widget.seedWordsLength);
+ return wowneroWordList.contains(word);
+ }
return _wordListHashSet.contains(word);
}
@@ -180,7 +187,13 @@ class _RestoreWalletViewState extends ConsumerState {
if (widget.coin == Coin.monero) {
height = monero.getHeigthByDate(date: widget.restoreFromDate);
+ } else if (widget.coin == Coin.wownero) {
+ height = wownero.getHeightByDate(date: widget.restoreFromDate);
}
+ // todo: wait until this implemented
+ // else if (widget.coin == Coin.wownero) {
+ // height = wownero.getHeightByDate(date: widget.restoreFromDate);
+ // }
// TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index
if (widget.coin == Coin.epicCash) {
diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart
index d77ad6b8c..e99cf2df4 100644
--- a/lib/pages/exchange_view/confirm_change_now_send.dart
+++ b/lib/pages/exchange_view/confirm_change_now_send.dart
@@ -10,6 +10,7 @@ import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
+import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/text_styles.dart';
@@ -27,6 +28,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget {
required this.walletId,
this.routeOnSuccessName = WalletView.routeName,
required this.trade,
+ this.shouldSendPublicFiroFunds,
}) : super(key: key);
static const String routeName = "/confirmChangeNowSend";
@@ -35,6 +37,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget {
final String walletId;
final String routeOnSuccessName;
final Trade trade;
+ final bool? shouldSendPublicFiroFunds;
@override
ConsumerState createState() =>
@@ -63,7 +66,15 @@ class _ConfirmChangeNowSendViewState
ref.read(walletsChangeNotifierProvider).getManager(walletId);
try {
- final txid = await manager.confirmSend(txData: transactionInfo);
+ late final String txid;
+
+ if (widget.shouldSendPublicFiroFunds == true) {
+ txid = await (manager.wallet as FiroWallet)
+ .confirmSendPublic(txData: transactionInfo);
+ } else {
+ txid = await manager.confirmSend(txData: transactionInfo);
+ }
+
unawaited(manager.refresh());
// save note
diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart
index 20fc81903..c87175955 100644
--- a/lib/pages/exchange_view/send_from_view.dart
+++ b/lib/pages/exchange_view/send_from_view.dart
@@ -10,6 +10,8 @@ import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
+import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
+import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@@ -18,7 +20,9 @@ import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/animated_text.dart';
+import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
+import 'package:stackwallet/widgets/expandable.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
@@ -162,6 +166,130 @@ class _SendFromCardState extends ConsumerState {
late final String address;
late final Trade trade;
+ Future _send(Manager manager, {bool? shouldSendPublicFiroFunds}) async {
+ final _amount = Format.decimalAmountToSatoshis(amount);
+
+ try {
+ bool wasCancelled = false;
+
+ unawaited(
+ showDialog(
+ context: context,
+ useSafeArea: false,
+ barrierDismissible: false,
+ builder: (context) {
+ return BuildingTransactionDialog(
+ onCancel: () {
+ wasCancelled = true;
+
+ Navigator.of(context).pop();
+ },
+ );
+ },
+ ),
+ );
+
+ late Map txData;
+
+ // if not firo then do normal send
+ if (shouldSendPublicFiroFunds == null) {
+ txData = await manager.prepareSend(
+ address: address,
+ satoshiAmount: _amount,
+ args: {
+ "feeRate": FeeRateType.average,
+ // ref.read(feeRateTypeStateProvider)
+ },
+ );
+ } else {
+ final firoWallet = manager.wallet as FiroWallet;
+ // otherwise do firo send based on balance selected
+ if (shouldSendPublicFiroFunds) {
+ txData = await firoWallet.prepareSendPublic(
+ address: address,
+ satoshiAmount: _amount,
+ args: {
+ "feeRate": FeeRateType.average,
+ // ref.read(feeRateTypeStateProvider)
+ },
+ );
+ } else {
+ txData = await firoWallet.prepareSend(
+ address: address,
+ satoshiAmount: _amount,
+ args: {
+ "feeRate": FeeRateType.average,
+ // ref.read(feeRateTypeStateProvider)
+ },
+ );
+ }
+ }
+
+ if (!wasCancelled) {
+ // pop building dialog
+
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+
+ txData["note"] =
+ "${trade.payInCurrency.toUpperCase()}/${trade.payOutCurrency.toUpperCase()} exchange";
+ txData["address"] = address;
+
+ if (mounted) {
+ await Navigator.of(context).push(
+ RouteGenerator.getRoute(
+ shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
+ builder: (_) => ConfirmChangeNowSendView(
+ transactionInfo: txData,
+ walletId: walletId,
+ routeOnSuccessName: HomeView.routeName,
+ trade: trade,
+ shouldSendPublicFiroFunds: shouldSendPublicFiroFunds,
+ ),
+ settings: const RouteSettings(
+ name: ConfirmChangeNowSendView.routeName,
+ ),
+ ),
+ );
+ }
+ }
+ } catch (e) {
+ // if (mounted) {
+ // pop building dialog
+ Navigator.of(context).pop();
+
+ await showDialog(
+ context: context,
+ useSafeArea: false,
+ barrierDismissible: true,
+ builder: (context) {
+ return StackDialog(
+ title: "Transaction failed",
+ message: e.toString(),
+ rightButton: TextButton(
+ style: Theme.of(context)
+ .extension()!
+ .getSecondaryEnabledButtonColor(context),
+ child: Text(
+ "Ok",
+ style: STextStyles.button(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .buttonTextSecondary,
+ ),
+ ),
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ );
+ },
+ );
+ // }
+ }
+ }
+
@override
void initState() {
walletId = widget.walletId;
@@ -182,181 +310,278 @@ class _SendFromCardState extends ConsumerState {
final coin = manager.coin;
+ final isFiro = coin == Coin.firoTestNet || coin == Coin.firo;
+
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
- child: MaterialButton(
- splashColor: Theme.of(context).extension()!.highlight,
- key: Key("walletsSheetItemButtonKey_$walletId"),
- padding: const EdgeInsets.all(8),
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
+ child: ConditionalParent(
+ condition: isFiro,
+ builder: (child) => Expandable(
+ header: Container(
+ color: Colors.transparent,
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: child,
+ ),
+ ),
+ body: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ MaterialButton(
+ splashColor:
+ Theme.of(context).extension()!.highlight,
+ key: Key("walletsSheetItemButtonFiroPrivateKey_$walletId"),
+ padding: const EdgeInsets.all(0),
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ onPressed: () => _send(
+ manager,
+ shouldSendPublicFiroFunds: false,
+ ),
+ child: Container(
+ color: Colors.transparent,
+ child: Padding(
+ padding: const EdgeInsets.only(
+ top: 6,
+ left: 16,
+ right: 16,
+ bottom: 6,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Use private balance",
+ style: STextStyles.itemSubtitle(context),
+ ),
+ FutureBuilder(
+ future: (manager.wallet as FiroWallet)
+ .availablePrivateBalance(),
+ builder: (builderContext,
+ AsyncSnapshot snapshot) {
+ if (snapshot.connectionState ==
+ ConnectionState.done &&
+ snapshot.hasData) {
+ return Text(
+ "${Format.localizedStringAsFixed(
+ value: snapshot.data!,
+ locale: locale,
+ decimalPlaces: Constants.decimalPlaces,
+ )} ${coin.ticker}",
+ style: STextStyles.itemSubtitle(context),
+ );
+ } else {
+ return AnimatedText(
+ stringsToLoopThrough: const [
+ "Loading balance",
+ "Loading balance.",
+ "Loading balance..",
+ "Loading balance..."
+ ],
+ style: STextStyles.itemSubtitle(context),
+ );
+ }
+ },
+ ),
+ ],
+ ),
+ SvgPicture.asset(
+ Assets.svg.chevronRight,
+ height: 14,
+ width: 7,
+ color: Theme.of(context)
+ .extension()!
+ .infoItemLabel,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ MaterialButton(
+ splashColor:
+ Theme.of(context).extension()!.highlight,
+ key: Key("walletsSheetItemButtonFiroPublicKey_$walletId"),
+ padding: const EdgeInsets.all(0),
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ onPressed: () => _send(
+ manager,
+ shouldSendPublicFiroFunds: true,
+ ),
+ child: Container(
+ color: Colors.transparent,
+ child: Padding(
+ padding: const EdgeInsets.only(
+ top: 6,
+ left: 16,
+ right: 16,
+ bottom: 6,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Use public balance",
+ style: STextStyles.itemSubtitle(context),
+ ),
+ FutureBuilder(
+ future: (manager.wallet as FiroWallet)
+ .availablePublicBalance(),
+ builder: (builderContext,
+ AsyncSnapshot snapshot) {
+ if (snapshot.connectionState ==
+ ConnectionState.done &&
+ snapshot.hasData) {
+ return Text(
+ "${Format.localizedStringAsFixed(
+ value: snapshot.data!,
+ locale: locale,
+ decimalPlaces: Constants.decimalPlaces,
+ )} ${coin.ticker}",
+ style: STextStyles.itemSubtitle(context),
+ );
+ } else {
+ return AnimatedText(
+ stringsToLoopThrough: const [
+ "Loading balance",
+ "Loading balance.",
+ "Loading balance..",
+ "Loading balance..."
+ ],
+ style: STextStyles.itemSubtitle(context),
+ );
+ }
+ },
+ ),
+ ],
+ ),
+ SvgPicture.asset(
+ Assets.svg.chevronRight,
+ height: 14,
+ width: 7,
+ color: Theme.of(context)
+ .extension()!
+ .infoItemLabel,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 6,
+ ),
+ ],
),
),
- onPressed: () async {
- final _amount = Format.decimalAmountToSatoshis(amount);
-
- try {
- bool wasCancelled = false;
-
- unawaited(showDialog(
- context: context,
- useSafeArea: false,
- barrierDismissible: false,
- builder: (context) {
- return BuildingTransactionDialog(
- onCancel: () {
- wasCancelled = true;
-
- Navigator.of(context).pop();
- },
- );
- },
- ));
-
- final txData = await manager.prepareSend(
- address: address,
- satoshiAmount: _amount,
- args: {
- "feeRate": FeeRateType.average,
- // ref.read(feeRateTypeStateProvider)
- },
- );
-
- if (!wasCancelled) {
- // pop building dialog
-
- if (mounted) {
- Navigator.of(context).pop();
- }
-
- txData["note"] =
- "${trade.payInCurrency.toUpperCase()}/${trade.payOutCurrency.toUpperCase()} exchange";
- txData["address"] = address;
-
- if (mounted) {
- await Navigator.of(context).push(
- RouteGenerator.getRoute(
- shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
- builder: (_) => ConfirmChangeNowSendView(
- transactionInfo: txData,
- walletId: walletId,
- routeOnSuccessName: HomeView.routeName,
- trade: trade,
- ),
- settings: const RouteSettings(
- name: ConfirmChangeNowSendView.routeName,
- ),
+ child: ConditionalParent(
+ condition: !isFiro,
+ builder: (child) => MaterialButton(
+ splashColor: Theme.of(context).extension()!.highlight,
+ key: Key("walletsSheetItemButtonKey_$walletId"),
+ padding: const EdgeInsets.all(8),
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ onPressed: () => _send(manager),
+ child: child,
+ ),
+ child: Row(
+ children: [
+ Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .colorForCoin(manager.coin)
+ .withOpacity(0.5),
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
),
- );
- }
- }
- } catch (e) {
- // if (mounted) {
- // pop building dialog
- Navigator.of(context).pop();
-
- await showDialog(
- context: context,
- useSafeArea: false,
- barrierDismissible: true,
- builder: (context) {
- return StackDialog(
- title: "Transaction failed",
- message: e.toString(),
- rightButton: TextButton(
- style: Theme.of(context)
- .extension()!
- .getSecondaryEnabledButtonColor(context),
- child: Text(
- "Ok",
- style: STextStyles.button(context).copyWith(
- color: Theme.of(context)
- .extension()!
- .buttonTextSecondary,
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(6),
+ child: SvgPicture.asset(
+ Assets.svg.iconFor(coin: coin),
+ width: 24,
+ height: 24,
+ ),
+ ),
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ Expanded(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ manager.walletName,
+ style: STextStyles.titleBold12(context),
+ ),
+ if (!isFiro)
+ const SizedBox(
+ height: 2,
),
- ),
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- );
- },
- );
- // }
- }
- },
- child: Row(
- children: [
- Container(
- decoration: BoxDecoration(
- color: Theme.of(context)
- .extension()!
- .colorForCoin(manager.coin)
- .withOpacity(0.5),
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
+ if (!isFiro)
+ FutureBuilder(
+ future: manager.totalBalance,
+ builder:
+ (builderContext, AsyncSnapshot snapshot) {
+ if (snapshot.connectionState ==
+ ConnectionState.done &&
+ snapshot.hasData) {
+ return Text(
+ "${Format.localizedStringAsFixed(
+ value: snapshot.data!,
+ locale: locale,
+ decimalPlaces: coin == Coin.monero
+ ? Constants.decimalPlacesMonero
+ : coin == Coin.wownero
+ ? Constants.decimalPlacesWownero
+ : Constants.decimalPlaces,
+ )} ${coin.ticker}",
+ style: STextStyles.itemSubtitle(context),
+ );
+ } else {
+ return AnimatedText(
+ stringsToLoopThrough: const [
+ "Loading balance",
+ "Loading balance.",
+ "Loading balance..",
+ "Loading balance..."
+ ],
+ style: STextStyles.itemSubtitle(context),
+ );
+ }
+ },
+ ),
+ ],
),
),
- child: Padding(
- padding: const EdgeInsets.all(6),
- child: SvgPicture.asset(
- Assets.svg.iconFor(coin: coin),
- width: 24,
- height: 24,
- ),
- ),
- ),
- const SizedBox(
- width: 12,
- ),
- Expanded(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- manager.walletName,
- style: STextStyles.titleBold12(context),
- ),
- const SizedBox(
- height: 2,
- ),
- FutureBuilder(
- future: manager.totalBalance,
- builder: (builderContext, AsyncSnapshot snapshot) {
- if (snapshot.connectionState == ConnectionState.done &&
- snapshot.hasData) {
- return Text(
- "${Format.localizedStringAsFixed(
- value: snapshot.data!,
- locale: locale,
- decimalPlaces: coin == Coin.monero
- ? Constants.decimalPlacesMonero
- : coin == Coin.wownero
- ? Constants.decimalPlacesWownero
- : Constants.decimalPlaces,
- )} ${coin.ticker}",
- style: STextStyles.itemSubtitle(context),
- );
- } else {
- return AnimatedText(
- stringsToLoopThrough: const [
- "Loading balance",
- "Loading balance.",
- "Loading balance..",
- "Loading balance..."
- ],
- style: STextStyles.itemSubtitle(context),
- );
- }
- },
- ),
- ],
- ),
- ),
- ],
+ ],
+ ),
),
),
);
diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart
index 4c3c4c968..ae615bd96 100644
--- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart
+++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart
@@ -11,6 +11,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
+import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
@@ -101,25 +102,28 @@ class _GenerateUriQrCodeViewState extends State {
return null;
}
- String query = "";
+ Map queryParams = {};
if (amountString.isNotEmpty) {
- query += "amount=$amountString";
+ queryParams["amount"] = amountString;
}
if (noteString.isNotEmpty) {
- if (query.isNotEmpty) {
- query += "&";
- }
- query += "message=$noteString";
+ queryParams["message"] = noteString;
}
- final uri = Uri(
- scheme: widget.coin.uriScheme,
- host: widget.receivingAddress,
- query: query.isNotEmpty ? query : null,
- );
+ String receivingAddress = widget.receivingAddress;
+ if ((widget.coin == Coin.bitcoincash ||
+ widget.coin == Coin.bitcoincashTestnet) &&
+ receivingAddress.contains(":")) {
+ // remove cash addr prefix
+ receivingAddress = receivingAddress.split(":").sublist(1).join();
+ }
- final uriString = uri.toString().replaceFirst("://", ":");
+ final uriString = AddressUtils.buildUriString(
+ widget.coin,
+ receivingAddress,
+ queryParams,
+ );
Logging.instance.log("Generated receiving QR code for: $uriString",
level: LogLevel.Info);
@@ -229,10 +233,21 @@ class _GenerateUriQrCodeViewState extends State {
@override
void initState() {
isDesktop = Util.isDesktop;
- _uriString = Uri(
- scheme: widget.coin.uriScheme,
- host: widget.receivingAddress,
- ).toString().replaceFirst("://", ":");
+
+ String receivingAddress = widget.receivingAddress;
+ if ((widget.coin == Coin.bitcoincash ||
+ widget.coin == Coin.bitcoincashTestnet) &&
+ receivingAddress.contains(":")) {
+ // remove cash addr prefix
+ receivingAddress = receivingAddress.split(":").sublist(1).join();
+ }
+
+ _uriString = AddressUtils.buildUriString(
+ widget.coin,
+ receivingAddress,
+ {},
+ );
+
amountController = TextEditingController();
noteController = TextEditingController();
super.initState();
diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart
index 81d5a3da2..26d1231f0 100644
--- a/lib/pages/send_view/confirm_transaction_view.dart
+++ b/lib/pages/send_view/confirm_transaction_view.dart
@@ -87,13 +87,13 @@ class _ConfirmTransactionViewState
txid = await manager.confirmSend(txData: transactionInfo);
}
- unawaited(manager.refresh());
-
// save note
await ref
.read(notesServiceChangeNotifierProvider(walletId))
.editOrAddNote(txid: txid, note: note);
+ unawaited(manager.refresh());
+
// pop back to wallet
if (mounted) {
Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName));
diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart
index 87aee413e..382e3f09e 100644
--- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart
+++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart
@@ -110,7 +110,29 @@ class _AddEditNodeViewState extends ConsumerState {
ref.read(nodeFormDataProvider).useSSL = false;
}
- testPassed = await testMoneroNodeConnection(Uri.parse(uriString));
+ final response = await testMoneroNodeConnection(
+ Uri.parse(uriString),
+ false,
+ );
+
+ if (response.cert != null) {
+ if (mounted) {
+ final shouldAllowBadCert = await showBadX509CertificateDialog(
+ response.cert!,
+ response.url!,
+ response.port!,
+ context,
+ );
+
+ if (shouldAllowBadCert) {
+ final response = await testMoneroNodeConnection(
+ Uri.parse(uriString), true);
+ testPassed = response.success;
+ }
+ }
+ } else {
+ testPassed = response.success;
+ }
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Warning);
diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart
index c5e666ce2..f9b64c460 100644
--- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart
+++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart
@@ -97,7 +97,29 @@ class _NodeDetailsViewState extends ConsumerState {
String uriString = "${uri.scheme}://${uri.host}:${node.port}$path";
- testPassed = await testMoneroNodeConnection(Uri.parse(uriString));
+ final response = await testMoneroNodeConnection(
+ Uri.parse(uriString),
+ false,
+ );
+
+ if (response.cert != null) {
+ if (mounted) {
+ final shouldAllowBadCert = await showBadX509CertificateDialog(
+ response.cert!,
+ response.url!,
+ response.port!,
+ context,
+ );
+
+ if (shouldAllowBadCert) {
+ final response = await testMoneroNodeConnection(
+ Uri.parse(uriString), true);
+ testPassed = response.success;
+ }
+ }
+ } else {
+ testPassed = response.success;
+ }
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Warning);
diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart
index 9242c0482..eacdda66a 100644
--- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart
+++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart
@@ -15,7 +15,10 @@ import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/progress_bar.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
@@ -93,316 +96,469 @@ class _RestoreFromFileViewState extends State {
@override
Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () async {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future.delayed(const Duration(milliseconds: 75));
- }
- if (mounted) {
- Navigator.of(context).pop();
- }
- },
- ),
- title: Text(
- "Create backup",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: Padding(
- padding: const EdgeInsets.all(16),
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
+ final isDesktop = Util.isDesktop;
+
+ return ConditionalParent(
+ condition: !isDesktop,
+ builder: (child) {
+ return Scaffold(
+ backgroundColor:
+ Theme.of(context).extension()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future.delayed(const Duration(milliseconds: 75));
+ }
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ },
+ ),
+ title: Text(
+ "Create backup",
+ style: STextStyles.navBarTitle(context),
+ ),
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(16),
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ return SingleChildScrollView(
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight: constraints.maxHeight,
+ ),
+ child: IntrinsicHeight(
+ child: child,
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ );
+ },
+ child: ConditionalParent(
+ condition: isDesktop,
+ builder: (child) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(bottom: 10),
+ child: Text(
+ "Choose file location",
+ style: STextStyles.desktopTextExtraExtraSmall(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark3),
),
- child: IntrinsicHeight(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- if (!Platform.isAndroid)
- Consumer(builder: (context, ref, __) {
- return Container(
- color: Colors.transparent,
- child: TextField(
- autocorrect: Util.isDesktop ? false : true,
- enableSuggestions: Util.isDesktop ? false : true,
- onTap: Platform.isAndroid
- ? null
- : () async {
- try {
- await stackFileSystem.prepareStorage();
+ ),
+ child,
+ ],
+ );
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ if (!Platform.isAndroid)
+ Consumer(builder: (context, ref, __) {
+ return Container(
+ color: Colors.transparent,
+ child: TextField(
+ autocorrect: Util.isDesktop ? false : true,
+ enableSuggestions: Util.isDesktop ? false : true,
+ onTap: Platform.isAndroid
+ ? null
+ : () async {
+ try {
+ await stackFileSystem.prepareStorage();
- if (mounted) {
- await stackFileSystem
- .pickDir(context);
- }
+ if (mounted) {
+ await stackFileSystem.pickDir(context);
+ }
- if (mounted) {
- setState(() {
- fileLocationController.text =
- stackFileSystem.dirPath ?? "";
- });
- }
- } catch (e, s) {
- Logging.instance.log("$e\n$s",
- level: LogLevel.Error);
- }
- },
- controller: fileLocationController,
- style: STextStyles.field(context),
- decoration: InputDecoration(
- hintText: "Save to...",
- hintStyle: STextStyles.fieldLabel(context),
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(
- width: 16,
- ),
- SvgPicture.asset(
- Assets.svg.folder,
- color: Theme.of(context)
- .extension()!
- .textDark3,
- width: 16,
- height: 16,
- ),
- const SizedBox(
- width: 12,
- ),
- ],
- ),
- ),
- ),
- key: const Key(
- "createBackupSaveToFileLocationTextFieldKey"),
- readOnly: true,
- toolbarOptions: const ToolbarOptions(
- copy: true,
- cut: false,
- paste: false,
- selectAll: false,
- ),
- onChanged: (newValue) {
- // ref.read(addressEntryDataProvider(widget.id)).address = newValue;
- },
+ if (mounted) {
+ setState(() {
+ fileLocationController.text =
+ stackFileSystem.dirPath ?? "";
+ });
+ }
+ } catch (e, s) {
+ Logging.instance
+ .log("$e\n$s", level: LogLevel.Error);
+ }
+ },
+ controller: fileLocationController,
+ style: STextStyles.field(context),
+ decoration: InputDecoration(
+ hintText: "Save to...",
+ hintStyle: STextStyles.fieldLabel(context),
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(
+ width: 16,
),
- );
- }),
- if (!Platform.isAndroid)
+ SvgPicture.asset(
+ Assets.svg.folder,
+ color: Theme.of(context)
+ .extension()!
+ .textDark3,
+ width: 16,
+ height: 16,
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ ],
+ ),
+ ),
+ ),
+ key:
+ const Key("createBackupSaveToFileLocationTextFieldKey"),
+ readOnly: true,
+ toolbarOptions: const ToolbarOptions(
+ copy: true,
+ cut: false,
+ paste: false,
+ selectAll: false,
+ ),
+ onChanged: (newValue) {
+ // ref.read(addressEntryDataProvider(widget.id)).address = newValue;
+ },
+ ),
+ );
+ }),
+ if (!Platform.isAndroid)
+ SizedBox(
+ height: !isDesktop ? 8 : 24,
+ ),
+ if (isDesktop)
+ Padding(
+ padding: const EdgeInsets.only(bottom: 10.0),
+ child: Text(
+ "Create a passphrase",
+ style: STextStyles.desktopTextExtraExtraSmall(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark3),
+ textAlign: TextAlign.left,
+ ),
+ ),
+ ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ key: const Key("createBackupPasswordFieldKey1"),
+ focusNode: passwordFocusNode,
+ controller: passwordController,
+ style: STextStyles.field(context),
+ obscureText: hidePassword,
+ enableSuggestions: false,
+ autocorrect: false,
+ decoration: standardInputDecoration(
+ "Create passphrase",
+ passwordFocusNode,
+ context,
+ ).copyWith(
+ labelStyle:
+ isDesktop ? STextStyles.fieldLabel(context) : null,
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
const SizedBox(
- height: 8,
+ width: 16,
),
- ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: const Key("createBackupPasswordFieldKey1"),
- focusNode: passwordFocusNode,
- controller: passwordController,
- style: STextStyles.field(context),
- obscureText: hidePassword,
- enableSuggestions: false,
- autocorrect: false,
- decoration: standardInputDecoration(
- "Create passphrase",
- passwordFocusNode,
- context,
- ).copyWith(
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(
- width: 16,
- ),
- GestureDetector(
- key: const Key(
- "createBackupPasswordFieldShowPasswordButtonKey"),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword
- ? Assets.svg.eye
- : Assets.svg.eyeSlash,
- color: Theme.of(context)
- .extension()!
- .textDark3,
- width: 16,
- height: 16,
- ),
- ),
- const SizedBox(
- width: 12,
- ),
- ],
- ),
- ),
- ),
- onChanged: (newValue) {
- if (newValue.isEmpty) {
- setState(() {
- passwordFeedback = "";
- });
- return;
- }
- final result = zxcvbn.evaluate(newValue);
- String suggestionsAndTips = "";
- for (var sug
- in result.feedback.suggestions!.toSet()) {
- suggestionsAndTips += "$sug\n";
- }
- suggestionsAndTips += result.feedback.warning!;
- String feedback =
- // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n"
- suggestionsAndTips;
-
- passwordStrength = result.score! / 4;
-
- // hack fix to format back string returned from zxcvbn
- if (feedback.contains("phrasesNo need")) {
- feedback = feedback.replaceFirst(
- "phrasesNo need", "phrases\nNo need");
- }
-
- if (feedback.endsWith("\n")) {
- feedback =
- feedback.substring(0, feedback.length - 2);
- }
-
+ GestureDetector(
+ key: const Key(
+ "createBackupPasswordFieldShowPasswordButtonKey"),
+ onTap: () async {
setState(() {
- passwordFeedback = feedback;
+ hidePassword = !hidePassword;
});
},
- ),
- ),
- if (passwordFocusNode.hasFocus ||
- passwordRepeatFocusNode.hasFocus ||
- passwordController.text.isNotEmpty)
- Padding(
- padding: EdgeInsets.only(
- left: 12,
- right: 12,
- top: passwordFeedback.isNotEmpty ? 4 : 0,
- ),
- child: passwordFeedback.isNotEmpty
- ? Text(
- passwordFeedback,
- style: STextStyles.infoSmall(context),
- )
- : null,
- ),
- if (passwordFocusNode.hasFocus ||
- passwordRepeatFocusNode.hasFocus ||
- passwordController.text.isNotEmpty)
- Padding(
- padding: const EdgeInsets.only(
- left: 12,
- right: 12,
- top: 10,
- ),
- child: ProgressBar(
- key: const Key("createStackBackUpProgressBar"),
- width: MediaQuery.of(context).size.width - 32 - 24,
- height: 5,
- fillColor: passwordStrength < 0.51
- ? Theme.of(context)
- .extension()!
- .accentColorRed
- : passwordStrength < 1
- ? Theme.of(context)
- .extension()!
- .accentColorYellow
- : Theme.of(context)
- .extension()!
- .accentColorGreen,
- backgroundColor: Theme.of(context)
+ child: SvgPicture.asset(
+ hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash,
+ color: Theme.of(context)
.extension()!
- .buttonBackSecondary,
- percent: passwordStrength < 0.25
- ? 0.03
- : passwordStrength,
+ .textDark3,
+ width: 16,
+ height: 16,
),
),
- const SizedBox(
- height: 10,
- ),
- ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
+ const SizedBox(
+ width: 12,
),
- child: TextField(
- key: const Key("createBackupPasswordFieldKey2"),
- focusNode: passwordRepeatFocusNode,
- controller: passwordRepeatController,
- style: STextStyles.field(context),
- obscureText: hidePassword,
- enableSuggestions: false,
- autocorrect: false,
- decoration: standardInputDecoration(
- "Confirm passphrase",
- passwordRepeatFocusNode,
- context,
- ).copyWith(
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(
- width: 16,
- ),
- GestureDetector(
- key: const Key(
- "createBackupPasswordFieldShowPasswordButtonKey"),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword
- ? Assets.svg.eye
- : Assets.svg.eyeSlash,
- color: Theme.of(context)
- .extension()!
- .textDark3,
- width: 16,
- height: 16,
- ),
- ),
- const SizedBox(
- width: 12,
- ),
- ],
- ),
- ),
- ),
- onChanged: (newValue) {
- setState(() {});
- // TODO: ? check if passwords match?
+ ],
+ ),
+ ),
+ ),
+ onChanged: (newValue) {
+ if (newValue.isEmpty) {
+ setState(() {
+ passwordFeedback = "";
+ });
+ return;
+ }
+ final result = zxcvbn.evaluate(newValue);
+ String suggestionsAndTips = "";
+ for (var sug in result.feedback.suggestions!.toSet()) {
+ suggestionsAndTips += "$sug\n";
+ }
+ suggestionsAndTips += result.feedback.warning!;
+ String feedback =
+ // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n"
+ suggestionsAndTips;
+
+ passwordStrength = result.score! / 4;
+
+ // hack fix to format back string returned from zxcvbn
+ if (feedback.contains("phrasesNo need")) {
+ feedback = feedback.replaceFirst(
+ "phrasesNo need", "phrases\nNo need");
+ }
+
+ if (feedback.endsWith("\n")) {
+ feedback = feedback.substring(0, feedback.length - 2);
+ }
+
+ setState(() {
+ passwordFeedback = feedback;
+ });
+ },
+ ),
+ ),
+ if (passwordFocusNode.hasFocus ||
+ passwordRepeatFocusNode.hasFocus ||
+ passwordController.text.isNotEmpty)
+ Padding(
+ padding: EdgeInsets.only(
+ left: 12,
+ right: 12,
+ top: passwordFeedback.isNotEmpty ? 4 : 0,
+ ),
+ child: passwordFeedback.isNotEmpty
+ ? Text(
+ passwordFeedback,
+ style: STextStyles.infoSmall(context),
+ )
+ : null,
+ ),
+ if (passwordFocusNode.hasFocus ||
+ passwordRepeatFocusNode.hasFocus ||
+ passwordController.text.isNotEmpty)
+ Padding(
+ padding: const EdgeInsets.only(
+ left: 12,
+ right: 12,
+ top: 10,
+ ),
+ child: ProgressBar(
+ key: const Key("createStackBackUpProgressBar"),
+ width: MediaQuery.of(context).size.width - 32 - 24,
+ height: 5,
+ fillColor: passwordStrength < 0.51
+ ? Theme.of(context)
+ .extension()!
+ .accentColorRed
+ : passwordStrength < 1
+ ? Theme.of(context)
+ .extension()!
+ .accentColorYellow
+ : Theme.of(context)
+ .extension()!
+ .accentColorGreen,
+ backgroundColor: Theme.of(context)
+ .extension()!
+ .buttonBackSecondary,
+ percent: passwordStrength < 0.25 ? 0.03 : passwordStrength,
+ ),
+ ),
+ const SizedBox(
+ height: 10,
+ ),
+ ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ key: const Key("createBackupPasswordFieldKey2"),
+ focusNode: passwordRepeatFocusNode,
+ controller: passwordRepeatController,
+ style: STextStyles.field(context),
+ obscureText: hidePassword,
+ enableSuggestions: false,
+ autocorrect: false,
+ decoration: standardInputDecoration(
+ "Confirm passphrase",
+ passwordRepeatFocusNode,
+ context,
+ ).copyWith(
+ labelStyle:
+ isDesktop ? STextStyles.fieldLabel(context) : null,
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(
+ width: 16,
+ ),
+ GestureDetector(
+ key: const Key(
+ "createBackupPasswordFieldShowPasswordButtonKey"),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
},
+ child: SvgPicture.asset(
+ hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash,
+ color: Theme.of(context)
+ .extension()!
+ .textDark3,
+ width: 16,
+ height: 16,
+ ),
),
- ),
- const SizedBox(
- height: 16,
- ),
- const Spacer(),
- TextButton(
- style: shouldEnableCreate
- ? Theme.of(context)
- .extension()!
- .getPrimaryEnabledButtonColor(context)
- : Theme.of(context)
- .extension()!
- .getPrimaryDisabledButtonColor(context),
+ const SizedBox(
+ width: 12,
+ ),
+ ],
+ ),
+ ),
+ ),
+ onChanged: (newValue) {
+ setState(() {});
+ // TODO: ? check if passwords match?
+ },
+ ),
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ if (!isDesktop) const Spacer(),
+ !isDesktop
+ ? TextButton(
+ style: shouldEnableCreate
+ ? Theme.of(context)
+ .extension()!
+ .getPrimaryEnabledButtonColor(context)
+ : Theme.of(context)
+ .extension()!
+ .getPrimaryDisabledButtonColor(context),
+ onPressed: !shouldEnableCreate
+ ? null
+ : () async {
+ final String pathToSave =
+ fileLocationController.text;
+ final String passphrase = passwordController.text;
+ final String repeatPassphrase =
+ passwordRepeatController.text;
+
+ if (pathToSave.isEmpty) {
+ unawaited(showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "Directory not chosen",
+ context: context,
+ ));
+ return;
+ }
+ if (!(await Directory(pathToSave).exists())) {
+ unawaited(showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "Directory does not exist",
+ context: context,
+ ));
+ return;
+ }
+ if (passphrase.isEmpty) {
+ unawaited(showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "A passphrase is required",
+ context: context,
+ ));
+ return;
+ }
+ if (passphrase != repeatPassphrase) {
+ unawaited(showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "Passphrase does not match",
+ context: context,
+ ));
+ return;
+ }
+
+ unawaited(showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => const StackDialog(
+ title: "Encrypting backup",
+ message: "This shouldn't take long",
+ ),
+ ));
+ // make sure the dialog is able to be displayed for at least 1 second
+ await Future.delayed(
+ const Duration(seconds: 1));
+
+ final DateTime now = DateTime.now();
+ final String fileToSave =
+ "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb";
+
+ final backup = await SWB.createStackWalletJSON();
+
+ bool result =
+ await SWB.encryptStackWalletWithPassphrase(
+ fileToSave,
+ passphrase,
+ jsonEncode(backup),
+ );
+
+ if (mounted) {
+ // pop encryption progress dialog
+ Navigator.of(context).pop();
+
+ if (result) {
+ await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => Platform.isAndroid
+ ? StackOkDialog(
+ title: "Backup saved to:",
+ message: fileToSave,
+ )
+ : const StackOkDialog(
+ title: "Backup creation succeeded"),
+ );
+ passwordController.text = "";
+ passwordRepeatController.text = "";
+ setState(() {});
+ } else {
+ await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => const StackOkDialog(
+ title: "Backup creation failed"),
+ );
+ }
+ }
+ },
+ child: Text(
+ "Create backup",
+ style: STextStyles.button(context),
+ ),
+ )
+ : Row(
+ children: [
+ PrimaryButton(
+ width: 183,
+ desktopMed: true,
+ label: "Create backup",
+ enabled: shouldEnableCreate,
onPressed: !shouldEnableCreate
? null
: () async {
@@ -502,17 +658,19 @@ class _RestoreFromFileViewState extends State {
}
}
},
- child: Text(
- "Create backup",
- style: STextStyles.button(context),
- ),
+ ),
+ const SizedBox(
+ width: 16,
+ ),
+ SecondaryButton(
+ width: 183,
+ desktopMed: true,
+ label: "Cancel",
+ onPressed: () {},
),
],
),
- ),
- ),
- );
- },
+ ],
),
),
);
diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart
index 232be9028..ee1fcf666 100644
--- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart
+++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart
@@ -1,3 +1,4 @@
+import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
@@ -8,6 +9,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart';
+// import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
@@ -15,13 +17,17 @@ import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
+import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
+import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:tuple/tuple.dart';
-import 'package:stackwallet/utilities/util.dart';
-
class RestoreFromFileView extends ConsumerStatefulWidget {
const RestoreFromFileView({Key? key}) : super(key: key);
@@ -42,6 +48,17 @@ class _RestoreFromFileViewState extends ConsumerState {
bool hidePassword = true;
+ Future restoreBackupPopup(BuildContext context) async {
+ // await showDialog(
+ // context: context,
+ // useSafeArea: false,
+ // barrierDismissible: true,
+ // builder: (context) {
+ // return const RestoreBackupDialog();
+ // },
+ // );
+ }
+
@override
void initState() {
stackFileSystem = StackFileSystem();
@@ -65,190 +82,243 @@ class _RestoreFromFileViewState extends ConsumerState {
@override
Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () async {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future.delayed(const Duration(milliseconds: 75));
- }
- if (mounted) {
- Navigator.of(context).pop();
- }
- },
- ),
- title: Text(
- "Restore from file",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: Padding(
- padding: const EdgeInsets.all(16),
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
+ final isDesktop = Util.isDesktop;
+
+ return ConditionalParent(
+ condition: !isDesktop,
+ builder: (child) {
+ return Scaffold(
+ backgroundColor:
+ Theme.of(context).extension()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future.delayed(
+ const Duration(milliseconds: 75));
+ }
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ },
+ ),
+ title: Text(
+ "Restore from file",
+ style: STextStyles.navBarTitle(context),
+ ),
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(16),
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ return SingleChildScrollView(
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight: constraints.maxHeight,
+ ),
+ child: IntrinsicHeight(
+ child: child,
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ );
+ },
+ child: ConditionalParent(
+ condition: isDesktop,
+ builder: (child) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(bottom: 10.0),
+ child: Text(
+ "Choose file location",
+ style: STextStyles.desktopTextExtraExtraSmall(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark3),
+ textAlign: TextAlign.left,
+ ),
),
- child: IntrinsicHeight(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- TextField(
- autocorrect: Util.isDesktop ? false : true,
- enableSuggestions: Util.isDesktop ? false : true,
- onTap: () async {
- try {
- await stackFileSystem.prepareStorage();
- if (mounted) {
- await stackFileSystem.openFile(context);
- }
+ child,
+ ],
+ );
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ TextField(
+ autocorrect: Util.isDesktop ? false : true,
+ enableSuggestions: Util.isDesktop ? false : true,
+ onTap: () async {
+ try {
+ await stackFileSystem.prepareStorage();
+ if (mounted) {
+ await stackFileSystem.openFile(context);
+ }
- if (mounted) {
+ if (mounted) {
+ setState(() {
+ fileLocationController.text =
+ stackFileSystem.filePath ?? "";
+ });
+ }
+ } catch (e, s) {
+ Logging.instance.log("$e\n$s", level: LogLevel.Error);
+ }
+ },
+ controller: fileLocationController,
+ style: STextStyles.field(context),
+ decoration: InputDecoration(
+ hintText: "Choose file...",
+ hintStyle: STextStyles.fieldLabel(context),
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(
+ width: 16,
+ ),
+ SvgPicture.asset(
+ Assets.svg.folder,
+ color: Theme.of(context)
+ .extension()!
+ .textDark3,
+ width: 16,
+ height: 16,
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ ],
+ ),
+ ),
+ ),
+ key: const Key("restoreFromFileLocationTextFieldKey"),
+ readOnly: true,
+ toolbarOptions: const ToolbarOptions(
+ copy: true,
+ cut: false,
+ paste: false,
+ selectAll: false,
+ ),
+ onChanged: (newValue) {},
+ ),
+ SizedBox(
+ height: !isDesktop ? 8 : 24,
+ ),
+ if (isDesktop)
+ Padding(
+ padding: const EdgeInsets.only(bottom: 10.0),
+ child: Text(
+ "Enter passphrase",
+ style: STextStyles.desktopTextExtraExtraSmall(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark3),
+ textAlign: TextAlign.left,
+ ),
+ ),
+ ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ key: const Key("restoreFromFilePasswordFieldKey"),
+ focusNode: passwordFocusNode,
+ controller: passwordController,
+ style: STextStyles.field(context),
+ obscureText: hidePassword,
+ enableSuggestions: false,
+ autocorrect: false,
+ decoration: standardInputDecoration(
+ "Enter password",
+ passwordFocusNode,
+ context,
+ ).copyWith(
+ labelStyle:
+ isDesktop ? STextStyles.fieldLabel(context) : null,
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(
+ width: 16,
+ ),
+ GestureDetector(
+ key: const Key(
+ "restoreFromFilePasswordFieldShowPasswordButtonKey"),
+ onTap: () async {
setState(() {
- fileLocationController.text =
- stackFileSystem.filePath ?? "";
+ hidePassword = !hidePassword;
});
- }
- } catch (e, s) {
- Logging.instance
- .log("$e\n$s", level: LogLevel.Error);
- }
- },
- controller: fileLocationController,
- style: STextStyles.field(context),
- decoration: InputDecoration(
- hintText: "Choose file...",
- hintStyle: STextStyles.fieldLabel(context),
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(
- width: 16,
- ),
- SvgPicture.asset(
- Assets.svg.folder,
- color: Theme.of(context)
- .extension()!
- .textDark3,
- width: 16,
- height: 16,
- ),
- const SizedBox(
- width: 12,
- ),
- ],
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(context)
+ .extension()!
+ .textDark3,
+ width: 16,
+ height: 16,
),
),
- ),
- key: const Key("restoreFromFileLocationTextFieldKey"),
- readOnly: true,
- toolbarOptions: const ToolbarOptions(
- copy: true,
- cut: false,
- paste: false,
- selectAll: false,
- ),
- onChanged: (newValue) {},
- ),
- const SizedBox(
- height: 8,
- ),
- ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: const Key("restoreFromFilePasswordFieldKey"),
- focusNode: passwordFocusNode,
- controller: passwordController,
- style: STextStyles.field(context),
- obscureText: hidePassword,
- enableSuggestions: false,
- autocorrect: false,
- decoration: standardInputDecoration(
- "Enter password",
- passwordFocusNode,
- context,
- ).copyWith(
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(
- width: 16,
- ),
- GestureDetector(
- key: const Key(
- "restoreFromFilePasswordFieldShowPasswordButtonKey"),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword
- ? Assets.svg.eye
- : Assets.svg.eyeSlash,
- color: Theme.of(context)
- .extension()!
- .textDark3,
- width: 16,
- height: 16,
- ),
- ),
- const SizedBox(
- width: 12,
- ),
- ],
- ),
- ),
+ const SizedBox(
+ width: 12,
),
- onChanged: (newValue) {
- setState(() {});
- },
- ),
+ ],
),
- const SizedBox(
- height: 16,
- ),
- const Spacer(),
- TextButton(
- style: passwordController.text.isEmpty ||
- fileLocationController.text.isEmpty
- ? Theme.of(context)
- .extension()!
- .getPrimaryDisabledButtonColor(context)
- : Theme.of(context)
- .extension()!
- .getPrimaryEnabledButtonColor(context),
- onPressed: passwordController.text.isEmpty ||
- fileLocationController.text.isEmpty
- ? null
- : () async {
- final String fileToRestore =
- fileLocationController.text;
- final String passphrase =
- passwordController.text;
+ ),
+ ),
+ onChanged: (newValue) {
+ setState(() {});
+ },
+ ),
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ if (!isDesktop) const Spacer(),
+ !isDesktop
+ ? TextButton(
+ style: passwordController.text.isEmpty ||
+ fileLocationController.text.isEmpty
+ ? Theme.of(context)
+ .extension()!
+ .getPrimaryDisabledButtonColor(context)
+ : Theme.of(context)
+ .extension()!
+ .getPrimaryEnabledButtonColor(context),
+ onPressed: passwordController.text.isEmpty ||
+ fileLocationController.text.isEmpty
+ ? null
+ : () async {
+ final String fileToRestore =
+ fileLocationController.text;
+ final String passphrase = passwordController.text;
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future.delayed(
- const Duration(milliseconds: 75));
- }
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future.delayed(
+ const Duration(milliseconds: 75));
+ }
- if (!(await File(fileToRestore).exists())) {
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Backup file does not exist",
- context: context,
- );
- return;
- }
+ if (!(await File(fileToRestore).exists())) {
+ await showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "Backup file does not exist",
+ context: context,
+ );
+ return;
+ }
- bool shouldPop = false;
+ bool shouldPop = false;
+ unawaited(
showDialog(
barrierDismissible: false,
context: context,
@@ -288,52 +358,233 @@ class _RestoreFromFileViewState extends ConsumerState {
],
),
),
+ ),
+ );
+
+ final String? jsonString = await compute(
+ SWB.decryptStackWalletWithPassphrase,
+ Tuple2(fileToRestore, passphrase),
+ debugLabel: "stack wallet decryption compute",
+ );
+
+ if (mounted) {
+ // pop LoadingIndicator
+ shouldPop = true;
+ Navigator.of(context).pop();
+
+ passwordController.text = "";
+
+ if (jsonString == null) {
+ await showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "Failed to decrypt backup file",
+ context: context,
+ );
+ return;
+ }
+
+ await Navigator.of(context).push(
+ RouteGenerator.getRoute(
+ builder: (_) => StackRestoreProgressView(
+ jsonString: jsonString,
+ ),
+ ),
);
+ }
+ },
+ child: Text(
+ "Restore",
+ style: STextStyles.button(context),
+ ),
+ )
+ : Row(
+ children: [
+ PrimaryButton(
+ width: 183,
+ desktopMed: true,
+ label: "Restore",
+ enabled: !(passwordController.text.isEmpty ||
+ fileLocationController.text.isEmpty),
+ onPressed: passwordController.text.isEmpty ||
+ fileLocationController.text.isEmpty
+ ? null
+ : () async {
+ final String fileToRestore =
+ fileLocationController.text;
+ final String passphrase =
+ passwordController.text;
- final String? jsonString = await compute(
- SWB.decryptStackWalletWithPassphrase,
- Tuple2(fileToRestore, passphrase),
- debugLabel: "stack wallet decryption compute",
- );
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future.delayed(
+ const Duration(milliseconds: 75));
+ }
- if (mounted) {
- // pop LoadingIndicator
- shouldPop = true;
- Navigator.of(context).pop();
-
- passwordController.text = "";
-
- if (jsonString == null) {
- showFloatingFlushBar(
+ if (!(await File(fileToRestore).exists())) {
+ await showFloatingFlushBar(
type: FlushBarType.warning,
- message: "Failed to decrypt backup file",
+ message: "Backup file does not exist",
context: context,
);
return;
}
- Navigator.of(context).push(
- RouteGenerator.getRoute(
- builder: (_) => StackRestoreProgressView(
- jsonString: jsonString,
+ bool shouldPop = false;
+ unawaited(
+ showDialog(
+ barrierDismissible: false,
+ context: context,
+ builder: (_) => WillPopScope(
+ onWillPop: () async {
+ return shouldPop;
+ },
+ child: Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.stretch,
+ mainAxisAlignment:
+ MainAxisAlignment.center,
+ children: [
+ Material(
+ color: Colors.transparent,
+ child: Center(
+ child: Text(
+ "Decrypting Stack backup file",
+ style:
+ STextStyles.pageTitleH2(
+ context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension<
+ StackColors>()!
+ .textWhite,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 64,
+ ),
+ const Center(
+ child: LoadingIndicator(
+ width: 100,
+ ),
+ ),
+ ],
+ ),
),
),
);
- }
- },
- child: Text(
- "Restore",
- style: STextStyles.button(context),
+
+ final String? jsonString = await compute(
+ SWB.decryptStackWalletWithPassphrase,
+ Tuple2(fileToRestore, passphrase),
+ debugLabel:
+ "stack wallet decryption compute",
+ );
+
+ if (mounted) {
+ // pop LoadingIndicator
+ shouldPop = true;
+ Navigator.of(
+ context,
+ rootNavigator: true,
+ ).pop();
+
+ passwordController.text = "";
+
+ if (jsonString == null) {
+ await showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message:
+ "Failed to decrypt backup file",
+ context: context,
+ );
+ return;
+ }
+
+ await showDialog(
+ context: context,
+ useSafeArea: false,
+ barrierDismissible: true,
+ builder: (context) {
+ return DesktopDialog(
+ maxHeight: 750,
+ maxWidth: 600,
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ return SingleChildScrollView(
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight:
+ constraints.maxHeight,
+ ),
+ child: IntrinsicHeight(
+ child: Column(
+ mainAxisAlignment:
+ MainAxisAlignment
+ .start,
+ children: [
+ Row(
+ mainAxisAlignment:
+ MainAxisAlignment
+ .spaceBetween,
+ children: [
+ Padding(
+ padding:
+ const EdgeInsets
+ .all(32),
+ child: Text(
+ "Restoring Stack Wallet",
+ style: STextStyles
+ .desktopH3(
+ context),
+ textAlign:
+ TextAlign
+ .center,
+ ),
+ ),
+ const DesktopDialogCloseButton(),
+ ],
+ ),
+ const SizedBox(
+ height: 30,
+ ),
+ Padding(
+ padding: EdgeInsets
+ .symmetric(
+ horizontal:
+ 32),
+ child:
+ StackRestoreProgressView(
+ jsonString:
+ jsonString,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ });
+ }
+ },
),
- ),
- ],
- ),
- ),
- ),
- );
- },
- ),
- ),
- );
+ const SizedBox(
+ width: 16,
+ ),
+ SecondaryButton(
+ width: 183,
+ desktopMed: true,
+ label: "Cancel",
+ onPressed: () {},
+ ),
+ ],
+ ),
+ ],
+ ),
+ ));
}
}
diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart
index 5e5142425..7dec4e740 100644
--- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart
+++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart
@@ -3,7 +3,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart';
@@ -17,6 +16,8 @@ import 'package:stackwallet/utilities/enums/stack_restoring_status.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
@@ -39,6 +40,8 @@ class StackRestoreProgressView extends ConsumerStatefulWidget {
class _StackRestoreProgressViewState
extends ConsumerState {
+ bool isDesktop = Util.isDesktop;
+
Future _cancel() async {
bool shouldPop = false;
unawaited(showDialog(
@@ -79,10 +82,15 @@ class _StackRestoreProgressViewState
await SWB.cancelRestore();
shouldPop = true;
+
+ int count = 0;
+
if (mounted) {
- Navigator.of(context).popUntil(ModalRoute.withName(widget.fromFile
- ? RestoreFromEncryptedStringView.routeName
- : StackBackupView.routeName));
+ !isDesktop
+ ? Navigator.of(context).popUntil(ModalRoute.withName(widget.fromFile
+ ? RestoreFromEncryptedStringView.routeName
+ : StackBackupView.routeName))
+ : Navigator.of(context).popUntil((_) => count++ >= 2);
}
}
@@ -179,281 +187,289 @@ class _StackRestoreProgressViewState
@override
Widget build(BuildContext context) {
- return WillPopScope(
- onWillPop: _onWillPop,
- child: Scaffold(
- backgroundColor: Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () async {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future.delayed(const Duration(milliseconds: 75));
- }
- if (_success) {
- _addWalletsToHomeView();
- if (mounted) {
- Navigator.of(context).pop();
- }
- } else {
- if (await _requestCancel()) {
- await _cancel();
- }
- }
- },
- ),
- title: Text(
- "Restoring Stack wallet",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: Padding(
- padding: const EdgeInsets.only(
- left: 12,
- top: 12,
- right: 12,
- ),
- child: SingleChildScrollView(
- child: Padding(
- padding: const EdgeInsets.only(
- left: 4,
- top: 4,
- right: 4,
- bottom: 0,
+ bool isDesktop = Util.isDesktop;
+
+ return ConditionalParent(
+ condition: !isDesktop,
+ builder: (child) {
+ return WillPopScope(
+ onWillPop: _onWillPop,
+ child: Scaffold(
+ backgroundColor:
+ Theme.of(context).extension()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future.delayed(
+ const Duration(milliseconds: 75));
+ }
+ if (_success) {
+ _addWalletsToHomeView();
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ } else {
+ if (await _requestCancel()) {
+ await _cancel();
+ }
+ }
+ },
),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- "Settings",
- style: STextStyles.itemSubtitle(context),
- ),
- const SizedBox(
- height: 12,
- ),
- Consumer(
- builder: (_, ref, __) {
- final state = ref.watch(stackRestoringUIStateProvider
- .select((value) => value.preferences));
- return RestoringItemCard(
- left: SizedBox(
- width: 32,
- height: 32,
- child: RoundedContainer(
- padding: const EdgeInsets.all(0),
+ title: Text(
+ "Restoring Stack wallet",
+ style: STextStyles.navBarTitle(context),
+ ),
+ ),
+ body: Padding(
+ padding: const EdgeInsets.only(
+ left: 12,
+ top: 12,
+ right: 12,
+ ),
+ child: child,
+ ),
+ ),
+ );
+ },
+ child: SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.only(
+ left: 4,
+ top: 4,
+ right: 4,
+ bottom: 0,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Settings",
+ style: STextStyles.itemSubtitle(context),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Consumer(
+ builder: (_, ref, __) {
+ final state = ref.watch(stackRestoringUIStateProvider
+ .select((value) => value.preferences));
+ return RestoringItemCard(
+ left: SizedBox(
+ width: 32,
+ height: 32,
+ child: RoundedContainer(
+ padding: const EdgeInsets.all(0),
+ color: Theme.of(context)
+ .extension()!
+ .buttonBackSecondary,
+ child: Center(
+ child: SvgPicture.asset(
+ Assets.svg.gear,
+ width: 16,
+ height: 16,
color: Theme.of(context)
.extension()!
- .buttonBackSecondary,
- child: Center(
- child: SvgPicture.asset(
- Assets.svg.gear,
- width: 16,
- height: 16,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ),
- ),
- ),
- right: SizedBox(
- width: 20,
- height: 20,
- child: _getIconForState(state),
- ),
- title: "Preferences",
- subTitle: state == StackRestoringStatus.failed
- ? Text(
- "Something went wrong",
- style: STextStyles.errorSmall(context),
- )
- : null,
- );
- },
- ),
- const SizedBox(
- height: 12,
- ),
- Consumer(
- builder: (_, ref, __) {
- final state = ref.watch(stackRestoringUIStateProvider
- .select((value) => value.addressBook));
- return RestoringItemCard(
- left: SizedBox(
- width: 32,
- height: 32,
- child: RoundedContainer(
- padding: const EdgeInsets.all(0),
- color: Theme.of(context)
- .extension()!
- .buttonBackSecondary,
- child: Center(
- child: AddressBookIcon(
- width: 16,
- height: 16,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ),
- ),
- ),
- right: SizedBox(
- width: 20,
- height: 20,
- child: _getIconForState(state),
- ),
- title: "Address book",
- subTitle: state == StackRestoringStatus.failed
- ? Text(
- "Something went wrong",
- style: STextStyles.errorSmall(context),
- )
- : null,
- );
- },
- ),
- const SizedBox(
- height: 12,
- ),
- Consumer(
- builder: (_, ref, __) {
- final state = ref.watch(stackRestoringUIStateProvider
- .select((value) => value.nodes));
- return RestoringItemCard(
- left: SizedBox(
- width: 32,
- height: 32,
- child: RoundedContainer(
- padding: const EdgeInsets.all(0),
- color: Theme.of(context)
- .extension()!
- .buttonBackSecondary,
- child: Center(
- child: SvgPicture.asset(
- Assets.svg.node,
- width: 16,
- height: 16,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ),
- ),
- ),
- right: SizedBox(
- width: 20,
- height: 20,
- child: _getIconForState(state),
- ),
- title: "Nodes",
- subTitle: state == StackRestoringStatus.failed
- ? Text(
- "Something went wrong",
- style: STextStyles.errorSmall(context),
- )
- : null,
- );
- },
- ),
- const SizedBox(
- height: 12,
- ),
- Consumer(
- builder: (_, ref, __) {
- final state = ref.watch(stackRestoringUIStateProvider
- .select((value) => value.trades));
- return RestoringItemCard(
- left: SizedBox(
- width: 32,
- height: 32,
- child: RoundedContainer(
- padding: const EdgeInsets.all(0),
- color: Theme.of(context)
- .extension()!
- .buttonBackSecondary,
- child: Center(
- child: SvgPicture.asset(
- Assets.svg.arrowRotate2,
- width: 16,
- height: 16,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ),
- ),
- ),
- right: SizedBox(
- width: 20,
- height: 20,
- child: _getIconForState(state),
- ),
- title: "Exchange history",
- subTitle: state == StackRestoringStatus.failed
- ? Text(
- "Something went wrong",
- style: STextStyles.errorSmall(context),
- )
- : null,
- );
- },
- ),
- const SizedBox(
- height: 16,
- ),
- Text(
- "Wallets",
- style: STextStyles.itemSubtitle(context),
- ),
- const SizedBox(
- height: 8,
- ),
- ...ref
- .watch(stackRestoringUIStateProvider
- .select((value) => value.walletStateProviders))
- .values
- .map(
- (provider) => Padding(
- padding: const EdgeInsets.symmetric(vertical: 4),
- child: RestoringWalletCard(
- provider: provider,
+ .accentColorDark,
),
),
),
- const SizedBox(
- height: 80,
+ ),
+ right: SizedBox(
+ width: 20,
+ height: 20,
+ child: _getIconForState(state),
+ ),
+ title: "Preferences",
+ subTitle: state == StackRestoringStatus.failed
+ ? Text(
+ "Something went wrong",
+ style: STextStyles.errorSmall(context),
+ )
+ : null,
+ );
+ },
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Consumer(
+ builder: (_, ref, __) {
+ final state = ref.watch(stackRestoringUIStateProvider
+ .select((value) => value.addressBook));
+ return RestoringItemCard(
+ left: SizedBox(
+ width: 32,
+ height: 32,
+ child: RoundedContainer(
+ padding: const EdgeInsets.all(0),
+ color: Theme.of(context)
+ .extension()!
+ .buttonBackSecondary,
+ child: Center(
+ child: AddressBookIcon(
+ width: 16,
+ height: 16,
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ ),
+ ),
+ ),
+ right: SizedBox(
+ width: 20,
+ height: 20,
+ child: _getIconForState(state),
+ ),
+ title: "Address book",
+ subTitle: state == StackRestoringStatus.failed
+ ? Text(
+ "Something went wrong",
+ style: STextStyles.errorSmall(context),
+ )
+ : null,
+ );
+ },
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Consumer(
+ builder: (_, ref, __) {
+ final state = ref.watch(stackRestoringUIStateProvider
+ .select((value) => value.nodes));
+ return RestoringItemCard(
+ left: SizedBox(
+ width: 32,
+ height: 32,
+ child: RoundedContainer(
+ padding: const EdgeInsets.all(0),
+ color: Theme.of(context)
+ .extension()!
+ .buttonBackSecondary,
+ child: Center(
+ child: SvgPicture.asset(
+ Assets.svg.node,
+ width: 16,
+ height: 16,
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ ),
+ ),
+ ),
+ right: SizedBox(
+ width: 20,
+ height: 20,
+ child: _getIconForState(state),
+ ),
+ title: "Nodes",
+ subTitle: state == StackRestoringStatus.failed
+ ? Text(
+ "Something went wrong",
+ style: STextStyles.errorSmall(context),
+ )
+ : null,
+ );
+ },
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Consumer(
+ builder: (_, ref, __) {
+ final state = ref.watch(stackRestoringUIStateProvider
+ .select((value) => value.trades));
+ return RestoringItemCard(
+ left: SizedBox(
+ width: 32,
+ height: 32,
+ child: RoundedContainer(
+ padding: const EdgeInsets.all(0),
+ color: Theme.of(context)
+ .extension()!
+ .buttonBackSecondary,
+ child: Center(
+ child: SvgPicture.asset(
+ Assets.svg.arrowRotate2,
+ width: 16,
+ height: 16,
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ ),
+ ),
+ ),
+ right: SizedBox(
+ width: 20,
+ height: 20,
+ child: _getIconForState(state),
+ ),
+ title: "Exchange history",
+ subTitle: state == StackRestoringStatus.failed
+ ? Text(
+ "Something went wrong",
+ style: STextStyles.errorSmall(context),
+ )
+ : null,
+ );
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ Text(
+ "Wallets",
+ style: STextStyles.itemSubtitle(context),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ ...ref
+ .watch(stackRestoringUIStateProvider
+ .select((value) => value.walletStateProviders))
+ .values
+ .map(
+ (provider) => Padding(
+ padding: const EdgeInsets.symmetric(vertical: 4),
+ child: RestoringWalletCard(
+ provider: provider,
+ ),
+ ),
),
- ],
+ const SizedBox(
+ height: 30,
),
- ),
- ),
- ),
- floatingActionButton: SizedBox(
- width: MediaQuery.of(context).size.width - 32,
- child: TextButton(
- onPressed: () async {
- if (_success) {
- _addWalletsToHomeView();
- Navigator.of(context)
- .popUntil(ModalRoute.withName(HomeView.routeName));
- } else {
- if (await _requestCancel()) {
- await _cancel();
- }
- }
- },
- style: Theme.of(context)
- .extension()!
- .getPrimaryEnabledButtonColor(context),
- child: Text(
- _success ? "OK" : "Cancel restore process",
- style: STextStyles.button(context).copyWith(
- color: Theme.of(context)
- .extension()!
- .buttonTextPrimary,
+ SizedBox(
+ width: MediaQuery.of(context).size.width - 32,
+ child: TextButton(
+ onPressed: () async {
+ if (_success) {
+ Navigator.of(context).pop();
+ } else {
+ if (await _requestCancel()) {
+ await _cancel();
+ }
+ }
+ },
+ style: Theme.of(context)
+ .extension()!
+ .getPrimaryEnabledButtonColor(context),
+ child: Text(
+ _success ? "OK" : "Cancel restore process",
+ style: STextStyles.button(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .buttonTextPrimary,
+ ),
+ ),
+ ),
),
- ),
+ ],
),
),
),
diff --git a/lib/pages/settings_views/global_settings_view/support_view.dart b/lib/pages/settings_views/global_settings_view/support_view.dart
index fdfa6f404..20aeedf61 100644
--- a/lib/pages/settings_views/global_settings_view/support_view.dart
+++ b/lib/pages/settings_views/global_settings_view/support_view.dart
@@ -4,7 +4,10 @@ import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -18,269 +21,363 @@ class SupportView extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final isDesktop = Util.isDesktop;
+
debugPrint("BUILD: $runtimeType");
- return Scaffold(
- backgroundColor: Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- title: Text(
- "Support",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- RoundedWhiteContainer(
- child: Text(
- "If you need support or want to report a bug, reach out to us on any of our socials!",
- style: STextStyles.smallMed12(context),
- ),
+ return ConditionalParent(
+ condition: !isDesktop,
+ builder: (child) {
+ return Scaffold(
+ backgroundColor:
+ Theme.of(context).extension()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
),
- const SizedBox(
- height: 12,
+ title: Text(
+ "Support",
+ style: STextStyles.navBarTitle(context),
),
- RoundedWhiteContainer(
- padding: const EdgeInsets.all(0),
- child: RawMaterialButton(
- // splashColor: Theme.of(context).extension()!.highlight,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(16),
+ child: child,
+ ),
+ );
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ RoundedWhiteContainer(
+ child: Text(
+ "If you need support or want to report a bug, reach out to us on any of our socials!",
+ style: STextStyles.smallMed12(context),
+ ),
+ ),
+ isDesktop
+ ? const SizedBox(
+ height: 24,
+ )
+ : const SizedBox(
+ height: 12,
),
- onPressed: () {
+ RoundedWhiteContainer(
+ padding: const EdgeInsets.all(0),
+ child: RawMaterialButton(
+ // splashColor: Theme.of(context).extension()!.highlight,
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ onPressed: () {
+ if (!isDesktop) {
launchUrl(
Uri.parse("https://t.me/stackwallet"),
mode: LaunchMode.externalApplication,
);
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 12,
- vertical: 20,
- ),
- child: Row(
- children: [
- SvgPicture.asset(
- Assets.socials.telegram,
- width: iconSize,
- height: iconSize,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- const SizedBox(
- width: 12,
- ),
- Text(
- "Telegram",
- style: STextStyles.titleBold12(context),
- textAlign: TextAlign.left,
- ),
- ],
- ),
+ }
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 20,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ SvgPicture.asset(
+ Assets.socials.telegram,
+ width: iconSize,
+ height: iconSize,
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ Text(
+ "Telegram",
+ style: STextStyles.titleBold12(context),
+ textAlign: TextAlign.left,
+ ),
+ ],
+ ),
+ BlueTextButton(
+ text: isDesktop ? "@stackwallet" : "",
+ onTap: () {
+ launchUrl(
+ Uri.parse("https://t.me/stackwallet"),
+ mode: LaunchMode.externalApplication,
+ );
+ },
+ ),
+ ],
),
),
),
- const SizedBox(
- height: 8,
- ),
- RoundedWhiteContainer(
- padding: const EdgeInsets.all(0),
- child: RawMaterialButton(
- // splashColor: Theme.of(context).extension()!.highlight,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ RoundedWhiteContainer(
+ padding: const EdgeInsets.all(0),
+ child: RawMaterialButton(
+ // splashColor: Theme.of(context).extension()!.highlight,
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
),
- onPressed: () {
+ ),
+ onPressed: () {
+ if (!isDesktop) {
launchUrl(
Uri.parse("https://discord.gg/RZMG3yUm"),
mode: LaunchMode.externalApplication,
);
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 12,
- vertical: 20,
- ),
- child: Row(
- children: [
- SvgPicture.asset(
- Assets.socials.discord,
- width: iconSize,
- height: iconSize,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- const SizedBox(
- width: 12,
- ),
- Text(
- "Discord",
- style: STextStyles.titleBold12(context),
- textAlign: TextAlign.left,
- ),
- ],
- ),
+ }
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 20,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ SvgPicture.asset(
+ Assets.socials.discord,
+ width: iconSize,
+ height: iconSize,
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ Text(
+ "Discord",
+ style: STextStyles.titleBold12(context),
+ textAlign: TextAlign.left,
+ ),
+ ],
+ ),
+ BlueTextButton(
+ text: isDesktop ? "Stack Wallet" : "",
+ onTap: () {
+ launchUrl(
+ Uri.parse(
+ "https://discord.gg/RZMG3yUm"), //expired link?
+ mode: LaunchMode.externalApplication,
+ );
+ },
+ ),
+ ],
),
),
),
- const SizedBox(
- height: 8,
- ),
- RoundedWhiteContainer(
- padding: const EdgeInsets.all(0),
- child: RawMaterialButton(
- // splashColor: Theme.of(context).extension()!.highlight,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ RoundedWhiteContainer(
+ padding: const EdgeInsets.all(0),
+ child: RawMaterialButton(
+ // splashColor: Theme.of(context).extension()!.highlight,
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
),
- onPressed: () {
+ ),
+ onPressed: () {
+ if (!isDesktop) {
launchUrl(
Uri.parse("https://www.reddit.com/r/stackwallet/"),
mode: LaunchMode.externalApplication,
);
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 12,
- vertical: 20,
- ),
- child: Row(
- children: [
- SvgPicture.asset(
- Assets.socials.reddit,
- width: iconSize,
- height: iconSize,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- const SizedBox(
- width: 12,
- ),
- Text(
- "Reddit",
- style: STextStyles.titleBold12(context),
- textAlign: TextAlign.left,
- ),
- ],
- ),
+ }
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 20,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ SvgPicture.asset(
+ Assets.socials.reddit,
+ width: iconSize,
+ height: iconSize,
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ Text(
+ "Reddit",
+ style: STextStyles.titleBold12(context),
+ textAlign: TextAlign.left,
+ ),
+ ],
+ ),
+ BlueTextButton(
+ text: isDesktop ? "r/stackwallet" : "",
+ onTap: () {
+ launchUrl(
+ Uri.parse("https://www.reddit.com/r/stackwallet/"),
+ mode: LaunchMode.externalApplication,
+ );
+ },
+ ),
+ ],
),
),
),
- const SizedBox(
- height: 8,
- ),
- RoundedWhiteContainer(
- padding: const EdgeInsets.all(0),
- child: RawMaterialButton(
- // splashColor: Theme.of(context).extension()!.highlight,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ RoundedWhiteContainer(
+ padding: const EdgeInsets.all(0),
+ child: RawMaterialButton(
+ // splashColor: Theme.of(context).extension()!.highlight,
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
),
- onPressed: () {
+ ),
+ onPressed: () {
+ if (!isDesktop) {
launchUrl(
Uri.parse("https://twitter.com/stack_wallet"),
mode: LaunchMode.externalApplication,
);
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 12,
- vertical: 20,
- ),
- child: Row(
- children: [
- SvgPicture.asset(
- Assets.socials.twitter,
- width: iconSize,
- height: iconSize,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- const SizedBox(
- width: 12,
- ),
- Text(
- "Twitter",
- style: STextStyles.titleBold12(context),
- textAlign: TextAlign.left,
- ),
- ],
- ),
+ }
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 20,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ SvgPicture.asset(
+ Assets.socials.twitter,
+ width: iconSize,
+ height: iconSize,
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ Text(
+ "Twitter",
+ style: STextStyles.titleBold12(context),
+ textAlign: TextAlign.left,
+ ),
+ ],
+ ),
+ BlueTextButton(
+ text: isDesktop ? "@stack_wallet" : "",
+ onTap: () {
+ launchUrl(
+ Uri.parse("https://twitter.com/stack_wallet"),
+ mode: LaunchMode.externalApplication,
+ );
+ },
+ ),
+ ],
),
),
),
- const SizedBox(
- height: 8,
- ),
- RoundedWhiteContainer(
- padding: const EdgeInsets.all(0),
- child: RawMaterialButton(
- // splashColor: Theme.of(context).extension()!.highlight,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ RoundedWhiteContainer(
+ padding: const EdgeInsets.all(0),
+ child: RawMaterialButton(
+ // splashColor: Theme.of(context).extension()!.highlight,
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
),
- onPressed: () {
+ ),
+ onPressed: () {
+ if (!isDesktop) {
launchUrl(
Uri.parse("mailto://support@stackwallet.com"),
mode: LaunchMode.externalApplication,
);
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 12,
- vertical: 20,
- ),
- child: Row(
- children: [
- SvgPicture.asset(
- Assets.svg.envelope,
- width: iconSize,
- height: iconSize,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- const SizedBox(
- width: 12,
- ),
- Text(
- "Email",
- style: STextStyles.titleBold12(context),
- textAlign: TextAlign.left,
- ),
- ],
- ),
+ }
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 20,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ SvgPicture.asset(
+ Assets.svg.envelope,
+ width: iconSize,
+ height: iconSize,
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ Text(
+ "Email",
+ style: STextStyles.titleBold12(context),
+ textAlign: TextAlign.left,
+ ),
+ ],
+ ),
+ BlueTextButton(
+ text: isDesktop ? "support@stackwallet.com" : "",
+ onTap: () {
+ launchUrl(
+ Uri.parse("mailto://support@stackwallet.com"),
+ mode: LaunchMode.externalApplication,
+ );
+ },
+ ),
+ ],
),
),
),
- ],
- ),
+ ),
+ ],
),
);
}
diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart
index 6f23f2e01..1c2fb8e5d 100644
--- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart
+++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart
@@ -471,75 +471,80 @@ class _TransactionDetailsViewState
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Column(
- crossAxisAlignment:
- CrossAxisAlignment.start,
- children: [
- Text(
+ Expanded(
+ child: Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.start,
+ children: [
+ Text(
+ _transaction.txType.toLowerCase() ==
+ "sent"
+ ? "Sent to"
+ : "Receiving address",
+ style: isDesktop
+ ? STextStyles
+ .desktopTextExtraExtraSmall(
+ context)
+ : STextStyles.itemSubtitle(
+ context),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
_transaction.txType.toLowerCase() ==
- "sent"
- ? "Sent to"
- : "Receiving address",
- style: isDesktop
- ? STextStyles
- .desktopTextExtraExtraSmall(
- context)
- : STextStyles.itemSubtitle(context),
- ),
- const SizedBox(
- height: 8,
- ),
- _transaction.txType.toLowerCase() ==
- "received"
- ? FutureBuilder(
- future: fetchContactNameFor(
- _transaction.address),
- builder: (builderContext,
- AsyncSnapshot
- snapshot) {
- String addressOrContactName =
- _transaction.address;
- if (snapshot.connectionState ==
- ConnectionState.done &&
- snapshot.hasData) {
- addressOrContactName =
- snapshot.data!;
- }
- return SelectableText(
- addressOrContactName,
- style: isDesktop
- ? STextStyles
- .desktopTextExtraExtraSmall(
- context)
- .copyWith(
- color: Theme.of(
- context)
- .extension<
- StackColors>()!
- .textDark,
- )
- : STextStyles
- .itemSubtitle12(
- context),
- );
- },
- )
- : SelectableText(
- _transaction.address,
- style: isDesktop
- ? STextStyles
- .desktopTextExtraExtraSmall(
- context)
- .copyWith(
- color: Theme.of(context)
- .extension<
- StackColors>()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context),
- ),
- ],
+ "received"
+ ? FutureBuilder(
+ future: fetchContactNameFor(
+ _transaction.address),
+ builder: (builderContext,
+ AsyncSnapshot
+ snapshot) {
+ String addressOrContactName =
+ _transaction.address;
+ if (snapshot.connectionState ==
+ ConnectionState
+ .done &&
+ snapshot.hasData) {
+ addressOrContactName =
+ snapshot.data!;
+ }
+ return SelectableText(
+ addressOrContactName,
+ style: isDesktop
+ ? STextStyles
+ .desktopTextExtraExtraSmall(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .textDark,
+ )
+ : STextStyles
+ .itemSubtitle12(
+ context),
+ );
+ },
+ )
+ : SelectableText(
+ _transaction.address,
+ style: isDesktop
+ ? STextStyles
+ .desktopTextExtraExtraSmall(
+ context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension<
+ StackColors>()!
+ .textDark,
+ )
+ : STextStyles
+ .itemSubtitle12(
+ context),
+ ),
+ ],
+ ),
),
if (isDesktop)
IconCopyButton(
diff --git a/lib/pages_desktop_specific/create_password/create_password_view.dart b/lib/pages_desktop_specific/create_password/create_password_view.dart
index 2391a22f6..0a8429058 100644
--- a/lib/pages_desktop_specific/create_password/create_password_view.dart
+++ b/lib/pages_desktop_specific/create_password/create_password_view.dart
@@ -1,10 +1,12 @@
import 'dart:async';
import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
+import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
@@ -18,7 +20,7 @@ import 'package:stackwallet/widgets/progress_bar.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:zxcvbn/zxcvbn.dart';
-class CreatePasswordView extends StatefulWidget {
+class CreatePasswordView extends ConsumerStatefulWidget {
const CreatePasswordView({
Key? key,
this.secureStore = const SecureStorageWrapper(
@@ -31,10 +33,10 @@ class CreatePasswordView extends StatefulWidget {
final FlutterSecureStorageInterface secureStore;
@override
- State createState() => _CreatePasswordViewState();
+ ConsumerState createState() => _CreatePasswordViewState();
}
-class _CreatePasswordViewState extends State {
+class _CreatePasswordViewState extends ConsumerState {
late final TextEditingController passwordController;
late final TextEditingController passwordRepeatController;
@@ -76,8 +78,16 @@ class _CreatePasswordViewState extends State {
return;
}
- await widget.secureStore
- .write(key: "stackDesktopPassword", value: passphrase);
+ try {
+ await ref.read(storageCryptoHandlerProvider).initFromNew(passphrase);
+ } catch (e) {
+ unawaited(showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "Error: $e",
+ context: context,
+ ));
+ return;
+ }
if (mounted) {
unawaited(Navigator.of(context)
diff --git a/lib/pages_desktop_specific/desktop_login_view.dart b/lib/pages_desktop_specific/desktop_login_view.dart
new file mode 100644
index 000000000..fe05d719f
--- /dev/null
+++ b/lib/pages_desktop_specific/desktop_login_view.dart
@@ -0,0 +1,184 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:stackwallet/pages_desktop_specific/forgot_password_desktop_view.dart';
+import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/constants.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
+import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/stack_text_field.dart';
+
+class DesktopLoginView extends StatefulWidget {
+ const DesktopLoginView({
+ Key? key,
+ this.startupWalletId,
+ }) : super(key: key);
+
+ static const String routeName = "/desktopLogin";
+
+ final String? startupWalletId;
+
+ @override
+ State createState() => _DesktopLoginViewState();
+}
+
+class _DesktopLoginViewState extends State {
+ late final TextEditingController passwordController;
+
+ late final FocusNode passwordFocusNode;
+
+ bool hidePassword = true;
+ bool _continueEnabled = false;
+
+ @override
+ void initState() {
+ passwordController = TextEditingController();
+ passwordFocusNode = FocusNode();
+
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ passwordController.dispose();
+ passwordFocusNode.dispose();
+
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return DesktopScaffold(
+ body: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ SizedBox(
+ width: 480,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SvgPicture.asset(
+ Assets.svg.stackIcon(context),
+ width: 100,
+ ),
+ const SizedBox(
+ height: 42,
+ ),
+ Text(
+ "Stack Wallet",
+ style: STextStyles.desktopH1(context),
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ SizedBox(
+ width: 350,
+ child: Text(
+ "Open source multicoin wallet for everyone",
+ textAlign: TextAlign.center,
+ style: STextStyles.desktopSubtitleH1(context),
+ ),
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ key: const Key("desktopLoginPasswordFieldKey"),
+ focusNode: passwordFocusNode,
+ controller: passwordController,
+ style: STextStyles.desktopTextMedium(context).copyWith(
+ height: 2,
+ ),
+ obscureText: hidePassword,
+ enableSuggestions: false,
+ autocorrect: false,
+ decoration: standardInputDecoration(
+ "Enter password",
+ passwordFocusNode,
+ context,
+ ).copyWith(
+ suffixIcon: UnconstrainedBox(
+ child: SizedBox(
+ height: 70,
+ child: Row(
+ children: [
+ const SizedBox(
+ width: 24,
+ ),
+ GestureDetector(
+ key: const Key(
+ "restoreFromFilePasswordFieldShowPasswordButtonKey"),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(context)
+ .extension()!
+ .textDark3,
+ width: 24,
+ height: 24,
+ ),
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ onChanged: (newValue) {
+ setState(() {
+ _continueEnabled = passwordController.text.isNotEmpty;
+ });
+ },
+ ),
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ PrimaryButton(
+ label: "Continue",
+ enabled: _continueEnabled,
+ onPressed: () {
+ // todo auth
+
+ Navigator.of(context).pushNamedAndRemoveUntil(
+ DesktopHomeView.routeName,
+ (route) => false,
+ );
+ },
+ ),
+ const SizedBox(
+ height: 60,
+ ),
+ BlueTextButton(
+ text: "Forgot password?",
+ textSize: 20,
+ onTap: () {
+ Navigator.of(context).pushNamed(
+ ForgotPasswordDesktopView.routeName,
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages_desktop_specific/forgot_password_desktop_view.dart b/lib/pages_desktop_specific/forgot_password_desktop_view.dart
new file mode 100644
index 000000000..d501cbd38
--- /dev/null
+++ b/lib/pages_desktop_specific/forgot_password_desktop_view.dart
@@ -0,0 +1,101 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
+import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
+import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/desktop/secondary_button.dart';
+
+class ForgotPasswordDesktopView extends StatefulWidget {
+ const ForgotPasswordDesktopView({
+ Key? key,
+ }) : super(key: key);
+
+ static const String routeName = "/forgotPasswordDesktop";
+
+ @override
+ State createState() =>
+ _ForgotPasswordDesktopViewState();
+}
+
+class _ForgotPasswordDesktopViewState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return DesktopScaffold(
+ appBar: DesktopAppBar(
+ leading: AppBarBackButton(
+ onPressed: () async {
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ },
+ ),
+ isCompactHeight: false,
+ ),
+ body: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ SizedBox(
+ width: 480,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SvgPicture.asset(
+ Assets.svg.stackIcon(context),
+ width: 100,
+ ),
+ const SizedBox(
+ height: 42,
+ ),
+ Text(
+ "Stack Wallet",
+ style: STextStyles.desktopH1(context),
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ SizedBox(
+ width: 400,
+ child: Text(
+ "Stack Wallet does not store your password. Create new wallet or use a Stack backup file to restore your wallet.",
+ textAlign: TextAlign.center,
+ style: STextStyles.desktopTextSmall(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 48,
+ ),
+ PrimaryButton(
+ label: "Create new wallet",
+ onPressed: () {
+ // // todo delete everything and start fresh?
+ },
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ SecondaryButton(
+ label: "Restore from backup",
+ onPressed: () {
+ // todo SWB restore
+ },
+ ),
+ const SizedBox(
+ height: kDesktopAppBarHeight,
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart
new file mode 100644
index 000000000..3622fcf1e
--- /dev/null
+++ b/lib/pages_desktop_specific/home/address_book_view/desktop_address_book.dart
@@ -0,0 +1,139 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:stackwallet/providers/global/wallets_provider.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/constants.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
+import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
+import 'package:stackwallet/widgets/stack_text_field.dart';
+import 'package:stackwallet/widgets/textfield_icon_button.dart';
+
+class DesktopAddressBook extends ConsumerStatefulWidget {
+ const DesktopAddressBook({Key? key}) : super(key: key);
+
+ static const String routeName = "/desktopAddressBook";
+
+ @override
+ ConsumerState createState() => _DesktopAddressBook();
+}
+
+class _DesktopAddressBook extends ConsumerState {
+ late final TextEditingController _searchController;
+
+ late final FocusNode _searchFocusNode;
+
+ String filter = "";
+
+ @override
+ void initState() {
+ _searchController = TextEditingController();
+ _searchFocusNode = FocusNode();
+
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ _searchController.dispose();
+ _searchFocusNode.dispose();
+
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ debugPrint("BUILD: $runtimeType");
+ final hasWallets = ref.watch(walletsChangeNotifierProvider).hasWallets;
+
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ DesktopAppBar(
+ isCompactHeight: true,
+ leading: Row(
+ children: [
+ const SizedBox(
+ width: 24,
+ ),
+ Text(
+ "Address Book",
+ style: STextStyles.desktopH3(context),
+ )
+ ],
+ ),
+ ),
+ const SizedBox(height: 53),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 24),
+ child: Row(
+ children: [
+ SizedBox(
+ height: 60,
+ width: 489,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ autocorrect: false,
+ enableSuggestions: false,
+ controller: _searchController,
+ focusNode: _searchFocusNode,
+ onChanged: (newString) {
+ setState(() => filter = newString);
+ },
+ style: STextStyles.field(context),
+ decoration: standardInputDecoration(
+ "Search...",
+ _searchFocusNode,
+ context,
+ ).copyWith(
+ labelStyle: STextStyles.fieldLabel(context)
+ .copyWith(fontSize: 16),
+ prefixIcon: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 10,
+ vertical: 16,
+ ),
+ child: SvgPicture.asset(
+ Assets.svg.search,
+ width: 16,
+ height: 16,
+ ),
+ ),
+ suffixIcon: _searchController.text.isNotEmpty
+ ? Padding(
+ padding: const EdgeInsets.only(right: 0),
+ child: UnconstrainedBox(
+ child: Row(
+ children: [
+ TextFieldIconButton(
+ child: const XIcon(),
+ onTap: () async {
+ setState(() {
+ _searchController.text = "";
+ filter = "";
+ });
+ },
+ ),
+ ],
+ ),
+ ),
+ )
+ : null,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ // Expanded(
+ // child: hasWallets ? const MyWallets() : const EmptyWallets(),
+ // ),
+ ],
+ );
+ }
+}
diff --git a/lib/pages_desktop_specific/home/desktop_home_view.dart b/lib/pages_desktop_specific/home/desktop_home_view.dart
index 6aa104081..cb8aba255 100644
--- a/lib/pages_desktop_specific/home/desktop_home_view.dart
+++ b/lib/pages_desktop_specific/home/desktop_home_view.dart
@@ -1,8 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/pages_desktop_specific/home/address_book_view/desktop_address_book.dart';
import 'package:stackwallet/pages_desktop_specific/home/desktop_menu.dart';
import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart';
+import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart';
+import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
@@ -29,19 +32,25 @@ class _DesktopHomeViewState extends ConsumerState {
Container(
color: Colors.red,
),
- Container(
- color: Colors.orange,
+ const Navigator(
+ key: Key("desktopAddressBookHomeKey"),
+ onGenerateRoute: RouteGenerator.generateRoute,
+ initialRoute: DesktopAddressBook.routeName,
),
const Navigator(
key: Key("desktopSettingHomeKey"),
onGenerateRoute: RouteGenerator.generateRoute,
initialRoute: DesktopSettingsView.routeName,
),
- Container(
- color: Colors.blue,
+ const Navigator(
+ key: Key("desktopSupportHomeKey"),
+ onGenerateRoute: RouteGenerator.generateRoute,
+ initialRoute: DesktopSupportView.routeName,
),
- Container(
- color: Colors.pink,
+ const Navigator(
+ key: Key("desktopAboutHomeKey"),
+ onGenerateRoute: RouteGenerator.generateRoute,
+ initialRoute: DesktopAboutView.routeName,
),
];
diff --git a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart
index 0406a059f..cf687e3e7 100644
--- a/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart
+++ b/lib/pages_desktop_specific/home/settings_menu/advanced_settings/debug_info_dialog.dart
@@ -1,10 +1,14 @@
+import 'dart:async';
+
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/isar/models/log.dart';
+import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/debug_service_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
+import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/enums/log_level_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
@@ -105,7 +109,7 @@ class _DebugInfoDialog extends ConsumerState {
],
),
Expanded(
- flex: 24,
+ // flex: 24,
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) {
@@ -314,7 +318,7 @@ class _DebugInfoDialog extends ConsumerState {
),
),
),
- const Spacer(),
+ // const Spacer(),
Padding(
padding: const EdgeInsets.all(32),
child: Row(
@@ -322,7 +326,18 @@ class _DebugInfoDialog extends ConsumerState {
Expanded(
child: SecondaryButton(
label: "Clear logs",
- onPressed: () {},
+ onPressed: () async {
+ await ref.read(debugServiceProvider).deleteAllMessages();
+ await ref.read(debugServiceProvider).updateRecentLogs();
+
+ if (mounted) {
+ Navigator.pop(context);
+ unawaited(showFloatingFlushBar(
+ type: FlushBarType.info,
+ context: context,
+ message: 'Logs cleared!'));
+ }
+ },
),
),
const SizedBox(
diff --git a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart
index d524453e2..bf6fc81e2 100644
--- a/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart
+++ b/lib/pages_desktop_specific/home/settings_menu/appearance_settings.dart
@@ -1,16 +1,19 @@
import 'package:flutter/material.dart';
-import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
+import 'package:stackwallet/hive/db.dart';
+import 'package:stackwallet/providers/global/prefs_provider.dart';
+import 'package:stackwallet/providers/ui/color_theme_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/theme/color_theme.dart';
+import 'package:stackwallet/utilities/theme/dark_colors.dart';
+import 'package:stackwallet/utilities/theme/light_colors.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
-import '../../../providers/global/prefs_provider.dart';
-import '../../../utilities/constants.dart';
-import '../../../widgets/custom_buttons/draggable_switch_button.dart';
-
class AppearanceOptionSettings extends ConsumerStatefulWidget {
const AppearanceOptionSettings({Key? key}) : super(key: key);
@@ -23,6 +26,19 @@ class AppearanceOptionSettings extends ConsumerStatefulWidget {
class _AppearanceOptionSettings
extends ConsumerState {
+ // late bool isLight;
+
+ // @override
+ // void initState() {
+ //
+ // super.initState();
+ // }
+ //
+ // @override
+ // void dispose() {
+ // super.dispose();
+ // }
+
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
@@ -128,10 +144,7 @@ class _AppearanceOptionSettings
),
),
const Padding(
- padding: EdgeInsets.only(
- left: 10,
- right: 10,
- ),
+ padding: EdgeInsets.all(10),
child: ThemeToggle(),
),
],
@@ -143,7 +156,7 @@ class _AppearanceOptionSettings
}
}
-class ThemeToggle extends StatefulWidget {
+class ThemeToggle extends ConsumerStatefulWidget {
const ThemeToggle({
Key? key,
}) : super(key: key);
@@ -152,160 +165,226 @@ class ThemeToggle extends StatefulWidget {
// final void Function(bool)? onChanged;
@override
- State createState() => _ThemeToggle();
+ ConsumerState createState() => _ThemeToggle();
}
-class _ThemeToggle extends State {
+class _ThemeToggle extends ConsumerState {
// late bool externalCallsEnabled;
+ late String _selectedTheme;
+
+ @override
+ void initState() {
+ _selectedTheme =
+ DB.instance.get(boxName: DB.boxNameTheme, key: "colorScheme")
+ as String? ??
+ "light";
+
+ super.initState();
+ }
+
@override
Widget build(BuildContext context) {
return Row(
- // mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: RawMaterialButton(
- elevation: 0,
- hoverColor: Colors.transparent,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius * 2,
- ),
+ MaterialButton(
+ splashColor: Colors.transparent,
+ hoverColor: Colors.transparent,
+ padding: const EdgeInsets.all(0),
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
),
- onPressed: () {}, //onPressed
- child: Stack(
+ ),
+ onPressed: () {
+ DB.instance.put(
+ boxName: DB.boxNameTheme,
+ key: "colorScheme",
+ value: ThemeType.light.name,
+ );
+ ref.read(colorThemeProvider.state).state =
+ StackColors.fromStackColorTheme(
+ LightColors(),
+ );
+
+ setState(() {
+ _selectedTheme = "light";
+ });
+ },
+ child: SizedBox(
+ width: 200,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
children: [
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.only(
- left: 24,
- ),
- child: SvgPicture.asset(
- Assets.svg.themeLight,
- ),
- ),
- Padding(
- padding: const EdgeInsets.only(
- left: 50,
- top: 12,
- ),
- child: Text(
- "Light",
- style:
- STextStyles.desktopTextExtraSmall(context).copyWith(
- color: Theme.of(context)
+ Container(
+ decoration: BoxDecoration(
+ border: Border.all(
+ width: 2.5,
+ color: _selectedTheme == "light"
+ ? Theme.of(context)
.extension()!
- .textDark,
- ),
- ),
- )
- ],
- ),
- // if (externalCallsEnabled)
- Positioned(
- bottom: 0,
- left: 6,
+ .infoItemIcons
+ : Theme.of(context).extension()!.popupBG,
+ ),
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
child: SvgPicture.asset(
- Assets.svg.checkCircle,
- width: 20,
- height: 20,
- color: Theme.of(context)
- .extension()!
- .infoItemIcons,
+ Assets.svg.themeLight,
),
),
- // if (!externalCallsEnabled)
- // Positioned(
- // top: 4,
- // right: 4,
- // child: Container(
- // width: 20,
- // height: 20,
- // decoration: BoxDecoration(
- // borderRadius: BorderRadius.circular(1000),
- // color: Theme.of(context)
- // .extension()!
- // .textFieldDefaultBG,
- // ),
- // ),
- // ),
+ const SizedBox(
+ height: 12,
+ ),
+ Row(
+ children: [
+ SizedBox(
+ width: 20,
+ height: 20,
+ child: Radio(
+ activeColor: Theme.of(context)
+ .extension()!
+ .radioButtonIconEnabled,
+ value: "light",
+ groupValue: _selectedTheme,
+ onChanged: (newValue) {
+ if (newValue is String && newValue == "light") {
+ DB.instance.put(
+ boxName: DB.boxNameTheme,
+ key: "colorScheme",
+ value: ThemeType.light.name,
+ );
+ ref.read(colorThemeProvider.state).state =
+ StackColors.fromStackColorTheme(
+ LightColors(),
+ );
+
+ setState(() {
+ _selectedTheme = "light";
+ });
+ }
+ },
+ ),
+ ),
+ const SizedBox(
+ width: 14,
+ ),
+ Text(
+ "Light",
+ style:
+ STextStyles.desktopTextExtraSmall(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark,
+ ),
+ ),
+ ],
+ ),
],
),
),
),
const SizedBox(
- width: 1,
+ width: 20,
),
- Expanded(
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: RawMaterialButton(
- elevation: 0,
- hoverColor: Colors.transparent,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius * 2,
- ),
- ),
- onPressed: () {}, //onPressed
- child: Stack(
- children: [
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SvgPicture.asset(
- Assets.svg.themeDark,
- ),
- Padding(
- padding: const EdgeInsets.only(
- left: 50,
- top: 12,
- ),
- child: Text(
- "Dark",
- style: STextStyles.desktopTextExtraSmall(context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textDark,
- ),
- ),
- ),
- ],
- ),
- // if (externalCallsEnabled)
- Positioned(
- bottom: 0,
- left: 0,
- child: SvgPicture.asset(
- Assets.svg.checkCircle,
- width: 20,
- height: 20,
- color: Theme.of(context)
- .extension()!
- .infoItemIcons,
+ MaterialButton(
+ splashColor: Colors.transparent,
+ hoverColor: Colors.transparent,
+ padding: const EdgeInsets.all(0),
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ onPressed: () {
+ DB.instance.put(
+ boxName: DB.boxNameTheme,
+ key: "colorScheme",
+ value: ThemeType.dark.name,
+ );
+ ref.read(colorThemeProvider.state).state =
+ StackColors.fromStackColorTheme(
+ DarkColors(),
+ );
+
+ setState(() {
+ _selectedTheme = "dark";
+ });
+ },
+ child: SizedBox(
+ width: 200,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Container(
+ decoration: BoxDecoration(
+ border: Border.all(
+ width: 2.5,
+ color: _selectedTheme == "dark"
+ ? Theme.of(context)
+ .extension()!
+ .infoItemIcons
+ : Theme.of(context).extension()!.popupBG,
+ ),
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
),
),
- // if (!externalCallsEnabled)
- // Positioned(
- // top: 4,
- // right: 4,
- // child: Container(
- // width: 20,
- // height: 20,
- // decoration: BoxDecoration(
- // borderRadius: BorderRadius.circular(1000),
- // color: Theme.of(context)
- // .extension()!
- // .textFieldDefaultBG,
- // ),
- // ),
- // ),
- ],
- ),
+ child: SvgPicture.asset(
+ Assets.svg.themeDark,
+ ),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Row(
+ children: [
+ SizedBox(
+ width: 20,
+ height: 20,
+ child: Radio(
+ activeColor: Theme.of(context)
+ .extension()!
+ .radioButtonIconEnabled,
+ value: "dark",
+ groupValue: _selectedTheme,
+ onChanged: (newValue) {
+ if (newValue is String && newValue == "dark") {
+ DB.instance.put(
+ boxName: DB.boxNameTheme,
+ key: "colorScheme",
+ value: ThemeType.dark.name,
+ );
+ ref.read(colorThemeProvider.state).state =
+ StackColors.fromStackColorTheme(
+ DarkColors(),
+ );
+
+ setState(() {
+ _selectedTheme = "dark";
+ });
+ }
+ },
+ ),
+ ),
+ const SizedBox(
+ width: 14,
+ ),
+ Text(
+ "Dark",
+ style:
+ STextStyles.desktopTextExtraSmall(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark,
+ ),
+ ),
+ ],
+ ),
+ ],
),
),
),
diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart
index 8b74f8d40..3ea6cea6c 100644
--- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart
+++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart
@@ -2,14 +2,28 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
+import 'package:intl/intl.dart';
+import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart';
+import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart';
+import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart';
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart';
-import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart';
+import 'package:stackwallet/providers/global/locale_provider.dart';
+import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
+import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
+import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:url_launcher/url_launcher.dart';
+import '../../../../providers/global/auto_swb_service_provider.dart';
+import '../../../../widgets/custom_buttons/blue_text_button.dart';
+
class BackupRestoreSettings extends ConsumerStatefulWidget {
const BackupRestoreSettings({Key? key}) : super(key: key);
@@ -21,290 +35,516 @@ class BackupRestoreSettings extends ConsumerStatefulWidget {
}
class _BackupRestoreSettings extends ConsumerState {
+ late bool createBackup = false;
+ late bool restoreBackup = false;
+
+ final toggleController = DSBController();
+
+ late final TextEditingController fileLocationController;
+ late final TextEditingController passwordController;
+ late final TextEditingController frequencyController;
+
+ late final FocusNode fileLocationFocusNode;
+ late final FocusNode passwordFocusNode;
+
+ String prettySinceLastBackupString(DateTime? time) {
+ if (time == null) {
+ return "-";
+ }
+ final difference = DateTime.now().difference(time);
+ int value;
+ String postfix;
+ if (difference < const Duration(seconds: 60)) {
+ value = difference.inSeconds;
+ postfix = "seconds";
+ } else if (difference < const Duration(minutes: 60)) {
+ value = difference.inMinutes;
+ postfix = "minutes";
+ } else if (difference < const Duration(hours: 24)) {
+ value = difference.inHours;
+ postfix = "hours";
+ } else if (difference.inDays < 8) {
+ value = difference.inDays;
+ postfix = "days";
+ } else {
+ // if greater than a week return the actual date
+ return DateFormat.yMMMMd(
+ ref.read(localeServiceChangeNotifierProvider).locale)
+ .format(time);
+ }
+
+ if (value == 1) {
+ postfix = postfix.substring(0, postfix.length - 1);
+ }
+
+ return "$value $postfix ago";
+ }
+
+ Future enableAutoBackup(BuildContext context) async {
+ await showDialog(
+ context: context,
+ useSafeArea: false,
+ barrierDismissible: true,
+ builder: (context) {
+ return const EnableBackupDialog();
+ },
+ );
+ }
+
+ Future createAutoBackup() async {
+ await showDialog(
+ context: context,
+ useSafeArea: false,
+ barrierDismissible: true,
+ builder: (context) {
+ return CreateAutoBackup();
+ },
+ );
+ }
+
+ Future attemptDisable() async {
+ final result = await showDialog(
+ context: context,
+ useSafeArea: false,
+ barrierDismissible: true,
+ builder: (context) {
+ return StackDialog(
+ title: "Disable Auto Backup",
+ message:
+ "You are turning off Auto Backup. You can turn it back on at any time. Your previous Auto Backup file will not be deleted. Remember to backup your wallets manually so you don't lose important information.",
+ leftButton: TextButton(
+ style: Theme.of(context)
+ .extension()!
+ .getSecondaryEnabledButtonColor(context),
+ child: Text(
+ "Back",
+ style: STextStyles.button(context).copyWith(
+ color:
+ Theme.of(context).extension()!.accentColorDark,
+ ),
+ ),
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ rightButton: TextButton(
+ style: Theme.of(context)
+ .extension()!
+ .getPrimaryEnabledButtonColor(context),
+ child: Text(
+ "Disable",
+ style: STextStyles.button(context),
+ ),
+ onPressed: () {
+ Navigator.of(context).pop();
+ setState(() {
+ ref.watch(prefsChangeNotifierProvider).isAutoBackupEnabled =
+ false;
+ });
+ },
+ ),
+ );
+ },
+ );
+ if (mounted) {
+ if (result is bool && result) {
+ ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled = false;
+ Navigator.of(context).pop();
+ } else {
+ toggleController.activate?.call();
+ }
+ }
+ }
+
+ @override
+ void initState() {
+ fileLocationController = TextEditingController();
+ passwordController = TextEditingController();
+ frequencyController = TextEditingController();
+
+ passwordController.text = "---------------";
+ fileLocationController.text =
+ ref.read(prefsChangeNotifierProvider).autoBackupLocation ?? " ";
+ frequencyController.text = Format.prettyFrequencyType(
+ ref.read(prefsChangeNotifierProvider).backupFrequencyType);
+
+ fileLocationFocusNode = FocusNode();
+ passwordFocusNode = FocusNode();
+
+ // _toggle = ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled;
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ fileLocationController.dispose();
+ passwordController.dispose();
+ frequencyController.dispose();
+
+ fileLocationFocusNode.dispose();
+ passwordFocusNode.dispose();
+
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
- return ListView(
- shrinkWrap: true,
- scrollDirection: Axis.vertical,
- children: [
- Padding(
- padding: const EdgeInsets.only(
- right: 30,
- ),
- child: RoundedWhiteContainer(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SvgPicture.asset(
- Assets.svg.backupAuto,
- width: 48,
- height: 48,
- ),
- Center(
- child: Padding(
- padding: const EdgeInsets.all(10),
- child: RichText(
- textAlign: TextAlign.start,
- text: TextSpan(
+
+ bool isEnabledAutoBackup = ref.watch(prefsChangeNotifierProvider
+ .select((value) => value.isAutoBackupEnabled));
+
+ ref.listen(
+ prefsChangeNotifierProvider
+ .select((value) => value.backupFrequencyType),
+ (previous, BackupFrequencyType next) {
+ frequencyController.text = Format.prettyFrequencyType(next);
+ });
+
+ return LayoutBuilder(builder: (context, constraints) {
+ return SingleChildScrollView(
+ scrollDirection: Axis.vertical,
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight: constraints.maxHeight,
+ ),
+ child: IntrinsicHeight(
+ child: Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(
+ right: 30,
+ ),
+ child: RoundedWhiteContainer(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
- TextSpan(
- text: "Auto Backup",
- style: STextStyles.desktopTextSmall(context),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ SvgPicture.asset(
+ Assets.svg.backupAuto,
+ width: 48,
+ height: 48,
+ ),
+ isEnabledAutoBackup
+ ? SvgPicture.asset(
+ Assets.svg.enableButton,
+ )
+ : SvgPicture.asset(
+ Assets.svg.disableButton,
+ ),
+ ],
+ ),
),
- TextSpan(
- text:
- "\n\nAuto backup is a custom Stack Wallet feature that offers a convenient backup of your data."
- "To ensure maximum security, we recommend using a unique password that you haven't used anywhere "
- "else on the internet before. Your password is not stored.",
- style:
- STextStyles.desktopTextExtraExtraSmall(context),
+ Center(
+ child: Row(
+ children: [
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.all(10),
+ child: RichText(
+ textAlign: TextAlign.start,
+ text: TextSpan(
+ children: [
+ TextSpan(
+ text: "Auto Backup",
+ style: STextStyles.desktopTextSmall(
+ context),
+ ),
+ TextSpan(
+ text:
+ "\n\nAuto backup is a custom Stack Wallet feature that offers a convenient backup of your data."
+ "To ensure maximum security, we recommend using a unique password that you haven't used anywhere "
+ "else on the internet before. Your password is not stored.",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context),
+ ),
+ TextSpan(
+ text:
+ "\n\nFor more information, please see our website ",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context),
+ ),
+ TextSpan(
+ text: "stackwallet.com",
+ style: STextStyles.richLink(context)
+ .copyWith(fontSize: 14),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () {
+ launchUrl(
+ Uri.parse(
+ "https://stackwallet.com/"),
+ mode: LaunchMode
+ .externalApplication,
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
),
- TextSpan(
- text:
- "\n\nFor more information, please see our website ",
- style:
- STextStyles.desktopTextExtraExtraSmall(context),
- ),
- TextSpan(
- text: "stackwallet.com",
- style: STextStyles.richLink(context)
- .copyWith(fontSize: 14),
- recognizer: TapGestureRecognizer()
- ..onTap = () {
- launchUrl(
- Uri.parse("https://stackwallet.com/"),
- mode: LaunchMode.externalApplication,
- );
- },
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(10),
+ child: !isEnabledAutoBackup
+ ? PrimaryButton(
+ desktopMed: true,
+ width: 200,
+ label: "Enable auto backup",
+ onPressed: () {
+ enableAutoBackup(context);
+ },
+ )
+ : Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.start,
+ children: [
+ Container(
+ width: 403,
+ color: Theme.of(context)
+ .extension()!
+ .background,
+ child: Padding(
+ padding:
+ const EdgeInsets.all(8.0),
+ child: Column(
+ children: [
+ Row(
+ mainAxisAlignment:
+ MainAxisAlignment
+ .spaceBetween,
+ children: [
+ Text(
+ "Backed up ${prettySinceLastBackupString(ref.watch(prefsChangeNotifierProvider.select((value) => value.lastAutoBackup)))}",
+ style: STextStyles
+ .itemSubtitle(
+ context),
+ ),
+ BlueTextButton(
+ text: "Back up now",
+ onTap: () {
+ ref
+ .read(
+ autoSWBServiceProvider)
+ .doBackup();
+ },
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 20,
+ ),
+ Row(
+ children: [
+ PrimaryButton(
+ desktopMed: true,
+ width: 190,
+ label: "Disable auto backup",
+ onPressed: () {
+ attemptDisable();
+ },
+ ),
+ const SizedBox(width: 16),
+ SecondaryButton(
+ desktopMed: true,
+ width: 190,
+ label: "Edit auto backup",
+ onPressed: () {
+ createAutoBackup();
+ },
+ ),
+ ],
+ )
+ ],
+ ),
+ ),
+ ],
),
],
),
),
),
- ),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: const [
- Padding(
- padding: EdgeInsets.all(
- 10,
- ),
- child: AutoBackupButton(),
+ const SizedBox(
+ height: 25,
+ ),
+ Padding(
+ padding: const EdgeInsets.only(
+ right: 30,
),
- ],
- ),
- ],
- ),
- ),
- ),
- const SizedBox(
- height: 25,
- ),
- Padding(
- padding: const EdgeInsets.only(
- right: 30,
- ),
- child: RoundedWhiteContainer(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SvgPicture.asset(
- Assets.svg.backupAdd,
- width: 48,
- height: 48,
- alignment: Alignment.topLeft,
- ),
- Center(
- child: Padding(
- padding: const EdgeInsets.all(10),
- child: RichText(
- textAlign: TextAlign.start,
- text: TextSpan(
+ child: RoundedWhiteContainer(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
- TextSpan(
- text: "Manual Backup",
- style: STextStyles.desktopTextSmall(context),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: SvgPicture.asset(
+ Assets.svg.backupAdd,
+ width: 48,
+ height: 48,
+ alignment: Alignment.topLeft,
+ ),
),
- TextSpan(
- text:
- "\n\nCreate manual backup to easily transfer your data between devices. "
- "You will create a backup file that can be later used in the Restore option. "
- "Use a strong password to encrypt your data.",
- style:
- STextStyles.desktopTextExtraExtraSmall(context),
+ Center(
+ child: Row(
+ children: [
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.all(10),
+ child: RichText(
+ textAlign: TextAlign.start,
+ text: TextSpan(
+ children: [
+ TextSpan(
+ text: "Manual Backup",
+ style: STextStyles.desktopTextSmall(
+ context),
+ ),
+ TextSpan(
+ text:
+ "\n\nCreate manual backup to easily transfer your data between devices. "
+ "You will create a backup file that can be later used in the Restore option. "
+ "Use a strong password to encrypt your data.",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(
+ 10,
+ ),
+ child: createBackup
+ ? const SizedBox(
+ width: 512,
+ child: CreateBackupView(),
+ )
+ : PrimaryButton(
+ desktopMed: true,
+ width: 200,
+ label: "Create manual backup",
+ onPressed: () {
+ setState(() {
+ createBackup = true;
+ });
+ },
+ ),
+ ),
+ ],
),
],
),
),
),
- ),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: const [
- Padding(
- padding: EdgeInsets.all(
- 10,
- ),
- child: ManualBackupButton(),
+ const SizedBox(
+ height: 25,
+ ),
+ Padding(
+ padding: const EdgeInsets.only(
+ right: 30,
+ bottom: 40,
),
- ],
- ),
- ],
- ),
- ),
- ),
- const SizedBox(
- height: 25,
- ),
- Padding(
- padding: const EdgeInsets.only(
- right: 30,
- ),
- child: RoundedWhiteContainer(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SvgPicture.asset(
- Assets.svg.backupRestore,
- width: 48,
- height: 48,
- alignment: Alignment.topLeft,
- ),
- Center(
- child: Padding(
- padding: const EdgeInsets.all(10),
- child: RichText(
- textAlign: TextAlign.start,
- text: TextSpan(
+ child: RoundedWhiteContainer(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
- TextSpan(
- text: "Restore Backup",
- style: STextStyles.desktopTextSmall(context),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: SvgPicture.asset(
+ Assets.svg.backupRestore,
+ width: 48,
+ height: 48,
+ alignment: Alignment.topLeft,
+ ),
),
- TextSpan(
- text:
- "\n\nUse your Stack Wallet backup file to restore your wallets, address book "
- "and wallet preferences.",
- style:
- STextStyles.desktopTextExtraExtraSmall(context),
+ Center(
+ child: Row(
+ children: [
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.all(10),
+ child: RichText(
+ textAlign: TextAlign.start,
+ text: TextSpan(
+ children: [
+ TextSpan(
+ text: "Restore Backup",
+ style: STextStyles.desktopTextSmall(
+ context),
+ ),
+ TextSpan(
+ text:
+ "\n\nUse your Stack Wallet backup file to restore your wallets, address book "
+ "and wallet preferences.",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(
+ 10,
+ ),
+ child: restoreBackup
+ ? const SizedBox(
+ width: 512,
+ child: RestoreFromFileView(),
+ )
+ : PrimaryButton(
+ desktopMed: true,
+ width: 200,
+ label: "Restore backup",
+ onPressed: () {
+ setState(() {
+ restoreBackup = true;
+ });
+ },
+ ),
+ ),
+ ],
),
],
),
),
),
- ),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: const [
- Padding(
- padding: EdgeInsets.all(
- 10,
- ),
- child: RestoreBackupButton(),
- ),
- ],
- ),
- ],
+ ],
+ ),
),
- ),
- ),
- ],
- );
- }
-}
-
-class AutoBackupButton extends ConsumerWidget {
- const AutoBackupButton({
- Key? key,
- }) : super(key: key);
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- Future enableAutoBackup() async {
- await showDialog(
- context: context,
- useSafeArea: false,
- barrierDismissible: true,
- builder: (context) {
- return const EnableBackupDialog();
- },
- );
- }
-
- return SizedBox(
- width: 200,
- height: 48,
- child: TextButton(
- style: Theme.of(context)
- .extension()!
- .getPrimaryEnabledButtonColor(context),
- onPressed: () {
- enableAutoBackup();
- },
- child: Text(
- "Enable auto backup",
- style: STextStyles.button(context),
- ),
- ),
- );
- }
-}
-
-class ManualBackupButton extends ConsumerWidget {
- const ManualBackupButton({
- Key? key,
- }) : super(key: key);
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- return SizedBox(
- width: 200,
- height: 48,
- child: TextButton(
- style: Theme.of(context)
- .extension()!
- .getPrimaryEnabledButtonColor(context),
- onPressed: () {},
- child: Text(
- "Create manual backup",
- style: STextStyles.button(context),
- ),
- ),
- );
- }
-}
-
-class RestoreBackupButton extends ConsumerWidget {
- const RestoreBackupButton({
- Key? key,
- }) : super(key: key);
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- Future restoreBackup() async {
- await showDialog(
- context: context,
- useSafeArea: false,
- barrierDismissible: true,
- builder: (context) {
- return const RestoreBackupDialog();
- },
- );
- }
-
- return SizedBox(
- width: 200,
- height: 48,
- child: TextButton(
- style: Theme.of(context)
- .extension()!
- .getPrimaryEnabledButtonColor(context),
- onPressed: () {
- restoreBackup();
- },
- child: Text(
- "Restore",
- style: STextStyles.button(context),
- ),
- ),
- );
+ ));
+ });
}
}
diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart
index f3e502bcb..acd0e689c 100644
--- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart
+++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/create_auto_backup.dart
@@ -1,55 +1,111 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:dropdown_button2/dropdown_button2.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/svg.dart';
+import 'package:stack_wallet_backup/stack_wallet_backup.dart';
+import 'package:stackwallet/notifications/show_flush_bar.dart';
+import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart';
+import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart';
+import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
+import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
+import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
+import 'package:stackwallet/utilities/enums/log_level_enum.dart';
+import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
+import 'package:stackwallet/utilities/format.dart';
+import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
+import 'package:stackwallet/widgets/progress_bar.dart';
+import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
+import 'package:zxcvbn/zxcvbn.dart';
-class CreateAutoBackup extends StatefulWidget {
- const CreateAutoBackup({Key? key}) : super(key: key);
+class CreateAutoBackup extends ConsumerStatefulWidget {
+ const CreateAutoBackup({
+ Key? key,
+ this.secureStore = const SecureStorageWrapper(
+ FlutterSecureStorage(),
+ ),
+ }) : super(key: key);
+
+ final FlutterSecureStorageInterface secureStore;
@override
- State createState() => _CreateAutoBackup();
+ ConsumerState createState() => _CreateAutoBackup();
}
-class _CreateAutoBackup extends State {
+class _CreateAutoBackup extends ConsumerState {
late final TextEditingController fileLocationController;
late final TextEditingController passphraseController;
late final TextEditingController passphraseRepeatController;
- late final FocusNode chooseFileLocation;
+ late final FlutterSecureStorageInterface secureStore;
+
+ late final StackFileSystem stackFileSystem;
late final FocusNode passphraseFocusNode;
late final FocusNode passphraseRepeatFocusNode;
+ final zxcvbn = Zxcvbn();
bool shouldShowPasswordHint = true;
bool hidePassword = true;
+ String passwordFeedback =
+ "Add another word or two. Uncommon words are better. Use a few words, avoid common phrases. No need for symbols, digits, or uppercase letters.";
+ double passwordStrength = 0.0;
+
+ bool get shouldEnableCreate {
+ return fileLocationController.text.isNotEmpty &&
+ passphraseController.text.isNotEmpty &&
+ passphraseRepeatController.text.isNotEmpty;
+ }
+
bool get fieldsMatch =>
passphraseController.text == passphraseRepeatController.text;
- String _currentDropDownValue = "Every 10 minutes";
+ BackupFrequencyType _currentDropDownValue =
+ BackupFrequencyType.everyTenMinutes;
- final List _dropDownItems = [
- "Every 10 minutes",
- "Every 20 minutes",
- "Every 30 minutes",
+ final List _dropDownItems = [
+ BackupFrequencyType.everyTenMinutes,
+ BackupFrequencyType.everyAppStart,
+ BackupFrequencyType.afterClosingAWallet,
];
@override
void initState() {
+ secureStore = widget.secureStore;
+ stackFileSystem = StackFileSystem();
+
fileLocationController = TextEditingController();
passphraseController = TextEditingController();
passphraseRepeatController = TextEditingController();
- chooseFileLocation = FocusNode();
passphraseFocusNode = FocusNode();
passphraseRepeatFocusNode = FocusNode();
+ if (Platform.isAndroid) {
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
+ final dir = await stackFileSystem.prepareStorage();
+ if (mounted) {
+ setState(() {
+ fileLocationController.text = dir.path;
+ });
+ }
+ });
+ }
+
super.initState();
}
@@ -59,7 +115,6 @@ class _CreateAutoBackup extends State {
passphraseController.dispose();
passphraseRepeatController.dispose();
- chooseFileLocation.dispose();
passphraseFocusNode.dispose();
passphraseRepeatFocusNode.dispose();
@@ -70,10 +125,13 @@ class _CreateAutoBackup extends State {
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType ");
- String? selectedItem = "Every 10 minutes";
+ bool isEnabledAutoBackup = ref.watch(prefsChangeNotifierProvider
+ .select((value) => value.isAutoBackupEnabled));
+ String? selectedItem = "Every 10 minutes";
+ final isDesktop = Util.isDesktop;
return DesktopDialog(
- maxHeight: 650,
+ maxHeight: 680,
maxWidth: 600,
child: Column(
children: [
@@ -127,198 +185,287 @@ class _CreateAutoBackup extends State {
height: 10,
),
Padding(
- padding: const EdgeInsets.only(
- left: 32,
- right: 32,
- ),
- child: ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: const Key("backupChooseFileLocation"),
- focusNode: chooseFileLocation,
- controller: fileLocationController,
- style: STextStyles.desktopTextMedium(context).copyWith(
- height: 2,
- ),
- textAlign: TextAlign.left,
- enableSuggestions: false,
- autocorrect: false,
- decoration: standardInputDecoration(
- "Save to...",
- chooseFileLocation,
- context,
- ).copyWith(
- labelStyle:
- STextStyles.desktopTextExtraExtraSmall(context).copyWith(
- color:
- Theme.of(context).extension()!.textDark3,
- ),
- suffixIcon: Container(
- decoration: BoxDecoration(
+ padding: const EdgeInsets.symmetric(horizontal: 32),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ if (!Platform.isAndroid)
+ Consumer(builder: (context, ref, __) {
+ return Container(
color: Colors.transparent,
- borderRadius: BorderRadius.circular(1000),
- ),
- height: 32,
- width: 32,
- child: Center(
- child: SvgPicture.asset(
- Assets.svg.folder,
- color: Theme.of(context)
- .extension()!
- .textDark3,
- width: 20,
- height: 17.5,
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- const SizedBox(
- height: 24,
- ),
- Container(
- alignment: Alignment.centerLeft,
- padding: const EdgeInsets.only(left: 32),
- child: Text(
- "Create a passphrase",
- style: STextStyles.desktopTextExtraSmall(context).copyWith(
- color: Theme.of(context).extension()!.textDark3,
- ),
- textAlign: TextAlign.left,
- ),
- ),
- const SizedBox(
- height: 10,
- ),
- Padding(
- padding: const EdgeInsets.only(
- left: 32,
- right: 32,
- ),
- child: ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: const Key("createBackupPassphrase"),
- focusNode: passphraseFocusNode,
- controller: passphraseController,
- style: STextStyles.desktopTextMedium(context).copyWith(
- height: 2,
- ),
- obscureText: hidePassword,
- enableSuggestions: false,
- autocorrect: false,
- decoration: standardInputDecoration(
- "Create passphrase",
- passphraseFocusNode,
- context,
- ).copyWith(
- labelStyle:
- STextStyles.desktopTextExtraExtraSmall(context).copyWith(
- color:
- Theme.of(context).extension()!.textDark3,
- ),
- suffixIcon: UnconstrainedBox(
- child: GestureDetector(
- key: const Key(
- "createDesktopAutoBackupShowPassphraseButton1"),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: Container(
- decoration: BoxDecoration(
- color: Colors.transparent,
- borderRadius: BorderRadius.circular(1000),
- ),
- height: 32,
- width: 32,
- child: Center(
- child: SvgPicture.asset(
- hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash,
- color: Theme.of(context)
- .extension()!
- .textDark3,
- width: 20,
- height: 17.5,
+ child: TextField(
+ autocorrect: false,
+ enableSuggestions: false,
+ onTap: Platform.isAndroid
+ ? null
+ : () async {
+ try {
+ await stackFileSystem.prepareStorage();
+
+ if (mounted) {
+ await stackFileSystem.pickDir(context);
+ }
+
+ if (mounted) {
+ setState(() {
+ fileLocationController.text =
+ stackFileSystem.dirPath ?? "";
+ });
+ }
+ } catch (e, s) {
+ Logging.instance
+ .log("$e\n$s", level: LogLevel.Error);
+ }
+ },
+ controller: fileLocationController,
+ style: STextStyles.field(context),
+ decoration: InputDecoration(
+ hintText: "Save to...",
+ hintStyle: STextStyles.fieldLabel(context),
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(
+ width: 16,
+ ),
+ SvgPicture.asset(
+ Assets.svg.folder,
+ color: Theme.of(context)
+ .extension()!
+ .textDark3,
+ width: 16,
+ height: 16,
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ ],
+ ),
),
),
+ key: const Key(
+ "createBackupSaveToFileLocationTextFieldKey"),
+ readOnly: true,
+ toolbarOptions: const ToolbarOptions(
+ copy: true,
+ cut: false,
+ paste: false,
+ selectAll: false,
+ ),
+ onChanged: (newValue) {},
),
+ );
+ }),
+ if (!Platform.isAndroid)
+ const SizedBox(
+ height: 24,
+ ),
+ if (isDesktop)
+ Padding(
+ padding: const EdgeInsets.only(bottom: 10.0),
+ child: Text(
+ "Create a passphrase",
+ style: STextStyles.desktopTextExtraSmall(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark3),
+ textAlign: TextAlign.left,
),
),
- ),
- ),
- ),
- ),
- const SizedBox(
- height: 16,
- ),
- Padding(
- padding: const EdgeInsets.only(
- left: 32,
- right: 32,
- ),
- child: ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: const Key("createBackupPassphrase"),
- focusNode: passphraseRepeatFocusNode,
- controller: passphraseRepeatController,
- style: STextStyles.desktopTextMedium(context).copyWith(
- height: 2,
- ),
- obscureText: hidePassword,
- enableSuggestions: false,
- autocorrect: false,
- decoration: standardInputDecoration(
- "Confirm passphrase",
- passphraseRepeatFocusNode,
- context,
- ).copyWith(
- labelStyle:
- STextStyles.desktopTextExtraExtraSmall(context).copyWith(
- color:
- Theme.of(context).extension()!.textDark3,
+ ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
),
- suffixIcon: UnconstrainedBox(
- child: GestureDetector(
- key: const Key(
- "createDesktopAutoBackupShowPassphraseButton2"),
- onTap: () async {
+ child: TextField(
+ key: const Key("createBackupPasswordFieldKey1"),
+ focusNode: passphraseFocusNode,
+ controller: passphraseController,
+ style: STextStyles.field(context),
+ obscureText: hidePassword,
+ enableSuggestions: false,
+ autocorrect: false,
+ decoration: standardInputDecoration(
+ "Create passphrase",
+ passphraseFocusNode,
+ context,
+ ).copyWith(
+ labelStyle:
+ isDesktop ? STextStyles.fieldLabel(context) : null,
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(
+ width: 16,
+ ),
+ GestureDetector(
+ key: const Key(
+ "createBackupPasswordFieldShowPasswordButtonKey"),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(context)
+ .extension()!
+ .textDark3,
+ width: 16,
+ height: 16,
+ ),
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ ],
+ ),
+ ),
+ ),
+ onChanged: (newValue) {
+ if (newValue.isEmpty) {
setState(() {
- hidePassword = !hidePassword;
+ passwordFeedback = "";
});
- },
- child: Container(
- decoration: BoxDecoration(
- color: Colors.transparent,
- borderRadius: BorderRadius.circular(1000),
- ),
- height: 32,
- width: 32,
- child: Center(
- child: SvgPicture.asset(
- hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash,
- color: Theme.of(context)
- .extension()!
- .textDark3,
- width: 20,
- height: 17.5,
- ),
+ return;
+ }
+ final result = zxcvbn.evaluate(newValue);
+ String suggestionsAndTips = "";
+ for (var sug in result.feedback.suggestions!.toSet()) {
+ suggestionsAndTips += "$sug\n";
+ }
+ suggestionsAndTips += result.feedback.warning!;
+ String feedback =
+ // "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n"
+ suggestionsAndTips;
+
+ passwordStrength = result.score! / 4;
+
+ // hack fix to format back string returned from zxcvbn
+ if (feedback.contains("phrasesNo need")) {
+ feedback = feedback.replaceFirst(
+ "phrasesNo need", "phrases\nNo need");
+ }
+
+ if (feedback.endsWith("\n")) {
+ feedback = feedback.substring(0, feedback.length - 2);
+ }
+
+ setState(() {
+ passwordFeedback = feedback;
+ });
+ },
+ ),
+ ),
+ if (passphraseFocusNode.hasFocus ||
+ passphraseRepeatFocusNode.hasFocus ||
+ passphraseController.text.isNotEmpty)
+ Padding(
+ padding: EdgeInsets.only(
+ left: 12,
+ right: 12,
+ top: passwordFeedback.isNotEmpty ? 4 : 0,
+ ),
+ child: passwordFeedback.isNotEmpty
+ ? Text(
+ passwordFeedback,
+ style: STextStyles.infoSmall(context),
+ )
+ : null,
+ ),
+ if (passphraseFocusNode.hasFocus ||
+ passphraseRepeatFocusNode.hasFocus ||
+ passphraseController.text.isNotEmpty)
+ Padding(
+ padding: const EdgeInsets.only(
+ left: 12,
+ right: 12,
+ top: 10,
+ ),
+ child: ProgressBar(
+ key: const Key("createStackBackUpProgressBar"),
+ width: 512,
+ height: 5,
+ fillColor: passwordStrength < 0.51
+ ? Theme.of(context)
+ .extension()!
+ .accentColorRed
+ : passwordStrength < 1
+ ? Theme.of(context)
+ .extension()!
+ .accentColorYellow
+ : Theme.of(context)
+ .extension()!
+ .accentColorGreen,
+ backgroundColor: Theme.of(context)
+ .extension()!
+ .buttonBackSecondary,
+ percent:
+ passwordStrength < 0.25 ? 0.03 : passwordStrength,
+ ),
+ ),
+ const SizedBox(
+ height: 10,
+ ),
+ ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ key: const Key("createBackupPasswordFieldKey2"),
+ focusNode: passphraseRepeatFocusNode,
+ controller: passphraseRepeatController,
+ style: STextStyles.field(context),
+ obscureText: hidePassword,
+ enableSuggestions: false,
+ autocorrect: false,
+ decoration: standardInputDecoration(
+ "Confirm passphrase",
+ passphraseRepeatFocusNode,
+ context,
+ ).copyWith(
+ labelStyle: STextStyles.fieldLabel(context),
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(
+ width: 16,
+ ),
+ GestureDetector(
+ key: const Key(
+ "createBackupPasswordFieldShowPasswordButtonKey"),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(context)
+ .extension()!
+ .textDark3,
+ width: 16,
+ height: 16,
+ ),
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ ],
),
),
),
+ onChanged: (newValue) {
+ setState(() {});
+ // TODO: ? check if passwords match?
+ },
),
),
- ),
+ ],
),
),
const SizedBox(
@@ -343,39 +490,85 @@ class _CreateAutoBackup extends State {
left: 32,
right: 32,
),
- child: DropdownButtonFormField(
- isExpanded: true,
- elevation: 0,
- style: STextStyles.desktopTextExtraSmall(context).copyWith(
- color: Theme.of(context).extension()!.textDark,
- ),
- icon: SvgPicture.asset(
- Assets.svg.chevronDown,
- width: 10,
- height: 5,
- color: Theme.of(context).extension()!.textDark3,
- ),
- dropdownColor:
- Theme.of(context).extension()!.textFieldActiveBG,
- // focusColor: ,
- value: _currentDropDownValue,
- items: _dropDownItems
- .map(
- (e) => DropdownMenuItem(
- value: e,
- child: Text(e),
+ child: isDesktop
+ ? DropdownButtonHideUnderline(
+ child: DropdownButton2(
+ offset: Offset(0, -10),
+ isExpanded: true,
+ dropdownElevation: 0,
+ value: _currentDropDownValue,
+ items: [
+ ..._dropDownItems.map(
+ (e) {
+ String message = "";
+ switch (e) {
+ case BackupFrequencyType.everyTenMinutes:
+ message = "Every 10 minutes";
+ break;
+ case BackupFrequencyType.everyAppStart:
+ message = "Every app startup";
+ break;
+ case BackupFrequencyType.afterClosingAWallet:
+ message =
+ "After closing a cryptocurrency wallet";
+ break;
+ }
+
+ return DropdownMenuItem(
+ value: e,
+ child: Text(message),
+ );
+ },
+ ),
+ ],
+ onChanged: (value) {
+ if (value is BackupFrequencyType) {
+ if (ref
+ .read(prefsChangeNotifierProvider)
+ .backupFrequencyType !=
+ value) {
+ ref
+ .read(prefsChangeNotifierProvider)
+ .backupFrequencyType = value;
+ }
+ setState(() {
+ _currentDropDownValue = value;
+ });
+ }
+ },
+ icon: SvgPicture.asset(
+ Assets.svg.chevronDown,
+ width: 10,
+ height: 5,
+ color: Theme.of(context)
+ .extension()!
+ .textDark3,
+ ),
+ buttonPadding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 8,
+ ),
+ buttonDecoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG,
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ dropdownDecoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG,
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
),
)
- .toList(),
- onChanged: (value) {
- if (value is String) {
- setState(() {
- _currentDropDownValue = value;
- });
- }
- },
- ),
+ : null,
),
+ const Spacer(),
Padding(
padding: const EdgeInsets.all(32),
child: Row(
@@ -385,7 +578,9 @@ class _CreateAutoBackup extends State {
label: "Cancel",
onPressed: () {
int count = 0;
- Navigator.of(context).popUntil((_) => count++ >= 2);
+ !isEnabledAutoBackup
+ ? Navigator.of(context).popUntil((_) => count++ >= 2)
+ : Navigator.of(context).pop();
},
),
),
@@ -395,8 +590,164 @@ class _CreateAutoBackup extends State {
Expanded(
child: PrimaryButton(
label: "Enable Auto Backup",
- enabled: false,
- onPressed: () {},
+ enabled: shouldEnableCreate,
+ onPressed: !shouldEnableCreate
+ ? null
+ : () async {
+ final String pathToSave =
+ fileLocationController.text;
+ final String passphrase = passphraseController.text;
+ final String repeatPassphrase =
+ passphraseRepeatController.text;
+
+ if (pathToSave.isEmpty) {
+ showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "Directory not chosen",
+ context: context,
+ );
+ return;
+ }
+ if (!(await Directory(pathToSave).exists())) {
+ showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "Directory does not exist",
+ context: context,
+ );
+ return;
+ }
+ if (passphrase.isEmpty) {
+ showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "A passphrase is required",
+ context: context,
+ );
+ return;
+ }
+ if (passphrase != repeatPassphrase) {
+ showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "Passphrase does not match",
+ context: context,
+ );
+ return;
+ }
+
+ showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => const StackDialog(
+ title: "Encrypting initial backup",
+ message: "This shouldn't take long",
+ ),
+ );
+
+ // make sure the dialog is able to be displayed for at least some time
+ final fut = Future.delayed(
+ const Duration(milliseconds: 300));
+
+ String adkString;
+ int adkVersion;
+ try {
+ final adk =
+ await compute(generateAdk, passphrase);
+ adkString = Format.uint8listToString(adk.item2);
+ adkVersion = adk.item1;
+ } on Exception catch (e, s) {
+ String err = getErrorMessageFromSWBException(e);
+ Logging.instance
+ .log("$err\n$s", level: LogLevel.Error);
+ // pop encryption progress dialog
+ Navigator.of(context).pop();
+ showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: err,
+ context: context,
+ );
+ return;
+ } catch (e, s) {
+ Logging.instance
+ .log("$e\n$s", level: LogLevel.Error);
+ // pop encryption progress dialog
+ Navigator.of(context).pop();
+ showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: "$e",
+ context: context,
+ );
+ return;
+ }
+
+ await secureStore.write(
+ key: "auto_adk_string", value: adkString);
+ await secureStore.write(
+ key: "auto_adk_version_string",
+ value: adkVersion.toString());
+
+ final DateTime now = DateTime.now();
+ final String fileToSave =
+ createAutoBackupFilename(pathToSave, now);
+
+ final backup = await SWB.createStackWalletJSON();
+
+ bool result = await SWB.encryptStackWalletWithADK(
+ fileToSave,
+ adkString,
+ jsonEncode(backup),
+ adkVersion: adkVersion,
+ );
+
+ // this future should already be complete unless there was an error encrypting
+ await Future.wait([fut]);
+
+ if (mounted) {
+ // pop encryption progress dialog
+ int count = 0;
+ Navigator.of(context)
+ .popUntil((_) => count++ >= 2);
+
+ if (result) {
+ ref
+ .read(prefsChangeNotifierProvider)
+ .autoBackupLocation = pathToSave;
+ ref
+ .read(prefsChangeNotifierProvider)
+ .lastAutoBackup = now;
+
+ ref
+ .read(prefsChangeNotifierProvider)
+ .isAutoBackupEnabled = true;
+
+ await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => Platform.isAndroid
+ ? StackOkDialog(
+ title:
+ "Stack Auto Backup enabled and saved to:",
+ message: fileToSave,
+ )
+ : const StackOkDialog(
+ title: "Stack Auto Backup enabled!"),
+ );
+ if (mounted) {
+ passphraseController.text = "";
+ passphraseRepeatController.text = "";
+
+ int count = 0;
+ Navigator.of(context)
+ .popUntil((_) => count++ >= 2);
+ }
+ } else {
+ await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => const StackOkDialog(
+ title: "Failed to enable Auto Backup"),
+ );
+ }
+ }
+ },
),
)
],
diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart
index 046d136a8..963fb4441 100644
--- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart
+++ b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/enable_backup_dialog.dart
@@ -61,8 +61,7 @@ class EnableBackupDialog extends StatelessWidget {
child: SecondaryButton(
label: "Cancel",
onPressed: () {
- int count = 0;
- Navigator.of(context).popUntil((_) => count++ >= 2);
+ Navigator.of(context).pop();
},
),
),
diff --git a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart b/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart
deleted file mode 100644
index 07d49274a..000000000
--- a/lib/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.dart
+++ /dev/null
@@ -1,93 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:stackwallet/utilities/text_styles.dart';
-import 'package:stackwallet/utilities/theme/stack_colors.dart';
-import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
-import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
-import 'package:stackwallet/widgets/desktop/primary_button.dart';
-import 'package:stackwallet/widgets/desktop/secondary_button.dart';
-
-class RestoreBackupDialog extends StatelessWidget {
- const RestoreBackupDialog({Key? key}) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- return DesktopDialog(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Padding(
- padding: const EdgeInsets.all(32),
- child: Text(
- "Restoring Stack Wallet",
- style: STextStyles.desktopH3(context),
- textAlign: TextAlign.center,
- ),
- ),
- const DesktopDialogCloseButton(),
- ],
- ),
- const SizedBox(
- height: 30,
- ),
- Padding(
- padding: const EdgeInsets.only(
- left: 32,
- right: 32,
- ),
- child: Row(
- children: [
- Text(
- "Settings",
- style: STextStyles.desktopTextExtraSmall(context).copyWith(
- color:
- Theme.of(context).extension()!.textDark3,
- ),
- textAlign: TextAlign.left,
- ),
- ],
- ),
- ),
- // RoundedWhiteContainer(
- // child: Column(
- // crossAxisAlignment: CrossAxisAlignment.start,
- // children: [
- // Row(),
- // ],
- // ),
- // ),
- const Spacer(),
- Padding(
- padding: const EdgeInsets.all(32),
- child: Row(
- children: [
- Expanded(
- child: SecondaryButton(
- label: "Cancel",
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- ),
- const SizedBox(
- width: 16,
- ),
- Expanded(
- child: PrimaryButton(
- label: "Continue",
- onPressed: () {
- // Navigator.of(context).pop();
- // onConfirm.call();
- },
- ),
- )
- ],
- ),
- ),
- ],
- ),
- );
- }
-}
diff --git a/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart b/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart
new file mode 100644
index 000000000..18988cb68
--- /dev/null
+++ b/lib/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart
@@ -0,0 +1,716 @@
+import 'dart:convert';
+
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS;
+import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS;
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:http/http.dart';
+import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS;
+import 'package:package_info_plus/package_info_plus.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
+import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
+import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
+import 'package:stackwallet/widgets/rounded_white_container.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+const kGithubAPI = "https://api.github.com";
+const kGithubSearch = "/search/commits";
+const kGithubHead = "/repos";
+
+enum CommitStatus { isHead, isOldCommit, notACommit, notLoaded }
+
+Future doesCommitExist(
+ String organization,
+ String project,
+ String commit,
+) async {
+ Logging.instance.log("doesCommitExist", level: LogLevel.Info);
+ final Client client = Client();
+ try {
+ final uri = Uri.parse(
+ "$kGithubAPI$kGithubHead/$organization/$project/commits/$commit");
+
+ final commitQuery = await client.get(
+ uri,
+ headers: {'Content-Type': 'application/json'},
+ );
+
+ final response = jsonDecode(commitQuery.body.toString());
+ Logging.instance.log("doesCommitExist $project $commit $response",
+ level: LogLevel.Info);
+ bool isThereCommit;
+ try {
+ isThereCommit = response['sha'] == commit;
+ Logging.instance
+ .log("isThereCommit $isThereCommit", level: LogLevel.Info);
+ return isThereCommit;
+ } catch (e, s) {
+ return false;
+ }
+ } catch (e, s) {
+ Logging.instance.log("$e $s", level: LogLevel.Error);
+ return false;
+ }
+}
+
+Future isHeadCommit(
+ String organization,
+ String project,
+ String branch,
+ String commit,
+) async {
+ Logging.instance.log("doesCommitExist", level: LogLevel.Info);
+ final Client client = Client();
+ try {
+ final uri = Uri.parse(
+ "$kGithubAPI$kGithubHead/$organization/$project/commits/$branch");
+
+ final commitQuery = await client.get(
+ uri,
+ headers: {'Content-Type': 'application/json'},
+ );
+
+ final response = jsonDecode(commitQuery.body.toString());
+ Logging.instance.log("isHeadCommit $project $commit $branch $response",
+ level: LogLevel.Info);
+ bool isHead;
+ try {
+ isHead = response['sha'] == commit;
+ Logging.instance.log("isHead $isHead", level: LogLevel.Info);
+ return isHead;
+ } catch (e, s) {
+ return false;
+ }
+ } catch (e, s) {
+ Logging.instance.log("$e $s", level: LogLevel.Error);
+ return false;
+ }
+}
+
+class DesktopAboutView extends ConsumerWidget {
+ const DesktopAboutView({Key? key}) : super(key: key);
+
+ static const String routeName = "/desktopAboutView";
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ String firoCommit = FIRO_VERSIONS.getPluginVersion();
+ String epicCashCommit = EPIC_VERSIONS.getPluginVersion();
+ String moneroCommit = MONERO_VERSIONS.getPluginVersion();
+ List futureFiroList = [
+ doesCommitExist("cypherstack", "flutter_liblelantus", firoCommit),
+ isHeadCommit("cypherstack", "flutter_liblelantus", "main", firoCommit),
+ ];
+ Future commitFiroFuture = Future.wait(futureFiroList);
+ List futureEpicList = [
+ doesCommitExist("cypherstack", "flutter_libepiccash", epicCashCommit),
+ isHeadCommit(
+ "cypherstack", "flutter_libepiccash", "main", epicCashCommit),
+ ];
+ Future commitEpicFuture = Future.wait(futureEpicList);
+ List futureMoneroList = [
+ doesCommitExist("cypherstack", "flutter_libmonero", moneroCommit),
+ isHeadCommit("cypherstack", "flutter_libmonero", "main", moneroCommit),
+ ];
+ Future commitMoneroFuture = Future.wait(futureMoneroList);
+
+ debugPrint("BUILD: $runtimeType");
+ return DesktopScaffold(
+ background: Theme.of(context).extension()!.background,
+ appBar: DesktopAppBar(
+ isCompactHeight: true,
+ leading: Row(
+ children: [
+ const SizedBox(
+ width: 24,
+ height: 24,
+ ),
+ Text(
+ "About",
+ style: STextStyles.desktopH3(context),
+ )
+ ],
+ ),
+ ),
+ body: Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.fromLTRB(24, 10, 24, 35),
+ child: Row(
+ children: [
+ Expanded(
+ child: RoundedWhiteContainer(
+ width: 929,
+ height: 411,
+ child: Padding(
+ padding: const EdgeInsets.only(left: 10, top: 10),
+ child: Column(
+ // mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Text(
+ "Stack Wallet",
+ style: STextStyles.desktopH3(context),
+ textAlign: TextAlign.start,
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Row(
+ children: [
+ RichText(
+ textAlign: TextAlign.start,
+ text: TextSpan(
+ style: STextStyles.label(context),
+ children: [
+ TextSpan(
+ text:
+ "By using Stack Wallet, you agree to the ",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark3),
+ ),
+ TextSpan(
+ text: "Terms of service",
+ style: STextStyles.richLink(context)
+ .copyWith(fontSize: 14),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () {
+ launchUrl(
+ Uri.parse(
+ "https://stackwallet.com/terms-of-service.html"),
+ mode:
+ LaunchMode.externalApplication,
+ );
+ },
+ ),
+ TextSpan(
+ text: " and ",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark3),
+ ),
+ TextSpan(
+ text: "Privacy policy",
+ style: STextStyles.richLink(context)
+ .copyWith(fontSize: 14),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () {
+ launchUrl(
+ Uri.parse(
+ "https://stackwallet.com/privacy-policy.html"),
+ mode:
+ LaunchMode.externalApplication,
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 32),
+ Padding(
+ padding:
+ const EdgeInsets.only(right: 10, bottom: 10),
+ child: Column(
+ children: [
+ FutureBuilder(
+ future: PackageInfo.fromPlatform(),
+ builder: (context,
+ AsyncSnapshot snapshot) {
+ String version = "";
+ String signature = "";
+ String build = "";
+
+ if (snapshot.connectionState ==
+ ConnectionState.done &&
+ snapshot.hasData) {
+ version = snapshot.data!.version;
+ build = snapshot.data!.buildNumber;
+ signature = snapshot.data!.buildSignature;
+ }
+
+ return Column(
+ mainAxisAlignment:
+ MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Version",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .textDark),
+ ),
+ const SizedBox(
+ height: 2,
+ ),
+ SelectableText(
+ version,
+ style:
+ STextStyles.itemSubtitle(
+ context),
+ ),
+ ],
+ ),
+ const SizedBox(
+ width: 400,
+ ),
+ Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Build number",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .textDark),
+ ),
+ const SizedBox(
+ height: 2,
+ ),
+ SelectableText(
+ build,
+ style:
+ STextStyles.itemSubtitle(
+ context),
+ ),
+ ],
+ ),
+ ],
+ ),
+ const SizedBox(height: 32),
+ Row(
+ children: [
+ Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Build signature",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .textDark),
+ ),
+ const SizedBox(
+ height: 2,
+ ),
+ SelectableText(
+ signature,
+ style:
+ STextStyles.itemSubtitle(
+ context),
+ ),
+ ],
+ ),
+ const SizedBox(
+ width: 350,
+ ),
+ FutureBuilder(
+ future: commitFiroFuture,
+ builder: (context,
+ AsyncSnapshot
+ snapshot) {
+ bool commitExists = false;
+ bool isHead = false;
+ CommitStatus stateOfCommit =
+ CommitStatus.notLoaded;
+
+ if (snapshot.connectionState ==
+ ConnectionState
+ .done &&
+ snapshot.hasData) {
+ commitExists = snapshot
+ .data![0] as bool;
+ isHead = snapshot.data![1]
+ as bool;
+ if (commitExists &&
+ isHead) {
+ stateOfCommit =
+ CommitStatus.isHead;
+ } else if (commitExists) {
+ stateOfCommit =
+ CommitStatus
+ .isOldCommit;
+ } else {
+ stateOfCommit =
+ CommitStatus
+ .notACommit;
+ }
+ }
+ TextStyle indicationStyle =
+ STextStyles.itemSubtitle(
+ context);
+ switch (stateOfCommit) {
+ case CommitStatus.isHead:
+ indicationStyle = STextStyles
+ .itemSubtitle(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .accentColorGreen);
+ break;
+ case CommitStatus
+ .isOldCommit:
+ indicationStyle = STextStyles
+ .itemSubtitle(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .accentColorYellow);
+ break;
+ case CommitStatus
+ .notACommit:
+ indicationStyle = STextStyles
+ .itemSubtitle(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .accentColorRed);
+ break;
+ default:
+ break;
+ }
+ return Column(
+ crossAxisAlignment:
+ CrossAxisAlignment
+ .start,
+ children: [
+ Text(
+ "Firo Build Commit",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .textDark),
+ ),
+ const SizedBox(
+ height: 2,
+ ),
+ SelectableText(
+ firoCommit,
+ style: indicationStyle,
+ ),
+ ],
+ );
+ }),
+ ],
+ ),
+ const SizedBox(height: 35),
+ Row(
+ children: [
+ FutureBuilder(
+ future: commitEpicFuture,
+ builder: (context,
+ AsyncSnapshot
+ snapshot) {
+ bool commitExists = false;
+ bool isHead = false;
+ CommitStatus stateOfCommit =
+ CommitStatus.notLoaded;
+
+ if (snapshot.connectionState ==
+ ConnectionState
+ .done &&
+ snapshot.hasData) {
+ commitExists = snapshot
+ .data![0] as bool;
+ isHead = snapshot.data![1]
+ as bool;
+ if (commitExists &&
+ isHead) {
+ stateOfCommit =
+ CommitStatus.isHead;
+ } else if (commitExists) {
+ stateOfCommit =
+ CommitStatus
+ .isOldCommit;
+ } else {
+ stateOfCommit =
+ CommitStatus
+ .notACommit;
+ }
+ }
+ TextStyle indicationStyle =
+ STextStyles.itemSubtitle(
+ context);
+ switch (stateOfCommit) {
+ case CommitStatus.isHead:
+ indicationStyle = STextStyles
+ .itemSubtitle(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .accentColorGreen);
+ break;
+ case CommitStatus
+ .isOldCommit:
+ indicationStyle = STextStyles
+ .itemSubtitle(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .accentColorYellow);
+ break;
+ case CommitStatus
+ .notACommit:
+ indicationStyle = STextStyles
+ .itemSubtitle(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .accentColorRed);
+ break;
+ default:
+ break;
+ }
+ return Column(
+ crossAxisAlignment:
+ CrossAxisAlignment
+ .start,
+ children: [
+ Text(
+ "Epic Cash Build Commit",
+ style: STextStyles
+ .desktopTextExtraExtraSmall(
+ context)
+ .copyWith(
+ color: Theme.of(
+ context)
+ .extension<
+ StackColors>()!
+ .textDark),
+ ),
+ const SizedBox(
+ height: 2,
+ ),
+ SelectableText(
+ epicCashCommit,
+ style: indicationStyle,
+ ),
+ ],
+ );
+ }),
+ const SizedBox(
+ width: 105,
+ ),
+ FutureBuilder(
+ future: commitMoneroFuture,
+ builder: (context,
+ AsyncSnapshot