mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 17:57:40 +00:00
commit
faee22f808
20 changed files with 1698 additions and 1283 deletions
4
assets/svg/framed-address-book.svg
Normal file
4
assets/svg/framed-address-book.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" rx="8" fill="#E0E3E3"/>
|
||||
<path d="M26 8H12.5C10.843 8 9.5 9.34297 9.5 11V29C9.5 30.657 10.843 32 12.5 32H26C27.657 32 29 30.657 29 29V11C29 9.34297 27.6547 8 26 8ZM19.25 14C20.907 14 22.25 15.343 22.25 17C22.25 18.657 20.907 20 19.25 20C17.5934 20 16.25 18.657 16.25 17C16.25 15.343 17.5953 14 19.25 14ZM23.75 26H14.75C14.3375 26 14 25.6625 14 25.25C14 23.1781 15.6781 21.5 17.75 21.5H20.75C22.8209 21.5 24.5 23.1791 24.5 25.25C24.5 25.6625 24.1625 26 23.75 26ZM31.25 11H30.5V15.5H31.25C31.6625 15.5 32 15.1625 32 14.75V11.75C32 11.3356 31.6625 11 31.25 11ZM31.25 17H30.5V21.5H31.25C31.6625 21.5 32 21.1625 32 20.75V17.75C32 17.3375 31.6625 17 31.25 17ZM31.25 23H30.5V27.5H31.25C31.6642 27.5 32 27.1642 32 26.75V23.75C32 23.3375 31.6625 23 31.25 23Z" fill="#232323"/>
|
||||
</svg>
|
After Width: | Height: | Size: 899 B |
4
assets/svg/framed-gear.svg
Normal file
4
assets/svg/framed-gear.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" rx="8" fill="#E0E3E3"/>
|
||||
<path d="M30.6765 16.1586C30.8183 16.5281 30.698 16.9449 30.4058 17.2156L28.5452 18.9086C28.5925 19.2652 28.6183 19.6305 28.6183 19.9613C28.6183 20.3695 28.5925 20.7348 28.5452 21.0914L30.4058 22.7844C30.698 23.0551 30.8183 23.4676 30.6765 23.8414C30.4874 24.3527 30.2597 24.8469 30.0019 25.3152L29.7999 25.6633C29.5163 26.1359 29.1984 26.5828 28.8503 27.0082C28.5925 27.3133 28.1757 27.4207 27.7976 27.3004L25.4042 26.5355C24.8284 26.9781 24.1538 27.3477 23.5136 27.6313L22.9765 30.0848C22.8906 30.4715 22.5898 30.7465 22.1945 30.8496C21.6015 30.9484 20.9913 31 20.3296 31C19.7452 31 19.1351 30.9484 18.5421 30.8496C18.1468 30.7465 17.846 30.4715 17.7601 30.0848L17.223 27.6313C16.5441 27.3477 15.9081 26.9781 15.3323 26.5355L12.9407 27.3004C12.5609 27.4207 12.1419 27.3133 11.8875 27.0082C11.5391 26.5828 11.2211 26.1359 10.9375 25.6633L10.7364 25.3152C10.4756 24.8469 10.2487 24.3527 10.0584 23.8414C9.91914 23.4719 10.0364 23.0551 10.3312 22.7844L12.19 21.0914C12.1428 20.7348 12.1183 20.3695 12.1183 20C12.1183 19.6305 12.1428 19.2652 12.19 18.9086L10.3312 17.2156C10.0364 16.9449 9.91914 16.5324 10.0584 16.1586C10.2487 15.6473 10.476 15.1531 10.7364 14.6848L10.9371 14.3367C11.2207 13.8641 11.5391 13.4172 11.8875 12.9939C12.1419 12.6867 12.5609 12.5802 12.9407 12.7013L15.3323 13.4645C15.9081 13.0202 16.5441 12.6506 17.223 12.37L17.7601 9.91652C17.846 9.52637 18.1468 9.21656 18.5421 9.15082C19.1351 9.05161 19.7452 9 20.3296 9C20.9913 9 21.6015 9.05161 22.1945 9.15082C22.5898 9.21656 22.8906 9.52637 22.9765 9.91652L23.5136 12.37C24.1538 12.6506 24.8284 13.0202 25.4042 13.4645L27.7976 12.7013C28.1757 12.5802 28.5925 12.6867 28.8503 12.9939C29.1984 13.4172 29.5163 13.8641 29.7999 14.3367L30.0019 14.6848C30.2597 15.1531 30.4874 15.6473 30.6765 16.1586ZM20.3683 23.4375C22.2675 23.4375 23.8058 21.8992 23.8058 19.9613C23.8058 18.1008 22.2675 16.5238 20.3683 16.5238C18.4691 16.5238 16.9308 18.1008 16.9308 19.9613C16.9308 21.8992 18.4691 23.4375 20.3683 23.4375Z" fill="#232323"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -1 +1 @@
|
|||
Subproject commit 51f74f05d465a92e0118cf7c2bcfb049df21af42
|
||||
Subproject commit 277d922c3b1d637c1ccda25f51395c618d293015
|
|
@ -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<MaterialAppWithTheme>
|
|||
late final Completer<void> loadingCompleter;
|
||||
|
||||
bool didLoad = false;
|
||||
bool _desktopHasPassword = false;
|
||||
|
||||
Future<void> load() async {
|
||||
try {
|
||||
|
@ -218,6 +220,11 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
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<MaterialAppWithTheme>
|
|||
builder: (BuildContext context, AsyncSnapshot<void> 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<void>.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(
|
||||
|
|
|
@ -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,426 +96,460 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.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: 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();
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
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<StackColors>()!
|
||||
.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)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
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<StackColors>()!
|
||||
.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);
|
||||
}
|
||||
|
||||
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<StackColors>()!
|
||||
.accentColorRed
|
||||
: passwordStrength < 1
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorYellow
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorGreen,
|
||||
backgroundColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.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(
|
||||
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<StackColors>()!
|
||||
.textDark3,
|
||||
width: 16,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (newValue) {
|
||||
setState(() {});
|
||||
// TODO: ? check if passwords match?
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
style: shouldEnableCreate
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonColor(context)
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.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<dynamic>(
|
||||
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<void>.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<dynamic>(
|
||||
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<dynamic>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => const StackOkDialog(
|
||||
title: "Backup creation failed"),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
"Create backup",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) {
|
||||
return Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.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.all(10.0),
|
||||
child: Text(
|
||||
"Choose file location",
|
||||
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
// child,
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
PrimaryButton(
|
||||
desktopMed: true,
|
||||
width: 200,
|
||||
label: "Create backup",
|
||||
onPressed: () {},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
SecondaryButton(
|
||||
desktopMed: true,
|
||||
width: 200,
|
||||
label: "Cancel",
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
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) {
|
||||
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<StackColors>()!
|
||||
.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)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
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<StackColors>()!
|
||||
.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);
|
||||
}
|
||||
|
||||
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<StackColors>()!
|
||||
.accentColorRed
|
||||
: passwordStrength < 1
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorYellow
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorGreen,
|
||||
backgroundColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.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(
|
||||
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<StackColors>()!
|
||||
.textDark3,
|
||||
width: 16,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (newValue) {
|
||||
setState(() {});
|
||||
// TODO: ? check if passwords match?
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
style: shouldEnableCreate
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonColor(context)
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.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<dynamic>(
|
||||
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<void>.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<dynamic>(
|
||||
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<dynamic>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => const StackOkDialog(
|
||||
title: "Backup creation failed"),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
"Create backup",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -8,6 +8,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 +16,15 @@ 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/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 +45,17 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
|
|||
|
||||
bool hidePassword = true;
|
||||
|
||||
Future<void> restoreBackupPopup(BuildContext context) async {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return const RestoreBackupDialog();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
stackFileSystem = StackFileSystem();
|
||||
|
@ -65,275 +79,322 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.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: 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);
|
||||
}
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
if (mounted) {
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) {
|
||||
return Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.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.all(10.0),
|
||||
child: Text(
|
||||
"Choose file location",
|
||||
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
// child,
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
PrimaryButton(
|
||||
desktopMed: true,
|
||||
width: 200,
|
||||
label: "Restore",
|
||||
onPressed: () {
|
||||
restoreBackupPopup(context);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
SecondaryButton(
|
||||
desktopMed: true,
|
||||
width: 200,
|
||||
label: "Cancel",
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
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) {
|
||||
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<StackColors>()!
|
||||
.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) {},
|
||||
),
|
||||
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(() {
|
||||
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(
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
hidePassword
|
||||
? Assets.svg.eye
|
||||
: Assets.svg.eyeSlash,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
width: 16,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
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<StackColors>()!
|
||||
.getPrimaryDisabledButtonColor(context)
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.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<void>.delayed(
|
||||
const Duration(milliseconds: 75));
|
||||
}
|
||||
|
||||
if (!(await File(fileToRestore).exists())) {
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Backup file does not exist",
|
||||
context: context,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
bool shouldPop = false;
|
||||
showDialog<dynamic>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (_) => WillPopScope(
|
||||
onWillPop: () async {
|
||||
return shouldPop;
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.folder,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
width: 16,
|
||||
height: 16,
|
||||
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(
|
||||
width: 12,
|
||||
height: 64,
|
||||
),
|
||||
const Center(
|
||||
child: LoadingIndicator(
|
||||
width: 100,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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<StackColors>()!
|
||||
.textDark3,
|
||||
width: 16,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
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) {
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Failed to decrypt backup file",
|
||||
context: context,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(context).push(
|
||||
RouteGenerator.getRoute(
|
||||
builder: (_) => StackRestoreProgressView(
|
||||
jsonString: jsonString,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (newValue) {
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
style: passwordController.text.isEmpty ||
|
||||
fileLocationController.text.isEmpty
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryDisabledButtonColor(context)
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.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<void>.delayed(
|
||||
const Duration(milliseconds: 75));
|
||||
}
|
||||
|
||||
if (!(await File(fileToRestore).exists())) {
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Backup file does not exist",
|
||||
context: context,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
bool shouldPop = false;
|
||||
showDialog<dynamic>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
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) {
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Failed to decrypt backup file",
|
||||
context: context,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(context).push(
|
||||
RouteGenerator.getRoute(
|
||||
builder: (_) => StackRestoreProgressView(
|
||||
jsonString: jsonString,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
"Restore",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
"Restore",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String>
|
||||
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<String>
|
||||
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(
|
||||
|
|
|
@ -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<CreatePasswordView> createState() => _CreatePasswordViewState();
|
||||
ConsumerState<CreatePasswordView> createState() => _CreatePasswordViewState();
|
||||
}
|
||||
|
||||
class _CreatePasswordViewState extends State<CreatePasswordView> {
|
||||
class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
||||
late final TextEditingController passwordController;
|
||||
late final TextEditingController passwordRepeatController;
|
||||
|
||||
|
@ -76,8 +78,16 @@ class _CreatePasswordViewState extends State<CreatePasswordView> {
|
|||
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)
|
||||
|
|
47
lib/pages_desktop_specific/desktop_login_view.dart
Normal file
47
lib/pages_desktop_specific/desktop_login_view.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
|
||||
class DesktopLoginView extends StatefulWidget {
|
||||
const DesktopLoginView({
|
||||
Key? key,
|
||||
this.startupWalletId,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/desktopLogin";
|
||||
|
||||
final String? startupWalletId;
|
||||
|
||||
@override
|
||||
State<DesktopLoginView> createState() => _DesktopLoginViewState();
|
||||
}
|
||||
|
||||
class _DesktopLoginViewState extends State<DesktopLoginView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Login",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Login",
|
||||
onPressed: () {
|
||||
// todo auth
|
||||
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
DesktopHomeView.routeName,
|
||||
(route) => false,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<DebugInfoDialog> {
|
|||
],
|
||||
),
|
||||
Expanded(
|
||||
flex: 24,
|
||||
// flex: 24,
|
||||
child: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) {
|
||||
|
@ -314,7 +318,7 @@ class _DebugInfoDialog extends ConsumerState<DebugInfoDialog> {
|
|||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
// const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Row(
|
||||
|
@ -322,7 +326,18 @@ class _DebugInfoDialog extends ConsumerState<DebugInfoDialog> {
|
|||
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(
|
||||
|
|
|
@ -23,6 +23,19 @@ class AppearanceOptionSettings extends ConsumerStatefulWidget {
|
|||
|
||||
class _AppearanceOptionSettings
|
||||
extends ConsumerState<AppearanceOptionSettings> {
|
||||
// late bool isLight;
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
//
|
||||
// super.initState();
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// void dispose() {
|
||||
// super.dispose();
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
@ -127,13 +140,7 @@ class _AppearanceOptionSettings
|
|||
],
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: ThemeToggle(),
|
||||
),
|
||||
ThemeToggle(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -169,95 +176,38 @@ class _ThemeToggle extends State<ThemeToggle> {
|
|||
elevation: 0,
|
||||
hoverColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||
width: 2,
|
||||
),
|
||||
// side: !externalCallsEnabled
|
||||
// ? BorderSide.none
|
||||
// : BorderSide(
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .infoItemIcons,
|
||||
// width: 2,
|
||||
// ),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius * 2,
|
||||
),
|
||||
),
|
||||
onPressed: () {}, //onPressed
|
||||
child: Stack(
|
||||
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)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
// if (externalCallsEnabled)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 6,
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.checkCircle,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemIcons,
|
||||
),
|
||||
),
|
||||
// if (!externalCallsEnabled)
|
||||
// Positioned(
|
||||
// top: 4,
|
||||
// right: 4,
|
||||
// child: Container(
|
||||
// width: 20,
|
||||
// height: 20,
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(1000),
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .textFieldDefaultBG,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 1,
|
||||
),
|
||||
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: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.themeDark,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.themeLight,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
|
@ -265,7 +215,7 @@ class _ThemeToggle extends State<ThemeToggle> {
|
|||
top: 12,
|
||||
),
|
||||
child: Text(
|
||||
"Dark",
|
||||
"Light",
|
||||
style: STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
|
@ -273,13 +223,13 @@ class _ThemeToggle extends State<ThemeToggle> {
|
|||
.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
// if (externalCallsEnabled)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
left: 6,
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.checkCircle,
|
||||
width: 20,
|
||||
|
@ -291,8 +241,8 @@ class _ThemeToggle extends State<ThemeToggle> {
|
|||
),
|
||||
// if (!externalCallsEnabled)
|
||||
// Positioned(
|
||||
// top: 4,
|
||||
// right: 4,
|
||||
// bottom: 0,
|
||||
// left: 6,
|
||||
// child: Container(
|
||||
// width: 20,
|
||||
// height: 20,
|
||||
|
@ -309,6 +259,90 @@ class _ThemeToggle extends State<ThemeToggle> {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 1,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: RawMaterialButton(
|
||||
elevation: 0,
|
||||
hoverColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
// side: !externalCallsEnabled
|
||||
// ? BorderSide.none
|
||||
// : BorderSide(
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .infoItemIcons,
|
||||
// width: 2,
|
||||
// ),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius * 2,
|
||||
),
|
||||
),
|
||||
onPressed: () {}, //onPressed
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.themeDark,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 45,
|
||||
top: 12,
|
||||
),
|
||||
child: Text(
|
||||
"Dark",
|
||||
style: STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// if (externalCallsEnabled)
|
||||
// Positioned(
|
||||
// bottom: 0,
|
||||
// left: 0,
|
||||
// child: SvgPicture.asset(
|
||||
// Assets.svg.checkCircle,
|
||||
// width: 20,
|
||||
// height: 20,
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .infoItemIcons,
|
||||
// ),
|
||||
// ),
|
||||
// if (!externalCallsEnabled)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@ 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: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/enable_backup_dialog.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/restore_backup_dialog.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/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
|
@ -21,290 +22,254 @@ class BackupRestoreSettings extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> {
|
||||
late bool createBackup = false;
|
||||
late bool restoreBackup = false;
|
||||
|
||||
Future<void> enableAutoBackup(BuildContext context) async {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return const EnableBackupDialog();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@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(
|
||||
|
||||
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),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.backupAuto,
|
||||
width: 48,
|
||||
height: 48,
|
||||
),
|
||||
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: 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: EdgeInsets.all(
|
||||
10,
|
||||
),
|
||||
child: PrimaryButton(
|
||||
desktopMed: true,
|
||||
width: 200,
|
||||
label: "Enable auto backup",
|
||||
onPressed: () {
|
||||
enableAutoBackup(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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),
|
||||
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: 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: EdgeInsets.all(
|
||||
10,
|
||||
),
|
||||
child: createBackup
|
||||
? const 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),
|
||||
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: 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: EdgeInsets.all(
|
||||
10,
|
||||
),
|
||||
child: restoreBackup
|
||||
? 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<void> enableAutoBackup() async {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return const EnableBackupDialog();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: 200,
|
||||
height: 48,
|
||||
child: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.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<StackColors>()!
|
||||
.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<void> restoreBackup() async {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return const RestoreBackupDialog();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: 200,
|
||||
height: 48,
|
||||
child: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonColor(context),
|
||||
onPressed: () {
|
||||
restoreBackup();
|
||||
},
|
||||
child: Text(
|
||||
"Restore",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
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/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/rounded_white_container.dart';
|
||||
|
||||
class RestoreBackupDialog extends StatelessWidget {
|
||||
const RestoreBackupDialog({Key? key}) : super(key: key);
|
||||
|
@ -12,82 +14,158 @@ class RestoreBackupDialog extends StatelessWidget {
|
|||
@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,
|
||||
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: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Settings",
|
||||
style: STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 32, vertical: 12),
|
||||
child: RoundedWhiteContainer(
|
||||
borderColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.background,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.framedAddressBook,
|
||||
width: 40,
|
||||
height: 40,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
"Address Book",
|
||||
style:
|
||||
STextStyles.desktopTextSmall(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
///TODO: CHECKMARK ANIMATION
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 32, vertical: 12),
|
||||
child: RoundedWhiteContainer(
|
||||
borderColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.background,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.framedGear,
|
||||
width: 40,
|
||||
height: 40,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
"Preferences",
|
||||
style:
|
||||
STextStyles.desktopTextSmall(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
///TODO: CHECKMARK ANIMATION
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Wallets",
|
||||
style: STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
SecondaryButton(
|
||||
desktopMed: true,
|
||||
width: 200,
|
||||
label: "Cancel restore process",
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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<StackColors>()!.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();
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/utilities/desktop_password_service.dart';
|
||||
|
||||
final storageCryptoHandlerProvider = Provider<DPS>((ref) => DPS());
|
|
@ -6,7 +6,7 @@ import 'dart:typed_data';
|
|||
import 'package:bech32/bech32.dart';
|
||||
import 'package:bip32/bip32.dart' as bip32;
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:bitbox/bitbox.dart' as Bitbox;
|
||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||
import 'package:bitcoindart/bitcoindart.dart';
|
||||
import 'package:bs58check/bs58check.dart' as bs58check;
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
@ -258,7 +258,7 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
}
|
||||
|
||||
Future<void> updateStoredChainHeight({required int newHeight}) async {
|
||||
DB.instance.put<dynamic>(
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: walletId, key: "storedChainHeight", value: newHeight);
|
||||
}
|
||||
|
||||
|
@ -266,8 +266,9 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
Uint8List? decodeBase58;
|
||||
Segwit? decodeBech32;
|
||||
try {
|
||||
if (Bitbox.Address.detectFormat(address) == 0) {
|
||||
address = Bitbox.Address.toLegacyAddress(address);
|
||||
if (bitbox.Address.detectFormat(address) ==
|
||||
bitbox.Address.formatCashAddr) {
|
||||
address = bitbox.Address.toLegacyAddress(address);
|
||||
}
|
||||
} catch (e, s) {}
|
||||
try {
|
||||
|
@ -609,7 +610,9 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
|
||||
// get address tx counts
|
||||
final counts = await _getBatchTxCount(addresses: txCountCallArgs);
|
||||
print("Counts $counts");
|
||||
if (kDebugMode) {
|
||||
print("Counts $counts");
|
||||
}
|
||||
// check and add appropriate addresses
|
||||
for (int k = 0; k < txCountBatchSize; k++) {
|
||||
int count = counts["${_id}_$k"]!;
|
||||
|
@ -745,31 +748,35 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
// notify on new incoming transaction
|
||||
for (final tx in unconfirmedTxnsToNotifyPending) {
|
||||
if (tx.txType == "Received") {
|
||||
NotificationApi.showNotification(
|
||||
title: "Incoming transaction",
|
||||
body: walletName,
|
||||
walletId: walletId,
|
||||
iconAssetName: Assets.svg.iconFor(coin: coin),
|
||||
date: DateTime.now(),
|
||||
shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS,
|
||||
coinName: coin.name,
|
||||
txid: tx.txid,
|
||||
confirmations: tx.confirmations,
|
||||
requiredConfirmations: MINIMUM_CONFIRMATIONS,
|
||||
unawaited(
|
||||
NotificationApi.showNotification(
|
||||
title: "Incoming transaction",
|
||||
body: walletName,
|
||||
walletId: walletId,
|
||||
iconAssetName: Assets.svg.iconFor(coin: coin),
|
||||
date: DateTime.now(),
|
||||
shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS,
|
||||
coinName: coin.name,
|
||||
txid: tx.txid,
|
||||
confirmations: tx.confirmations,
|
||||
requiredConfirmations: MINIMUM_CONFIRMATIONS,
|
||||
),
|
||||
);
|
||||
await txTracker.addNotifiedPending(tx.txid);
|
||||
} else if (tx.txType == "Sent") {
|
||||
NotificationApi.showNotification(
|
||||
title: "Sending transaction",
|
||||
body: walletName,
|
||||
walletId: walletId,
|
||||
iconAssetName: Assets.svg.iconFor(coin: coin),
|
||||
date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
|
||||
shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS,
|
||||
coinName: coin.name,
|
||||
txid: tx.txid,
|
||||
confirmations: tx.confirmations,
|
||||
requiredConfirmations: MINIMUM_CONFIRMATIONS,
|
||||
unawaited(
|
||||
NotificationApi.showNotification(
|
||||
title: "Sending transaction",
|
||||
body: walletName,
|
||||
walletId: walletId,
|
||||
iconAssetName: Assets.svg.iconFor(coin: coin),
|
||||
date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
|
||||
shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS,
|
||||
coinName: coin.name,
|
||||
txid: tx.txid,
|
||||
confirmations: tx.confirmations,
|
||||
requiredConfirmations: MINIMUM_CONFIRMATIONS,
|
||||
),
|
||||
);
|
||||
await txTracker.addNotifiedPending(tx.txid);
|
||||
}
|
||||
|
@ -778,26 +785,30 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
// notify on confirmed
|
||||
for (final tx in unconfirmedTxnsToNotifyConfirmed) {
|
||||
if (tx.txType == "Received") {
|
||||
NotificationApi.showNotification(
|
||||
title: "Incoming transaction confirmed",
|
||||
body: walletName,
|
||||
walletId: walletId,
|
||||
iconAssetName: Assets.svg.iconFor(coin: coin),
|
||||
date: DateTime.now(),
|
||||
shouldWatchForUpdates: false,
|
||||
coinName: coin.name,
|
||||
unawaited(
|
||||
NotificationApi.showNotification(
|
||||
title: "Incoming transaction confirmed",
|
||||
body: walletName,
|
||||
walletId: walletId,
|
||||
iconAssetName: Assets.svg.iconFor(coin: coin),
|
||||
date: DateTime.now(),
|
||||
shouldWatchForUpdates: false,
|
||||
coinName: coin.name,
|
||||
),
|
||||
);
|
||||
|
||||
await txTracker.addNotifiedConfirmed(tx.txid);
|
||||
} else if (tx.txType == "Sent") {
|
||||
NotificationApi.showNotification(
|
||||
title: "Outgoing transaction confirmed",
|
||||
body: walletName,
|
||||
walletId: walletId,
|
||||
iconAssetName: Assets.svg.iconFor(coin: coin),
|
||||
date: DateTime.now(),
|
||||
shouldWatchForUpdates: false,
|
||||
coinName: coin.name,
|
||||
unawaited(
|
||||
NotificationApi.showNotification(
|
||||
title: "Outgoing transaction confirmed",
|
||||
body: walletName,
|
||||
walletId: walletId,
|
||||
iconAssetName: Assets.svg.iconFor(coin: coin),
|
||||
date: DateTime.now(),
|
||||
shouldWatchForUpdates: false,
|
||||
coinName: coin.name,
|
||||
),
|
||||
);
|
||||
await txTracker.addNotifiedConfirmed(tx.txid);
|
||||
}
|
||||
|
@ -862,7 +873,7 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
if (currentHeight != storedHeight) {
|
||||
if (currentHeight != -1) {
|
||||
// -1 failed to fetch current height
|
||||
updateStoredChainHeight(newHeight: currentHeight);
|
||||
await updateStoredChainHeight(newHeight: currentHeight);
|
||||
}
|
||||
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
|
||||
|
@ -1147,10 +1158,26 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
bool validateAddress(String address) {
|
||||
try {
|
||||
// 0 for bitcoincash: address scheme, 1 for legacy address
|
||||
final format = Bitbox.Address.detectFormat(address);
|
||||
print("format $format");
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
final format = bitbox.Address.detectFormat(address);
|
||||
if (kDebugMode) {
|
||||
print("format $format");
|
||||
}
|
||||
|
||||
if (_coin == Coin.bitcoincashTestnet) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (format == bitbox.Address.formatCashAddr) {
|
||||
String addr = address;
|
||||
if (address.contains(":")) {
|
||||
addr = address.split(":").last;
|
||||
}
|
||||
|
||||
return addr.startsWith("q");
|
||||
} else {
|
||||
return address.startsWith("1");
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1226,7 +1253,7 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
);
|
||||
|
||||
if (shouldRefresh) {
|
||||
refresh();
|
||||
unawaited(refresh());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1522,12 +1549,15 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
break;
|
||||
}
|
||||
|
||||
print("Array key is ${jsonEncode(arrayKey)}");
|
||||
if (kDebugMode) {
|
||||
print("Array key is ${jsonEncode(arrayKey)}");
|
||||
}
|
||||
final internalChainArray =
|
||||
DB.instance.get<dynamic>(boxName: walletId, key: arrayKey);
|
||||
if (derivePathType == DerivePathType.bip44) {
|
||||
if (Bitbox.Address.detectFormat(internalChainArray.last as String) == 1) {
|
||||
return Bitbox.Address.toCashAddress(internalChainArray.last as String);
|
||||
if (bitbox.Address.detectFormat(internalChainArray.last as String) ==
|
||||
bitbox.Address.formatLegacy) {
|
||||
return bitbox.Address.toCashAddress(internalChainArray.last as String);
|
||||
}
|
||||
}
|
||||
return internalChainArray.last as String;
|
||||
|
@ -1642,7 +1672,9 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
batches[batchNumber] = {};
|
||||
}
|
||||
final scripthash = _convertToScriptHash(allAddresses[i], _network);
|
||||
print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash");
|
||||
if (kDebugMode) {
|
||||
print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash");
|
||||
}
|
||||
batches[batchNumber]!.addAll({
|
||||
scripthash: [scripthash]
|
||||
});
|
||||
|
@ -1818,20 +1850,28 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
}) async {
|
||||
try {
|
||||
final Map<String, List<dynamic>> args = {};
|
||||
print("Address $addresses");
|
||||
if (kDebugMode) {
|
||||
print("Address $addresses");
|
||||
}
|
||||
for (final entry in addresses.entries) {
|
||||
args[entry.key] = [_convertToScriptHash(entry.value, _network)];
|
||||
}
|
||||
|
||||
print("Args ${jsonEncode(args)}");
|
||||
if (kDebugMode) {
|
||||
print("Args ${jsonEncode(args)}");
|
||||
}
|
||||
|
||||
final response = await electrumXClient.getBatchHistory(args: args);
|
||||
print("Response ${jsonEncode(response)}");
|
||||
if (kDebugMode) {
|
||||
print("Response ${jsonEncode(response)}");
|
||||
}
|
||||
final Map<String, int> result = {};
|
||||
for (final entry in response.entries) {
|
||||
result[entry.key] = entry.value.length;
|
||||
}
|
||||
print("result ${jsonEncode(result)}");
|
||||
if (kDebugMode) {
|
||||
print("result ${jsonEncode(result)}");
|
||||
}
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
|
@ -1995,8 +2035,9 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
/// Returns the scripthash or throws an exception on invalid bch address
|
||||
String _convertToScriptHash(String bchAddress, NetworkType network) {
|
||||
try {
|
||||
if (Bitbox.Address.detectFormat(bchAddress) == 0) {
|
||||
bchAddress = Bitbox.Address.toLegacyAddress(bchAddress);
|
||||
if (bitbox.Address.detectFormat(bchAddress) ==
|
||||
bitbox.Address.formatCashAddr) {
|
||||
bchAddress = bitbox.Address.toLegacyAddress(bchAddress);
|
||||
}
|
||||
final output = Address.addressToOutputScript(bchAddress, network);
|
||||
final hash = sha256.convert(output.toList(growable: false)).toString();
|
||||
|
@ -2073,8 +2114,8 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
List<String> allAddressesOld = await _fetchAllOwnAddresses();
|
||||
List<String> allAddresses = [];
|
||||
for (String address in allAddressesOld) {
|
||||
if (Bitbox.Address.detectFormat(address) == 1) {
|
||||
allAddresses.add(Bitbox.Address.toCashAddress(address));
|
||||
if (bitbox.Address.detectFormat(address) == bitbox.Address.formatLegacy) {
|
||||
allAddresses.add(bitbox.Address.toCashAddress(address));
|
||||
} else {
|
||||
allAddresses.add(address);
|
||||
}
|
||||
|
@ -2085,8 +2126,9 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
as List<dynamic>;
|
||||
List<dynamic> changeAddressesP2PKH = [];
|
||||
for (var address in changeAddressesP2PKHOld) {
|
||||
if (Bitbox.Address.detectFormat(address as String) == 1) {
|
||||
changeAddressesP2PKH.add(Bitbox.Address.toCashAddress(address));
|
||||
if (bitbox.Address.detectFormat(address as String) ==
|
||||
bitbox.Address.formatLegacy) {
|
||||
changeAddressesP2PKH.add(bitbox.Address.toCashAddress(address));
|
||||
} else {
|
||||
changeAddressesP2PKH.add(address);
|
||||
}
|
||||
|
@ -2108,21 +2150,27 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
unconfirmedCachedTransactions
|
||||
.removeWhere((key, value) => value.confirmedStatus);
|
||||
|
||||
print("CACHED_TRANSACTIONS_IS $cachedTransactions");
|
||||
if (kDebugMode) {
|
||||
print("CACHED_TRANSACTIONS_IS $cachedTransactions");
|
||||
}
|
||||
if (cachedTransactions != null) {
|
||||
for (final tx in allTxHashes.toList(growable: false)) {
|
||||
final txHeight = tx["height"] as int;
|
||||
if (txHeight > 0 &&
|
||||
txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) {
|
||||
if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) {
|
||||
print(cachedTransactions.findTransaction(tx["tx_hash"] as String));
|
||||
print(unconfirmedCachedTransactions[tx["tx_hash"] as String]);
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
cachedTransactions.findTransaction(tx["tx_hash"] as String));
|
||||
print(unconfirmedCachedTransactions[tx["tx_hash"] as String]);
|
||||
}
|
||||
final cachedTx =
|
||||
cachedTransactions.findTransaction(tx["tx_hash"] as String);
|
||||
if (!(cachedTx != null &&
|
||||
addressType(address: cachedTx.address) ==
|
||||
DerivePathType.bip44 &&
|
||||
Bitbox.Address.detectFormat(cachedTx.address) == 1)) {
|
||||
bitbox.Address.detectFormat(cachedTx.address) ==
|
||||
bitbox.Address.formatLegacy)) {
|
||||
allTxHashes.remove(tx);
|
||||
}
|
||||
}
|
||||
|
@ -2782,8 +2830,9 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
final n = output["n"];
|
||||
if (n != null && n == utxosToUse[i].vout) {
|
||||
String address = output["scriptPubKey"]["addresses"][0] as String;
|
||||
if (Bitbox.Address.detectFormat(address) == 0) {
|
||||
address = Bitbox.Address.toLegacyAddress(address);
|
||||
if (bitbox.Address.detectFormat(address) ==
|
||||
bitbox.Address.formatCashAddr) {
|
||||
address = bitbox.Address.toLegacyAddress(address);
|
||||
}
|
||||
if (!addressTxid.containsKey(address)) {
|
||||
addressTxid[address] = <String>[];
|
||||
|
@ -2814,8 +2863,9 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
);
|
||||
for (int i = 0; i < p2pkhLength; i++) {
|
||||
String address = addressesP2PKH[i];
|
||||
if (Bitbox.Address.detectFormat(address) == 0) {
|
||||
address = Bitbox.Address.toLegacyAddress(address);
|
||||
if (bitbox.Address.detectFormat(address) ==
|
||||
bitbox.Address.formatCashAddr) {
|
||||
address = bitbox.Address.toLegacyAddress(address);
|
||||
}
|
||||
|
||||
// receives
|
||||
|
@ -2950,36 +3000,36 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
required List<String> recipients,
|
||||
required List<int> satoshiAmounts,
|
||||
}) async {
|
||||
final builder = Bitbox.Bitbox.transactionBuilder();
|
||||
final builder = bitbox.Bitbox.transactionBuilder();
|
||||
|
||||
// retrieve address' utxos from the rest api
|
||||
List<Bitbox.Utxo> _utxos =
|
||||
List<bitbox.Utxo> _utxos =
|
||||
[]; // await Bitbox.Address.utxo(address) as List<Bitbox.Utxo>;
|
||||
utxosToUse.forEach((element) {
|
||||
_utxos.add(Bitbox.Utxo(
|
||||
for (var element in utxosToUse) {
|
||||
_utxos.add(bitbox.Utxo(
|
||||
element.txid,
|
||||
element.vout,
|
||||
Bitbox.BitcoinCash.fromSatoshi(element.value),
|
||||
bitbox.BitcoinCash.fromSatoshi(element.value),
|
||||
element.value,
|
||||
0,
|
||||
MINIMUM_CONFIRMATIONS + 1));
|
||||
});
|
||||
Logger.print("bch utxos: ${_utxos}");
|
||||
}
|
||||
Logger.print("bch utxos: $_utxos");
|
||||
|
||||
// placeholder for input signatures
|
||||
final signatures = <Map>[];
|
||||
final List<Map<dynamic, dynamic>> signatures = [];
|
||||
|
||||
// placeholder for total input balance
|
||||
int totalBalance = 0;
|
||||
// int totalBalance = 0;
|
||||
|
||||
// iterate through the list of address _utxos and use them as inputs for the
|
||||
// withdrawal transaction
|
||||
_utxos.forEach((Bitbox.Utxo utxo) {
|
||||
for (var utxo in _utxos) {
|
||||
// add the utxo as an input for the transaction
|
||||
builder.addInput(utxo.txid, utxo.vout);
|
||||
final ec = utxoSigningData[utxo.txid]["keyPair"] as ECPair;
|
||||
|
||||
final bitboxEC = Bitbox.ECPair.fromWIF(ec.toWIF());
|
||||
final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF());
|
||||
|
||||
// add a signature to the list to be used later
|
||||
signatures.add({
|
||||
|
@ -2988,15 +3038,15 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
"original_amount": utxo.satoshis
|
||||
});
|
||||
|
||||
totalBalance += utxo.satoshis;
|
||||
});
|
||||
// totalBalance += utxo.satoshis;
|
||||
}
|
||||
|
||||
// calculate the fee based on number of inputs and one expected output
|
||||
final fee =
|
||||
Bitbox.BitcoinCash.getByteCount(signatures.length, recipients.length);
|
||||
// final fee =
|
||||
// bitbox.BitcoinCash.getByteCount(signatures.length, recipients.length);
|
||||
|
||||
// calculate how much balance will be left over to spend after the fee
|
||||
final sendAmount = totalBalance - fee;
|
||||
// final sendAmount = totalBalance - fee;
|
||||
|
||||
// add the output based on the address provided in the testing data
|
||||
for (int i = 0; i < recipients.length; i++) {
|
||||
|
@ -3006,12 +3056,12 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
}
|
||||
|
||||
// sign all inputs
|
||||
signatures.forEach((signature) {
|
||||
for (var signature in signatures) {
|
||||
builder.sign(
|
||||
signature["vin"] as int,
|
||||
signature["key_pair"] as Bitbox.ECPair,
|
||||
signature["key_pair"] as bitbox.ECPair,
|
||||
signature["original_amount"] as int);
|
||||
});
|
||||
}
|
||||
|
||||
// build the transaction
|
||||
final tx = builder.build();
|
||||
|
@ -3038,7 +3088,7 @@ class BitcoinCashWallet extends CoinServiceAPI {
|
|||
);
|
||||
|
||||
// clear cache
|
||||
_cachedElectrumXClient.clearSharedTransactionCache(coin: coin);
|
||||
await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin);
|
||||
|
||||
// back up data
|
||||
await _rescanBackup();
|
||||
|
|
|
@ -59,6 +59,8 @@ class _SVG {
|
|||
String txExchangeFailed(BuildContext context) =>
|
||||
"assets/svg/${Theme.of(context).extension<StackColors>()!.themeType.name}/tx-exchange-icon-failed.svg";
|
||||
|
||||
String get framedGear => "assets/svg/framed-gear.svg";
|
||||
String get framedAddressBook => "assets/svg/framed-address-book.svg";
|
||||
String get themeLight => "assets/svg/light/light-mode.svg";
|
||||
String get themeDark => "assets/svg/dark/dark-theme.svg";
|
||||
String get circleNode => "assets/svg/node-circle.svg";
|
||||
|
|
89
lib/utilities/desktop_password_service.dart
Normal file
89
lib/utilities/desktop_password_service.dart
Normal file
|
@ -0,0 +1,89 @@
|
|||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:stack_wallet_backup/secure_storage.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
const String _kKeyBlobKey = "swbKeyBlobKeyStringID";
|
||||
|
||||
String _getMessageFromException(Object exception) {
|
||||
if (exception is IncorrectPassphrase) {
|
||||
return exception.errMsg();
|
||||
}
|
||||
if (exception is BadDecryption) {
|
||||
return exception.errMsg();
|
||||
}
|
||||
if (exception is InvalidLength) {
|
||||
return exception.errMsg();
|
||||
}
|
||||
if (exception is EncodingError) {
|
||||
return exception.errMsg();
|
||||
}
|
||||
|
||||
return exception.toString();
|
||||
}
|
||||
|
||||
class DPS {
|
||||
StorageCryptoHandler? _handler;
|
||||
final SecureStorageWrapper secureStorageWrapper;
|
||||
|
||||
StorageCryptoHandler get handler {
|
||||
if (_handler == null) {
|
||||
throw Exception(
|
||||
"DPS: attempted to access handler without proper authentication");
|
||||
}
|
||||
return _handler!;
|
||||
}
|
||||
|
||||
DPS({
|
||||
this.secureStorageWrapper = const SecureStorageWrapper(
|
||||
FlutterSecureStorage(),
|
||||
),
|
||||
});
|
||||
|
||||
Future<void> initFromNew(String passphrase) async {
|
||||
if (_handler != null) {
|
||||
throw Exception("DPS: attempted to re initialize with new passphrase");
|
||||
}
|
||||
|
||||
try {
|
||||
_handler = await StorageCryptoHandler.fromNewPassphrase(passphrase);
|
||||
await secureStorageWrapper.write(
|
||||
key: _kKeyBlobKey,
|
||||
value: await _handler!.getKeyBlob(),
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"${_getMessageFromException(e)}\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initFromExisting(String passphrase) async {
|
||||
if (_handler != null) {
|
||||
throw Exception(
|
||||
"DPS: attempted to re initialize with existing passphrase");
|
||||
}
|
||||
final keyBlob = await secureStorageWrapper.read(key: _kKeyBlobKey);
|
||||
|
||||
if (keyBlob == null) {
|
||||
throw Exception(
|
||||
"DPS: failed to find keyBlob while attempting to initialize with existing passphrase");
|
||||
}
|
||||
|
||||
try {
|
||||
_handler = await StorageCryptoHandler.fromExisting(passphrase, keyBlob);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"${_getMessageFromException(e)}\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> hasPassword() async {
|
||||
return (await secureStorageWrapper.read(key: _kKeyBlobKey)) != null;
|
||||
}
|
||||
}
|
|
@ -1378,8 +1378,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: b7b184ec36466f2a24104a7056de88881cb0c1e9
|
||||
resolved-ref: b7b184ec36466f2a24104a7056de88881cb0c1e9
|
||||
ref: "011dc9ce3d29f5fdeeaf711d58b5122f055c146d"
|
||||
resolved-ref: "011dc9ce3d29f5fdeeaf711d58b5122f055c146d"
|
||||
url: "https://github.com/cypherstack/stack_wallet_backup.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
|
|
|
@ -54,7 +54,7 @@ dependencies:
|
|||
stack_wallet_backup:
|
||||
git:
|
||||
url: https://github.com/cypherstack/stack_wallet_backup.git
|
||||
ref: b7b184ec36466f2a24104a7056de88881cb0c1e9
|
||||
ref: 011dc9ce3d29f5fdeeaf711d58b5122f055c146d
|
||||
|
||||
# Utility plugins
|
||||
# provider: ^6.0.1
|
||||
|
@ -338,6 +338,8 @@ flutter:
|
|||
- assets/svg/message-question-1.svg
|
||||
- assets/svg/drd-icon.svg
|
||||
- assets/svg/box-auto.svg
|
||||
- assets/svg/framed-address-book.svg
|
||||
- assets/svg/framed-gear.svg
|
||||
# exchange icons
|
||||
- assets/svg/exchange_icons/change_now_logo_1.svg
|
||||
- assets/svg/exchange_icons/simpleswap-icon.svg
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:bitcoindart/bitcoindart.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
@ -140,7 +139,7 @@ void main() {
|
|||
test("invalid mainnet bitcoincash legacy/p2pkh address", () {
|
||||
expect(
|
||||
mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"),
|
||||
true);
|
||||
false);
|
||||
expect(secureStore?.interactions, 0);
|
||||
verifyNoMoreInteractions(client);
|
||||
verifyNoMoreInteractions(cachedClient);
|
||||
|
|
Loading…
Reference in a new issue