xpriv gui clean up

This commit is contained in:
julian 2024-07-04 09:31:41 -06:00
parent c4a765086d
commit afab9e5918
8 changed files with 380 additions and 215 deletions

View file

@ -36,6 +36,7 @@ import '../../../../widgets/rounded_white_container.dart';
import '../../../../widgets/stack_dialog.dart';
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
import '../../../wallet_view/transaction_views/transaction_details_view.dart';
import 'wallet_xprivs.dart';
class WalletBackupView extends ConsumerWidget {
const WalletBackupView({
@ -96,35 +97,6 @@ class WalletBackupView extends ConsumerWidget {
);
},
),
// child: AspectRatio(
// aspectRatio: 1,
// child: AppBarIconButton(
// color:
// Theme.of(context).extension<StackColors>()!.background,
// shadows: const [],
// icon: SvgPicture.asset(
// Assets.svg.copy,
// width: 20,
// height: 20,
// color: Theme.of(context)
// .extension<StackColors>()!
// .topNavIconPrimary,
// ),
// onPressed: () async {
// await clipboardInterface
// .setData(ClipboardData(text: mnemonic.join(" ")));
// unawaited(
// showFloatingFlushBar(
// type: FlushBarType.info,
// message: "Copied to clipboard",
// iconAsset: Assets.svg.copy,
// context: context,
// ),
// );
// },
// ),
// ),
),
if (!frost && xprivData == null)
Padding(
@ -146,14 +118,16 @@ class WalletBackupView extends ConsumerWidget {
onPressed: () async {
await clipboardInterface
.setData(ClipboardData(text: mnemonic.join(" ")));
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
),
);
if (context.mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
),
);
}
},
),
),
@ -177,45 +151,6 @@ class WalletBackupView extends ConsumerWidget {
}
}
class _XPrivs extends StatelessWidget {
const _XPrivs({super.key, required this.walletId, required this.xprivData});
final String walletId;
final ({List<XPriv> xprivs, String fingerprint}) xprivData;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
DetailItem(
title: "Master fingerprint",
detail: xprivData.fingerprint,
horizontal: true,
),
const SizedBox(
height: 16,
),
...xprivData.xprivs.map(
(e) => Padding(
padding: const EdgeInsets.only(
bottom: 16,
),
child: Column(
children: [
DetailItem(
title: e.path,
detail: e.xpriv,
),
],
),
),
),
],
);
}
}
class _Mnemonic extends ConsumerWidget {
const _Mnemonic({super.key, required this.walletId, required this.mnemonic});
@ -514,25 +449,46 @@ class MobileXPrivsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"Wallet xpriv(s)",
style: STextStyles.navBarTitle(context),
),
),
title: Text(
"Wallet xpriv(s)",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SingleChildScrollView(
child: _XPrivs(
walletId: walletId,
xprivData: xprivData,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: LayoutBuilder(
builder: (context, constraints) => SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: WalletXPrivs(
walletId: walletId,
xprivData: xprivData,
),
),
const SizedBox(
height: 16,
),
],
),
),
),
),
),
),
),
),

View file

@ -0,0 +1,221 @@
/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
import 'dart:async';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import '../../../../notifications/show_flush_bar.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/assets.dart';
import '../../../../utilities/clipboard_interface.dart';
import '../../../../utilities/constants.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/detail_item.dart';
import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart';
class WalletXPrivs extends ConsumerStatefulWidget {
const WalletXPrivs({
super.key,
required this.xprivData,
required this.walletId,
this.clipboardInterface = const ClipboardWrapper(),
});
final ({List<XPriv> xprivs, String fingerprint}) xprivData;
final String walletId;
final ClipboardInterface clipboardInterface;
@override
ConsumerState<WalletXPrivs> createState() => WalletXPrivsState();
}
class WalletXPrivsState extends ConsumerState<WalletXPrivs> {
late String _currentDropDownValue;
String _current(String key) =>
widget.xprivData.xprivs.firstWhere((e) => e.path == key).xpriv;
Future<void> _copy() async {
await widget.clipboardInterface.setData(
ClipboardData(text: _current(_currentDropDownValue)),
);
if (mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
),
);
}
}
@override
void initState() {
_currentDropDownValue = widget.xprivData.xprivs.first.path;
super.initState();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: Util.isDesktop
? const EdgeInsets.symmetric(horizontal: 20)
: EdgeInsets.zero,
child: Column(
mainAxisSize: Util.isDesktop ? MainAxisSize.min : MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: Util.isDesktop ? 12 : 16,
),
DetailItem(
title: "Master fingerprint",
detail: widget.xprivData.fingerprint,
horizontal: true,
borderColor: Util.isDesktop
? Theme.of(context).extension<StackColors>()!.textFieldDefaultBG
: null,
),
SizedBox(
height: Util.isDesktop ? 12 : 16,
),
DetailItemBase(
horizontal: true,
borderColor: Util.isDesktop
? Theme.of(context).extension<StackColors>()!.textFieldDefaultBG
: null,
title: Text(
"Derivation",
style: STextStyles.itemSubtitle(context),
),
detail: SizedBox(
width: Util.isDesktop ? 200 : 170,
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
value: _currentDropDownValue,
items: [
...widget.xprivData.xprivs.map(
(e) => DropdownMenuItem(
value: e.path,
child: Text(
e.path,
style: STextStyles.w500_14(context),
),
),
),
],
onChanged: (value) {
if (value is String) {
setState(() {
_currentDropDownValue = value;
});
}
},
isExpanded: true,
buttonStyleData: ButtonStyleData(
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
iconStyleData: IconStyleData(
icon: Padding(
padding: const EdgeInsets.only(right: 10),
child: SvgPicture.asset(
Assets.svg.chevronDown,
width: 12,
height: 6,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
),
),
dropdownStyleData: DropdownStyleData(
offset: const Offset(0, -10),
elevation: 0,
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
menuItemStyleData: const MenuItemStyleData(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
),
),
),
),
),
SizedBox(
height: Util.isDesktop ? 12 : 16,
),
QR(
data: _current(_currentDropDownValue),
size:
Util.isDesktop ? 256 : MediaQuery.of(context).size.width / 1.5,
),
SizedBox(
height: Util.isDesktop ? 12 : 16,
),
RoundedWhiteContainer(
borderColor: Util.isDesktop
? Theme.of(context).extension<StackColors>()!.textFieldDefaultBG
: null,
child: SelectableText(
_current(_currentDropDownValue),
style: STextStyles.w500_14(context),
),
),
SizedBox(
height: Util.isDesktop ? 12 : 16,
),
if (!Util.isDesktop) const Spacer(),
Row(
children: [
if (Util.isDesktop) const Spacer(),
if (Util.isDesktop)
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Copy",
onPressed: _copy,
),
),
],
),
],
),
);
}
}

View file

@ -22,6 +22,7 @@ import '../../../../utilities/assets.dart';
import '../../../../utilities/constants.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
@ -84,6 +85,7 @@ class _UnlockWalletKeysDesktopState
final wallet = ref.read(pWallets).getWallet(widget.walletId);
({String keys, String config})? frostData;
List<String>? words;
({List<XPriv> xprivs, String fingerprint})? xprivData;
// TODO: [prio=low] handle wallets that don't have a mnemonic
// All wallets currently are mnemonic based
@ -100,6 +102,10 @@ class _UnlockWalletKeysDesktopState
words = await wallet.getMnemonicAsWords();
}
if (wallet is ExtendedKeysInterface) {
xprivData = await wallet.getXPrivs();
}
if (mounted) {
await Navigator.of(context).pushReplacementNamed(
WalletKeysDesktopPopup.routeName,
@ -107,6 +113,7 @@ class _UnlockWalletKeysDesktopState
mnemonic: words ?? [],
walletId: widget.walletId,
frostData: frostData,
xprivData: xprivData,
),
);
}
@ -320,6 +327,10 @@ class _UnlockWalletKeysDesktopState
({String keys, String config})? frostData;
List<String>? words;
({
List<XPriv> xprivs,
String fingerprint
})? xprivData;
final wallet =
ref.read(pWallets).getWallet(widget.walletId);
@ -339,6 +350,10 @@ class _UnlockWalletKeysDesktopState
words = await wallet.getMnemonicAsWords();
}
if (wallet is ExtendedKeysInterface) {
xprivData = await wallet.getXPrivs();
}
if (mounted) {
await Navigator.of(context)
.pushReplacementNamed(
@ -347,6 +362,7 @@ class _UnlockWalletKeysDesktopState
mnemonic: words ?? [],
walletId: widget.walletId,
frostData: frostData,
xprivData: xprivData,
),
);
}

View file

@ -16,8 +16,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../notifications/show_flush_bar.dart';
import '../../../../pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
import '../../../../pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart';
import '../../../../pages/wallet_view/transaction_views/transaction_details_view.dart';
import '../../../../providers/providers.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/address_utils.dart';
import '../../../../utilities/assets.dart';
@ -29,8 +29,6 @@ import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/desktop/secondary_button.dart';
import '../../../../widgets/detail_item.dart';
import '../../../../widgets/loading_indicator.dart';
import '../../../../widgets/rounded_white_container.dart';
import 'qr_code_desktop_popup_content.dart';
@ -41,12 +39,14 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
required this.walletId,
this.frostData,
this.clipboardInterface = const ClipboardWrapper(),
this.xprivData,
});
final List<String> words;
final String walletId;
final ({String keys, String config})? frostData;
final ClipboardInterface clipboardInterface;
final ({List<XPriv> xprivs, String fingerprint})? xprivData;
static const String routeName = "walletKeysDesktopPopup";
@ -77,7 +77,7 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
],
),
const SizedBox(
height: 28,
height: 6,
),
frostData != null
? Column(
@ -176,8 +176,7 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
),
],
)
: (ref.watch(pWallets).getWallet(walletId)
is ExtendedKeysInterface)
: xprivData != null
? CustomTabView(
titles: const ["Mnemonic", "XPriv(s)"],
children: [
@ -187,8 +186,8 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
words: words,
),
),
_MasterSeedPrivateKey(
words: words,
WalletXPrivs(
xprivData: xprivData!,
walletId: walletId,
),
],
@ -306,91 +305,3 @@ class _Mnemonic extends StatelessWidget {
);
}
}
class _MasterSeedPrivateKey extends ConsumerStatefulWidget {
const _MasterSeedPrivateKey({
super.key,
required this.words,
required this.walletId,
this.clipboardInterface = const ClipboardWrapper(),
});
final List<String> words;
final String walletId;
final ClipboardInterface clipboardInterface;
@override
ConsumerState<_MasterSeedPrivateKey> createState() =>
_MasterSeedPrivateKeyState();
}
class _MasterSeedPrivateKeyState extends ConsumerState<_MasterSeedPrivateKey> {
final controller = TextEditingController();
@override
void initState() {
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
const SizedBox(
height: 12,
),
FutureBuilder(
future: (ref.read(pWallets).getWallet(widget.walletId)
as ExtendedKeysInterface)
.getXPrivs(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
DetailItem(
title: "Master fingerprint",
detail: snapshot.data!.fingerprint,
),
...snapshot.data!.xprivs.map(
(e) => Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
DetailItem(
title: "Derivation",
detail: e.path,
),
DetailItem(
title: "xpriv",
detail: e.xpriv,
),
],
),
),
),
],
);
} else {
return const LoadingIndicator(
width: 100,
height: 100,
);
}
},
),
],
),
);
}
}

View file

@ -2365,15 +2365,38 @@ class RouteGenerator {
name: settings.name,
),
);
// return getRoute(
// shouldUseMaterialRoute: useMaterialPageRoute,
// builder: (_) => WalletKeysDesktopPopup(
// words: args,
// ),
// settings: RouteSettings(
// name: settings.name,
// ),
// );
} else if (args is ({
List<String> mnemonic,
String walletId,
({String keys, String config})? frostData,
({List<XPriv> xprivs, String fingerprint})? xprivData,
})) {
return FadePageRoute(
WalletKeysDesktopPopup(
words: args.mnemonic,
walletId: args.walletId,
frostData: args.frostData,
xprivData: args.xprivData,
),
RouteSettings(
name: settings.name,
),
);
} else if (args is ({
List<String> mnemonic,
String walletId,
({List<XPriv> xprivs, String fingerprint})? xprivData,
})) {
return FadePageRoute(
WalletKeysDesktopPopup(
words: args.mnemonic,
walletId: args.walletId,
xprivData: args.xprivData,
),
RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");

View file

@ -73,7 +73,15 @@ mixin ExtendedKeysInterface<T extends ElectrumXCurrencyInterface>
return (
fingerprint: fingerprint,
xprivs: await Future.wait(futures),
xprivs: [
(
path: "Master",
xpriv: master.encode(
cryptoCurrency.networkParams.privHDPrefix,
),
),
...(await Future.wait(futures)),
],
);
}
}

View file

@ -16,6 +16,8 @@ class DetailItem extends StatelessWidget {
this.showEmptyDetail = true,
this.horizontal = false,
this.disableSelectableText = false,
this.borderColor,
this.expandDetail = false,
});
final String title;
@ -25,6 +27,8 @@ class DetailItem extends StatelessWidget {
final bool horizontal;
final bool disableSelectableText;
final Color? overrideDetailTextColor;
final Color? borderColor;
final bool expandDetail;
@override
Widget build(BuildContext context) {
@ -45,6 +49,8 @@ class DetailItem extends StatelessWidget {
return DetailItemBase(
horizontal: horizontal,
borderColor: borderColor,
expandDetail: expandDetail,
title: disableSelectableText
? Text(
title,
@ -74,22 +80,30 @@ class DetailItemBase extends StatelessWidget {
required this.detail,
this.button,
this.horizontal = false,
this.borderColor,
this.expandDetail = false,
});
final Widget title;
final Widget detail;
final Widget? button;
final bool horizontal;
final Color? borderColor;
final bool expandDetail;
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: !Util.isDesktop,
condition: !Util.isDesktop || borderColor != null,
builder: (child) => RoundedWhiteContainer(
padding: Util.isDesktop
? const EdgeInsets.all(16)
: const EdgeInsets.all(12),
borderColor: borderColor,
child: child,
),
child: ConditionalParent(
condition: Util.isDesktop,
condition: Util.isDesktop && borderColor == null,
builder: (child) => Padding(
padding: const EdgeInsets.all(16),
child: child,
@ -99,7 +113,15 @@ class DetailItemBase extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
title,
detail,
if (expandDetail)
const SizedBox(
width: 16,
),
ConditionalParent(
condition: expandDetail,
builder: (child) => Expanded(child: child),
child: detail,
),
],
)
: Column(
@ -115,7 +137,11 @@ class DetailItemBase extends StatelessWidget {
const SizedBox(
height: 5,
),
detail,
ConditionalParent(
condition: expandDetail,
builder: (child) => Expanded(child: child),
child: detail,
),
],
),
),

View file

@ -11,17 +11,21 @@ class QR extends StatelessWidget {
@override
Widget build(BuildContext context) {
return QrImageView(
data: data,
size: size,
padding: padding ?? const EdgeInsets.all(10),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
// backgroundColor:
// Theme.of(context).extension<StackColors>()!.background,
// foregroundColor: Theme.of(context)
// .extension<StackColors>()!
// .accentColorDark,
return SizedBox(
width: size,
height: size,
child: QrImageView(
data: data,
size: size,
padding: padding ?? const EdgeInsets.all(10),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
// backgroundColor:
// Theme.of(context).extension<StackColors>()!.background,
// foregroundColor: Theme.of(context)
// .extension<StackColors>()!
// .accentColorDark,
),
);
}
}