mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-22 11:29:23 +00:00
ui view only wallet changes
This commit is contained in:
parent
53eb6ac8d1
commit
1f0ee995b9
17 changed files with 1501 additions and 430 deletions
|
@ -12,8 +12,6 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:blockchain_utils/bip/bip/bip39/bip39_mnemonic.dart';
|
||||
import 'package:blockchain_utils/bip/bip/bip39/bip39_mnemonic_generator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
|
|
@ -23,6 +23,7 @@ import '../../../../utilities/format.dart';
|
|||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../../wallets/crypto_currency/interfaces/view_only_option_currency_interface.dart';
|
||||
import '../../../../widgets/conditional_parent.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/custom_buttons/checkbox_text_button.dart';
|
||||
|
@ -32,7 +33,9 @@ import '../../../../widgets/desktop/desktop_scaffold.dart';
|
|||
import '../../../../widgets/expandable.dart';
|
||||
import '../../../../widgets/rounded_white_container.dart';
|
||||
import '../../../../widgets/stack_text_field.dart';
|
||||
import '../../../../widgets/toggle.dart';
|
||||
import '../../create_or_restore_wallet_view/sub_widgets/coin_image.dart';
|
||||
import '../restore_view_only_wallet_view.dart';
|
||||
import '../restore_wallet_view.dart';
|
||||
import '../sub_widgets/mnemonic_word_count_select_sheet.dart';
|
||||
import 'sub_widgets/mobile_mnemonic_length_selector.dart';
|
||||
|
@ -69,7 +72,6 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
final bool _nextEnabled = true;
|
||||
DateTime? _restoreFromDate;
|
||||
bool hidePassword = true;
|
||||
bool _expandedAdavnced = false;
|
||||
|
||||
bool get supportsMnemonicPassphrase => coin.hasMnemonicPassphraseSupport;
|
||||
|
||||
|
@ -99,27 +101,46 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
bool _nextLock = false;
|
||||
Future<void> nextPressed() async {
|
||||
if (!isDesktop) {
|
||||
// hide keyboard if has focus
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
if (_nextLock) return;
|
||||
_nextLock = true;
|
||||
try {
|
||||
if (!isDesktop) {
|
||||
// hide keyboard if has focus
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
RestoreWalletView.routeName,
|
||||
arguments: Tuple6(
|
||||
walletName,
|
||||
coin,
|
||||
ref.read(mnemonicWordCountStateProvider.state).state,
|
||||
_restoreFromDate,
|
||||
passwordController.text,
|
||||
enableLelantusScanning,
|
||||
),
|
||||
);
|
||||
if (mounted) {
|
||||
if (!_showViewOnlyOption) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
RestoreWalletView.routeName,
|
||||
arguments: Tuple6(
|
||||
walletName,
|
||||
coin,
|
||||
ref.read(mnemonicWordCountStateProvider.state).state,
|
||||
_restoreFromDate,
|
||||
passwordController.text,
|
||||
enableLelantusScanning,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(
|
||||
RestoreViewOnlyWalletView.routeName,
|
||||
arguments: (
|
||||
walletName: walletName,
|
||||
coin: coin,
|
||||
restoreFromDate: _restoreFromDate,
|
||||
enableLelantusScanning: enableLelantusScanning,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
_nextLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,17 +185,12 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
);
|
||||
}
|
||||
|
||||
bool _showViewOnlyOption = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType with ${coin.identifier} $walletName");
|
||||
|
||||
final lengths = coin.possibleMnemonicLengths;
|
||||
|
||||
final isMoneroAnd25 = coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
final isWowneroAnd25 = coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
|
||||
return MasterScaffold(
|
||||
isDesktop: isDesktop,
|
||||
appBar: isDesktop
|
||||
|
@ -227,288 +243,57 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
SizedBox(
|
||||
height: isDesktop ? 40 : 24,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
Text(
|
||||
"Choose start date",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
if (coin is ViewOnlyOptionCurrencyInterface)
|
||||
SizedBox(
|
||||
height: isDesktop ? 16 : 8,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
if (!isDesktop)
|
||||
RestoreFromDatePicker(
|
||||
onTap: chooseDate,
|
||||
controller: _dateController,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
if (isDesktop)
|
||||
// TODO desktop date picker
|
||||
RestoreFromDatePicker(
|
||||
onTap: chooseDesktopDate,
|
||||
controller: _dateController,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Choose the date you made the wallet (approximate is fine)",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.smallMed12(context).copyWith(
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
),
|
||||
Text(
|
||||
"Choose recovery phrase length",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 16 : 8,
|
||||
),
|
||||
if (isDesktop)
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<int>(
|
||||
value:
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state,
|
||||
items: [
|
||||
...lengths.map(
|
||||
(e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
"$e words",
|
||||
style: STextStyles.desktopTextMedium(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value is int) {
|
||||
ref.read(mnemonicWordCountStateProvider.state).state =
|
||||
value;
|
||||
}
|
||||
height: isDesktop ? 56 : 48,
|
||||
width: isDesktop ? 490 : null,
|
||||
child: Toggle(
|
||||
key: UniqueKey(),
|
||||
onText: "Seed",
|
||||
offText: "View Only",
|
||||
onColor:
|
||||
Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
offColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
isOn: _showViewOnlyOption,
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
_showViewOnlyOption = value;
|
||||
});
|
||||
},
|
||||
isExpanded: true,
|
||||
iconStyleData: IconStyleData(
|
||||
icon: 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,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isDesktop)
|
||||
MobileMnemonicLengthSelector(
|
||||
chooseMnemonicLength: chooseMnemonicLength,
|
||||
),
|
||||
if (supportsMnemonicPassphrase)
|
||||
if (coin is ViewOnlyOptionCurrencyInterface)
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
height: isDesktop ? 40 : 24,
|
||||
),
|
||||
if (supportsMnemonicPassphrase)
|
||||
Expandable(
|
||||
onExpandChanged: (state) {
|
||||
setState(() {
|
||||
_expandedAdavnced = state == ExpandableState.expanded;
|
||||
});
|
||||
},
|
||||
header: Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8.0,
|
||||
bottom: 8.0,
|
||||
right: 10,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Advanced",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(
|
||||
context,
|
||||
).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
_expandedAdavnced
|
||||
? Assets.svg.chevronUp
|
||||
: Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
],
|
||||
),
|
||||
_showViewOnlyOption
|
||||
? ViewOnlyRestoreOption(
|
||||
coin: coin,
|
||||
dateController: _dateController,
|
||||
dateChooserFunction:
|
||||
isDesktop ? chooseDesktopDate : chooseDate,
|
||||
)
|
||||
: SeedRestoreOption(
|
||||
coin: coin,
|
||||
dateController: _dateController,
|
||||
pwController: passwordController,
|
||||
pwFocusNode: passwordFocusNode,
|
||||
supportsMnemonicPassphrase: supportsMnemonicPassphrase,
|
||||
dateChooserFunction:
|
||||
isDesktop ? chooseDesktopDate : chooseDate,
|
||||
chooseMnemonicLength: chooseMnemonicLength,
|
||||
lelScanChanged: (value) {
|
||||
enableLelantusScanning = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
if (coin is Firo)
|
||||
CheckboxTextButton(
|
||||
label: "Scan for Lelantus transactions",
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
enableLelantusScanning = newValue ?? true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (coin is Firo)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("mnemonicPassphraseFieldKey1"),
|
||||
focusNode: passwordFocusNode,
|
||||
controller: passwordController,
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextMedium(context)
|
||||
.copyWith(
|
||||
height: 2,
|
||||
)
|
||||
: STextStyles.field(context),
|
||||
obscureText: hidePassword,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: standardInputDecoration(
|
||||
"BIP39 passphrase",
|
||||
passwordFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
suffixIcon: UnconstrainedBox(
|
||||
child: ConditionalParent(
|
||||
condition: isDesktop,
|
||||
builder: (child) => SizedBox(
|
||||
height: 70,
|
||||
child: child,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: isDesktop ? 24 : 16,
|
||||
),
|
||||
GestureDetector(
|
||||
key: const Key(
|
||||
"mnemonicPassphraseFieldShowPasswordButtonKey",
|
||||
),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
hidePassword = !hidePassword;
|
||||
});
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
hidePassword
|
||||
? Assets.svg.eye
|
||||
: Assets.svg.eyeSlash,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
width: isDesktop ? 24 : 16,
|
||||
height: isDesktop ? 24 : 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"If the recovery phrase you are about to restore "
|
||||
"was created with an optional BIP39 passphrase "
|
||||
"you can enter it here.",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isDesktop)
|
||||
const Spacer(
|
||||
flex: 3,
|
||||
|
@ -532,3 +317,394 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SeedRestoreOption extends ConsumerStatefulWidget {
|
||||
const SeedRestoreOption({
|
||||
super.key,
|
||||
required this.coin,
|
||||
required this.dateController,
|
||||
required this.pwController,
|
||||
required this.pwFocusNode,
|
||||
required this.supportsMnemonicPassphrase,
|
||||
required this.dateChooserFunction,
|
||||
required this.chooseMnemonicLength,
|
||||
required this.lelScanChanged,
|
||||
});
|
||||
|
||||
final CryptoCurrency coin;
|
||||
final TextEditingController dateController;
|
||||
final TextEditingController pwController;
|
||||
final FocusNode pwFocusNode;
|
||||
final bool supportsMnemonicPassphrase;
|
||||
|
||||
final Future<void> Function() dateChooserFunction;
|
||||
final Future<void> Function() chooseMnemonicLength;
|
||||
final void Function(bool) lelScanChanged;
|
||||
|
||||
@override
|
||||
ConsumerState<SeedRestoreOption> createState() => _SeedRestoreOptionState();
|
||||
}
|
||||
|
||||
class _SeedRestoreOptionState extends ConsumerState<SeedRestoreOption> {
|
||||
bool _hidePassword = true;
|
||||
bool _expandedAdvanced = false;
|
||||
bool _enableLelantusScanning = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final lengths = widget.coin.possibleMnemonicLengths;
|
||||
|
||||
final isMoneroAnd25 = widget.coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
final isWowneroAnd25 = widget.coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
Text(
|
||||
"Choose start date",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 16 : 8,
|
||||
),
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
RestoreFromDatePicker(
|
||||
onTap: widget.dateChooserFunction,
|
||||
controller: widget.dateController,
|
||||
),
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Choose the date you made the wallet (approximate is fine)",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.smallMed12(context).copyWith(
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
Text(
|
||||
"Choose recovery phrase length",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 16 : 8,
|
||||
),
|
||||
if (Util.isDesktop)
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<int>(
|
||||
value: ref.watch(mnemonicWordCountStateProvider.state).state,
|
||||
items: [
|
||||
...lengths.map(
|
||||
(e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
"$e words",
|
||||
style: STextStyles.desktopTextMedium(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value is int) {
|
||||
ref.read(mnemonicWordCountStateProvider.state).state = value;
|
||||
}
|
||||
},
|
||||
isExpanded: true,
|
||||
iconStyleData: IconStyleData(
|
||||
icon: 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!Util.isDesktop)
|
||||
MobileMnemonicLengthSelector(
|
||||
chooseMnemonicLength: widget.chooseMnemonicLength,
|
||||
),
|
||||
if (widget.supportsMnemonicPassphrase)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
if (widget.supportsMnemonicPassphrase)
|
||||
Expandable(
|
||||
onExpandChanged: (state) {
|
||||
setState(() {
|
||||
_expandedAdvanced = state == ExpandableState.expanded;
|
||||
});
|
||||
},
|
||||
header: Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8.0,
|
||||
bottom: 8.0,
|
||||
right: 10,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Advanced",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(
|
||||
context,
|
||||
).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
_expandedAdvanced
|
||||
? Assets.svg.chevronUp
|
||||
: Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
if (widget.coin is Firo)
|
||||
CheckboxTextButton(
|
||||
label: "Scan for Lelantus transactions",
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
_enableLelantusScanning = newValue ?? true;
|
||||
});
|
||||
|
||||
widget.lelScanChanged(_enableLelantusScanning);
|
||||
},
|
||||
),
|
||||
if (widget.coin is Firo)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("mnemonicPassphraseFieldKey1"),
|
||||
focusNode: widget.pwFocusNode,
|
||||
controller: widget.pwController,
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextMedium(context).copyWith(
|
||||
height: 2,
|
||||
)
|
||||
: STextStyles.field(context),
|
||||
obscureText: _hidePassword,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: standardInputDecoration(
|
||||
"BIP39 passphrase",
|
||||
widget.pwFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
suffixIcon: UnconstrainedBox(
|
||||
child: ConditionalParent(
|
||||
condition: Util.isDesktop,
|
||||
builder: (child) => SizedBox(
|
||||
height: 70,
|
||||
child: child,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
GestureDetector(
|
||||
key: const Key(
|
||||
"mnemonicPassphraseFieldShowPasswordButtonKey",
|
||||
),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_hidePassword = !_hidePassword;
|
||||
});
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
_hidePassword
|
||||
? Assets.svg.eye
|
||||
: Assets.svg.eyeSlash,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
width: Util.isDesktop ? 24 : 16,
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"If the recovery phrase you are about to restore "
|
||||
"was created with an optional BIP39 passphrase "
|
||||
"you can enter it here.",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ViewOnlyRestoreOption extends StatefulWidget {
|
||||
const ViewOnlyRestoreOption({
|
||||
super.key,
|
||||
required this.coin,
|
||||
required this.dateController,
|
||||
required this.dateChooserFunction,
|
||||
});
|
||||
|
||||
final CryptoCurrency coin;
|
||||
final TextEditingController dateController;
|
||||
|
||||
final Future<void> Function() dateChooserFunction;
|
||||
|
||||
@override
|
||||
State<ViewOnlyRestoreOption> createState() => _ViewOnlyRestoreOptionState();
|
||||
}
|
||||
|
||||
class _ViewOnlyRestoreOptionState extends State<ViewOnlyRestoreOption> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final showDateOption = widget.coin is ViewOnlyOptionCurrencyInterface;
|
||||
return Column(
|
||||
children: [
|
||||
if (showDateOption)
|
||||
Text(
|
||||
"Choose start date",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if (showDateOption)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 16 : 8,
|
||||
),
|
||||
if (showDateOption)
|
||||
RestoreFromDatePicker(
|
||||
onTap: widget.dateChooserFunction,
|
||||
controller: widget.dateController,
|
||||
),
|
||||
if (showDateOption)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (showDateOption)
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Choose the date you made the wallet (approximate is fine)",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.smallMed12(context).copyWith(
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showDateOption)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,393 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cs_monero/src/deprecated/get_height_by_date.dart'
|
||||
as cs_monero_deprecated;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../../../pages_desktop_specific/desktop_home_view.dart';
|
||||
import '../../../pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
||||
import '../../../providers/db/main_db_provider.dart';
|
||||
import '../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../providers/providers.dart';
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/barcode_scanner_interface.dart';
|
||||
import '../../../utilities/clipboard_interface.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../wallets/isar/models/wallet_info.dart';
|
||||
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/monero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/wownero_wallet.dart';
|
||||
import '../../../wallets/wallet/wallet.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../widgets/desktop/desktop_app_bar.dart';
|
||||
import '../../../widgets/desktop/desktop_scaffold.dart';
|
||||
import '../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../widgets/stack_text_field.dart';
|
||||
import '../../home_view/home_view.dart';
|
||||
import 'confirm_recovery_dialog.dart';
|
||||
import 'sub_widgets/restore_failed_dialog.dart';
|
||||
import 'sub_widgets/restore_succeeded_dialog.dart';
|
||||
import 'sub_widgets/restoring_dialog.dart';
|
||||
|
||||
class RestoreViewOnlyWalletView extends ConsumerStatefulWidget {
|
||||
const RestoreViewOnlyWalletView({
|
||||
super.key,
|
||||
required this.walletName,
|
||||
required this.coin,
|
||||
required this.restoreFromDate,
|
||||
this.enableLelantusScanning = false,
|
||||
this.barcodeScanner = const BarcodeScannerWrapper(),
|
||||
this.clipboard = const ClipboardWrapper(),
|
||||
});
|
||||
|
||||
static const routeName = "/restoreViewOnlyWallet";
|
||||
|
||||
final String walletName;
|
||||
final CryptoCurrency coin;
|
||||
final DateTime? restoreFromDate;
|
||||
final bool enableLelantusScanning;
|
||||
final BarcodeScannerInterface barcodeScanner;
|
||||
final ClipboardInterface clipboard;
|
||||
|
||||
@override
|
||||
ConsumerState<RestoreViewOnlyWalletView> createState() =>
|
||||
_RestoreViewOnlyWalletViewState();
|
||||
}
|
||||
|
||||
class _RestoreViewOnlyWalletViewState
|
||||
extends ConsumerState<RestoreViewOnlyWalletView> {
|
||||
late final TextEditingController addressController;
|
||||
late final TextEditingController viewKeyController;
|
||||
|
||||
bool _enableRestoreButton = false;
|
||||
|
||||
bool _buttonLock = false;
|
||||
|
||||
Future<void> _requestRestore() async {
|
||||
if (_buttonLock) return;
|
||||
_buttonLock = true;
|
||||
|
||||
try {
|
||||
if (!Util.isDesktop) {
|
||||
// wait for keyboard to disappear
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return ConfirmRecoveryDialog(
|
||||
onConfirm: _attemptRestore,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_buttonLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _attemptRestore() async {
|
||||
int height = 0;
|
||||
final Map<String, dynamic> otherDataJson = {
|
||||
WalletInfoKeys.isViewOnlyKey: true,
|
||||
};
|
||||
|
||||
if (widget.restoreFromDate != null) {
|
||||
if (widget.coin is Monero) {
|
||||
height = cs_monero_deprecated.getMoneroHeightByDate(
|
||||
date: widget.restoreFromDate!,
|
||||
);
|
||||
}
|
||||
if (widget.coin is Wownero) {
|
||||
height = cs_monero_deprecated.getWowneroHeightByDate(
|
||||
date: widget.restoreFromDate!,
|
||||
);
|
||||
}
|
||||
if (height < 0) {
|
||||
height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.coin is Firo) {
|
||||
otherDataJson.addAll(
|
||||
{
|
||||
WalletInfoKeys.lelantusCoinIsarRescanRequired: false,
|
||||
WalletInfoKeys.enableLelantusScanning: widget.enableLelantusScanning,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!Platform.isLinux && !Util.isDesktop) await WakelockPlus.enable();
|
||||
|
||||
try {
|
||||
final info = WalletInfo.createNew(
|
||||
coin: widget.coin,
|
||||
name: widget.walletName,
|
||||
restoreHeight: height,
|
||||
otherDataJsonString: jsonEncode(otherDataJson),
|
||||
);
|
||||
|
||||
bool isRestoring = true;
|
||||
// show restoring in progress
|
||||
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: false,
|
||||
builder: (context) {
|
||||
return RestoringDialog(
|
||||
onCancel: () async {
|
||||
isRestoring = false;
|
||||
|
||||
await ref.read(pWallets).deleteWallet(
|
||||
info,
|
||||
ref.read(secureStoreProvider),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
var node = ref
|
||||
.read(nodeServiceChangeNotifierProvider)
|
||||
.getPrimaryNodeFor(currency: widget.coin);
|
||||
|
||||
if (node == null) {
|
||||
node = widget.coin.defaultNode;
|
||||
await ref.read(nodeServiceChangeNotifierProvider).setPrimaryNodeFor(
|
||||
coin: widget.coin,
|
||||
node: node,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
final wallet = await Wallet.create(
|
||||
walletInfo: info,
|
||||
mainDB: ref.read(mainDBProvider),
|
||||
secureStorageInterface: ref.read(secureStoreProvider),
|
||||
nodeService: ref.read(nodeServiceChangeNotifierProvider),
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
viewOnlyData: ViewOnlyWalletData(
|
||||
address: addressController.text,
|
||||
privateViewKey: viewKeyController.text,
|
||||
),
|
||||
);
|
||||
|
||||
// TODO: extract interface with isRestore param
|
||||
switch (wallet.runtimeType) {
|
||||
case const (EpiccashWallet):
|
||||
await (wallet as EpiccashWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (MoneroWallet):
|
||||
await (wallet as MoneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (WowneroWallet):
|
||||
await (wallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await wallet.init();
|
||||
}
|
||||
|
||||
await wallet.recover(isRescan: false);
|
||||
|
||||
// check if state is still active before continuing
|
||||
if (mounted) {
|
||||
// don't remove this setMnemonicVerified thing
|
||||
await wallet.info.setMnemonicVerified(
|
||||
isar: ref.read(mainDBProvider).isar,
|
||||
);
|
||||
|
||||
ref.read(pWallets).addWallet(wallet);
|
||||
|
||||
if (mounted) {
|
||||
if (Util.isDesktop) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(
|
||||
DesktopHomeView.routeName,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
HomeView.routeName,
|
||||
(route) => false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return const RestoreSucceededDialog();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// check if state is still active and restore wasn't cancelled
|
||||
// before continuing
|
||||
if (mounted && isRestoring) {
|
||||
// pop waiting dialog
|
||||
Navigator.pop(context);
|
||||
|
||||
// show restoring wallet failed dialog
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return RestoreFailedDialog(
|
||||
errorMessage: e.toString(),
|
||||
walletId: info.walletId,
|
||||
walletName: info.name,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (!Platform.isLinux && !Util.isDesktop) await WakelockPlus.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
addressController = TextEditingController();
|
||||
viewKeyController = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDesktop = Util.isDesktop;
|
||||
return MasterScaffold(
|
||||
isDesktop: isDesktop,
|
||||
appBar: isDesktop
|
||||
? const DesktopAppBar(
|
||||
isCompactHeight: false,
|
||||
leading: AppBarBackButton(),
|
||||
trailing: ExitToMyStackButton(),
|
||||
)
|
||||
: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 50),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
color: Theme.of(context).extension<StackColors>()!.background,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
maxWidth: isDesktop ? 480 : double.infinity,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
if (isDesktop)
|
||||
const Spacer(
|
||||
flex: 10,
|
||||
),
|
||||
if (!isDesktop)
|
||||
Text(
|
||||
widget.walletName,
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 0 : 4,
|
||||
),
|
||||
Text(
|
||||
"Enter view only details",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopH2(context)
|
||||
: STextStyles.pageTitleH1(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
),
|
||||
FullTextField(
|
||||
label: "Address",
|
||||
controller: addressController,
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
_enableRestoreButton = newValue.isNotEmpty &&
|
||||
viewKeyController.text.isNotEmpty;
|
||||
});
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 16 : 12,
|
||||
),
|
||||
FullTextField(
|
||||
label: "View Key",
|
||||
controller: viewKeyController,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_enableRestoreButton = value.isNotEmpty &&
|
||||
addressController.text.isNotEmpty;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (!isDesktop) const Spacer(),
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
),
|
||||
PrimaryButton(
|
||||
enabled: _enableRestoreButton,
|
||||
onPressed: _requestRestore,
|
||||
width: isDesktop ? 480 : null,
|
||||
label: "Restore",
|
||||
),
|
||||
if (isDesktop)
|
||||
const Spacer(
|
||||
flex: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -655,16 +655,18 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
const Duration(milliseconds: 100),
|
||||
);
|
||||
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return ConfirmRecoveryDialog(
|
||||
onConfirm: attemptRestore,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (mounted) {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return ConfirmRecoveryDialog(
|
||||
onConfirm: attemptRestore,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -39,6 +39,7 @@ import '../../../wallets/wallet/impl/epiccash_wallet.dart';
|
|||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../widgets/background.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../widgets/desktop/secondary_button.dart';
|
||||
|
@ -273,6 +274,7 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
|||
String keys
|
||||
})? prevGen,
|
||||
})? frostWalletData;
|
||||
ViewOnlyWalletData? voData;
|
||||
if (wallet is BitcoinFrostWallet) {
|
||||
final futures = [
|
||||
wallet.getSerializedKeys(),
|
||||
|
@ -298,10 +300,17 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
|||
),
|
||||
);
|
||||
}
|
||||
} else if (wallet
|
||||
is MnemonicInterface) {
|
||||
mnemonic =
|
||||
await wallet.getMnemonicAsWords();
|
||||
} else {
|
||||
if (wallet
|
||||
is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly) {
|
||||
voData = await wallet
|
||||
.getViewOnlyWalletData();
|
||||
} else if (wallet
|
||||
is MnemonicInterface) {
|
||||
mnemonic = await wallet
|
||||
.getMnemonicAsWords();
|
||||
}
|
||||
}
|
||||
|
||||
KeyDataInterface? keyData;
|
||||
|
@ -312,36 +321,68 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
|||
}
|
||||
|
||||
if (context.mounted) {
|
||||
await Navigator.push(
|
||||
context,
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute:
|
||||
RouteGenerator
|
||||
.useMaterialPageRoute,
|
||||
builder: (_) => LockscreenView(
|
||||
routeOnSuccessArguments: (
|
||||
walletId: walletId,
|
||||
mnemonic: mnemonic ?? [],
|
||||
frostWalletData:
|
||||
frostWalletData,
|
||||
keyData: keyData,
|
||||
if (voData != null) {
|
||||
await Navigator.push(
|
||||
context,
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute:
|
||||
RouteGenerator
|
||||
.useMaterialPageRoute,
|
||||
builder: (_) => LockscreenView(
|
||||
routeOnSuccessArguments: (
|
||||
walletId: walletId,
|
||||
keyData: keyData,
|
||||
),
|
||||
showBackButton: true,
|
||||
routeOnSuccess:
|
||||
MobileKeyDataView
|
||||
.routeName,
|
||||
biometricsCancelButtonString:
|
||||
"CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to view recovery data",
|
||||
biometricsAuthenticationTitle:
|
||||
"View recovery data",
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name:
|
||||
"/viewRecoveryDataLockscreen",
|
||||
),
|
||||
showBackButton: true,
|
||||
routeOnSuccess:
|
||||
WalletBackupView.routeName,
|
||||
biometricsCancelButtonString:
|
||||
"CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to view recovery phrase",
|
||||
biometricsAuthenticationTitle:
|
||||
"View recovery phrase",
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name:
|
||||
"/viewRecoverPhraseLockscreen",
|
||||
);
|
||||
} else {
|
||||
await Navigator.push(
|
||||
context,
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute:
|
||||
RouteGenerator
|
||||
.useMaterialPageRoute,
|
||||
builder: (_) => LockscreenView(
|
||||
routeOnSuccessArguments: (
|
||||
walletId: walletId,
|
||||
mnemonic: mnemonic ?? [],
|
||||
frostWalletData:
|
||||
frostWalletData,
|
||||
keyData: keyData,
|
||||
),
|
||||
showBackButton: true,
|
||||
routeOnSuccess:
|
||||
WalletBackupView
|
||||
.routeName,
|
||||
biometricsCancelButtonString:
|
||||
"CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to view recovery phrase",
|
||||
biometricsAuthenticationTitle:
|
||||
"View recovery phrase",
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name:
|
||||
"/viewRecoverPhraseLockscreen",
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../app_config.dart';
|
||||
import '../../../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart';
|
||||
import '../../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../../providers/global/wallets_provider.dart';
|
||||
import '../../../../route_generator.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../widgets/conditional_parent.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/custom_buttons/simple_copy_button.dart';
|
||||
import '../../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../../widgets/detail_item.dart';
|
||||
import '../../../../widgets/rounded_white_container.dart';
|
||||
import '../../../../widgets/stack_dialog.dart';
|
||||
import '../../../home_view/home_view.dart';
|
||||
import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||
|
||||
class DeleteViewOnlyWalletKeysView extends ConsumerStatefulWidget {
|
||||
const DeleteViewOnlyWalletKeysView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
static const routeName = "/deleteWalletViewOnlyData";
|
||||
|
||||
final String walletId;
|
||||
final ViewOnlyWalletData data;
|
||||
|
||||
@override
|
||||
ConsumerState<DeleteViewOnlyWalletKeysView> createState() =>
|
||||
_DeleteViewOnlyWalletKeysViewState();
|
||||
}
|
||||
|
||||
class _DeleteViewOnlyWalletKeysViewState
|
||||
extends ConsumerState<DeleteViewOnlyWalletKeysView> {
|
||||
bool _lock = false;
|
||||
void _continuePressed() async {
|
||||
if (_lock) {
|
||||
return;
|
||||
}
|
||||
_lock = true;
|
||||
try {
|
||||
if (Util.isDesktop) {
|
||||
await Navigator.of(context).push(
|
||||
RouteGenerator.getRoute(
|
||||
builder: (context) {
|
||||
return ConfirmDelete(
|
||||
walletId: widget.walletId,
|
||||
);
|
||||
},
|
||||
settings: const RouteSettings(
|
||||
name: "/desktopConfirmDelete",
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await showDialog<dynamic>(
|
||||
barrierDismissible: true,
|
||||
context: context,
|
||||
builder: (_) => StackDialog(
|
||||
title: "Thanks! Your wallet will be deleted.",
|
||||
leftButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(context),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonStyle(context),
|
||||
onPressed: () async {
|
||||
await ref.read(pWallets).deleteWallet(
|
||||
ref.read(pWalletInfo(widget.walletId)),
|
||||
ref.read(secureStoreProvider),
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(HomeView.routeName),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
"Ok",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_lock = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConditionalParent(
|
||||
condition: !Util.isDesktop,
|
||||
builder: (child) => Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, cons) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: cons.maxHeight),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Please write down your backup data. Keep it safe and "
|
||||
"never share it with anyone. "
|
||||
"Your backup data is the only way you can access your "
|
||||
"wallet if you forget your PIN, lose your phone, etc."
|
||||
"\n\n"
|
||||
"${AppConfig.appName} does not keep nor is able to restore "
|
||||
"your backup data. "
|
||||
"Only you have access to your wallet.",
|
||||
style: STextStyles.label(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
if (widget.data.address != null)
|
||||
DetailItem(
|
||||
title: "Address",
|
||||
detail: widget.data.address!,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: widget.data.address!,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: widget.data.address!,
|
||||
),
|
||||
),
|
||||
if (widget.data.address != null)
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (widget.data.privateViewKey != null)
|
||||
DetailItem(
|
||||
title: "Private view key",
|
||||
detail: widget.data.privateViewKey!,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: widget.data.privateViewKey!,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: widget.data.privateViewKey!,
|
||||
),
|
||||
),
|
||||
if (!Util.isDesktop) const Spacer(),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Continue",
|
||||
onPressed: _continuePressed,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -14,11 +14,9 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../../../../app_config.dart';
|
||||
import '../../../../notifications/show_flush_bar.dart';
|
||||
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
|
||||
import '../../../home_view/home_view.dart';
|
||||
import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||
import '../../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../../providers/global/wallets_provider.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
|
@ -35,6 +33,9 @@ import '../../../../widgets/desktop/primary_button.dart';
|
|||
import '../../../../widgets/detail_item.dart';
|
||||
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 '../../../home_view/home_view.dart';
|
||||
import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||
|
||||
class DeleteWalletRecoveryPhraseView extends ConsumerStatefulWidget {
|
||||
const DeleteWalletRecoveryPhraseView({
|
||||
|
@ -69,7 +70,6 @@ class _DeleteWalletRecoveryPhraseViewState
|
|||
late ClipboardInterface _clipboardInterface;
|
||||
|
||||
bool _lock = false;
|
||||
|
||||
void _continuePressed() {
|
||||
if (_lock) {
|
||||
return;
|
||||
|
|
|
@ -17,9 +17,11 @@ import '../../../../themes/stack_colors.dart';
|
|||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../widgets/background.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/rounded_container.dart';
|
||||
import 'delete_view_only_wallet_keys_view.dart';
|
||||
import 'delete_wallet_recovery_phrase_view.dart';
|
||||
|
||||
class DeleteWalletWarningView extends ConsumerWidget {
|
||||
|
@ -118,6 +120,7 @@ class DeleteWalletWarningView extends ConsumerWidget {
|
|||
String keys,
|
||||
({String config, String keys})? prevGen,
|
||||
})? frostWalletData;
|
||||
ViewOnlyWalletData? viewOnlyData;
|
||||
|
||||
if (wallet is BitcoinFrostWallet) {
|
||||
final futures = [
|
||||
|
@ -142,18 +145,33 @@ class DeleteWalletWarningView extends ConsumerWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
} else if (wallet is MnemonicInterface) {
|
||||
mnemonic = await wallet.getMnemonicAsWords();
|
||||
} else {
|
||||
if (wallet is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly) {
|
||||
viewOnlyData = await wallet.getViewOnlyWalletData();
|
||||
} else if (wallet is MnemonicInterface) {
|
||||
mnemonic = await wallet.getMnemonicAsWords();
|
||||
}
|
||||
}
|
||||
if (context.mounted) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
DeleteWalletRecoveryPhraseView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
mnemonicWords: mnemonic ?? [],
|
||||
frostWalletData: frostWalletData,
|
||||
),
|
||||
);
|
||||
if (viewOnlyData != null) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
DeleteViewOnlyWalletKeysView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
data: viewOnlyData,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(
|
||||
DeleteWalletRecoveryPhraseView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
mnemonicWords: mnemonic ?? [],
|
||||
frostWalletData: frostWalletData,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
|
|
|
@ -56,6 +56,7 @@ import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart
|
|||
import '../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -524,6 +525,10 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
final prefs = ref.watch(prefsChangeNotifierProvider);
|
||||
final showExchange = prefs.enableExchange;
|
||||
|
||||
final wallet = ref.watch(pWallets).getWallet(walletId);
|
||||
|
||||
final viewOnly = wallet is ViewOnlyOptionInterface && wallet.isViewOnly;
|
||||
|
||||
return ConditionalParent(
|
||||
condition: _rescanningOnOpen,
|
||||
builder: (child) {
|
||||
|
@ -1026,38 +1031,39 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
icon: const FrostSignNavIcon(),
|
||||
onTap: () => _onFrostSignPressed(context),
|
||||
),
|
||||
WalletNavigationBarItemData(
|
||||
label: "Send",
|
||||
icon: const SendNavIcon(),
|
||||
onTap: () {
|
||||
// not sure what this is supposed to accomplish?
|
||||
// switch (ref
|
||||
// .read(walletBalanceToggleStateProvider.state)
|
||||
// .state) {
|
||||
// case WalletBalanceToggleState.full:
|
||||
// ref
|
||||
// .read(publicPrivateBalanceStateProvider.state)
|
||||
// .state = "Public";
|
||||
// break;
|
||||
// case WalletBalanceToggleState.available:
|
||||
// ref
|
||||
// .read(publicPrivateBalanceStateProvider.state)
|
||||
// .state = "Private";
|
||||
// break;
|
||||
// }
|
||||
Navigator.of(context).pushNamed(
|
||||
ref.read(pWallets).getWallet(walletId)
|
||||
is BitcoinFrostWallet
|
||||
? FrostSendView.routeName
|
||||
: SendView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
coin: coin,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (Constants.enableExchange &&
|
||||
if (!viewOnly)
|
||||
WalletNavigationBarItemData(
|
||||
label: "Send",
|
||||
icon: const SendNavIcon(),
|
||||
onTap: () {
|
||||
// not sure what this is supposed to accomplish?
|
||||
// switch (ref
|
||||
// .read(walletBalanceToggleStateProvider.state)
|
||||
// .state) {
|
||||
// case WalletBalanceToggleState.full:
|
||||
// ref
|
||||
// .read(publicPrivateBalanceStateProvider.state)
|
||||
// .state = "Public";
|
||||
// break;
|
||||
// case WalletBalanceToggleState.available:
|
||||
// ref
|
||||
// .read(publicPrivateBalanceStateProvider.state)
|
||||
// .state = "Private";
|
||||
// break;
|
||||
// }
|
||||
Navigator.of(context).pushNamed(
|
||||
wallet is BitcoinFrostWallet
|
||||
? FrostSendView.routeName
|
||||
: SendView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
coin: coin,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!viewOnly &&
|
||||
Constants.enableExchange &&
|
||||
ref.watch(pWalletCoin(walletId)) is! FrostCurrency &&
|
||||
AppConfig.hasFeature(AppFeature.swap) &&
|
||||
showExchange)
|
||||
|
@ -1113,12 +1119,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (ref.watch(
|
||||
pWallets.select(
|
||||
(value) => value.getWallet(widget.walletId)
|
||||
is CoinControlInterface,
|
||||
),
|
||||
) &&
|
||||
if (wallet is CoinControlInterface &&
|
||||
ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableCoinControl,
|
||||
|
@ -1137,12 +1138,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (ref.watch(
|
||||
pWallets.select(
|
||||
(value) =>
|
||||
value.getWallet(widget.walletId) is PaynymInterface,
|
||||
),
|
||||
))
|
||||
if (wallet is PaynymInterface)
|
||||
WalletNavigationBarItemData(
|
||||
label: "PayNym",
|
||||
icon: const PaynymNavIcon(),
|
||||
|
@ -1213,12 +1209,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (ref.watch(
|
||||
pWallets.select(
|
||||
(value) => value.getWallet(widget.walletId)
|
||||
is CashFusionInterface,
|
||||
),
|
||||
))
|
||||
if (wallet is CashFusionInterface && !viewOnly)
|
||||
WalletNavigationBarItemData(
|
||||
label: "Fusion",
|
||||
icon: const FusionNavIcon(),
|
||||
|
@ -1229,12 +1220,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (ref.watch(
|
||||
pWallets.select(
|
||||
(value) =>
|
||||
value.getWallet(widget.walletId) is LibMoneroWallet,
|
||||
),
|
||||
))
|
||||
if (wallet is LibMoneroWallet && !viewOnly)
|
||||
WalletNavigationBarItemData(
|
||||
label: "Churn",
|
||||
icon: const ChurnNavIcon(),
|
||||
|
|
|
@ -10,18 +10,21 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../../app_config.dart';
|
||||
import 'delete_wallet_keys_popup.dart';
|
||||
import '../../../../pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_view_only_wallet_keys_view.dart';
|
||||
import '../../../../providers/global/wallets_provider.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
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/rounded_container.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'delete_wallet_keys_popup.dart';
|
||||
|
||||
class DesktopAttentionDeleteWallet extends ConsumerStatefulWidget {
|
||||
const DesktopAttentionDeleteWallet({
|
||||
|
@ -114,6 +117,58 @@ class _DesktopAttentionDeleteWallet
|
|||
onPressed: () async {
|
||||
final wallet =
|
||||
ref.read(pWallets).getWallet(widget.walletId);
|
||||
|
||||
if (wallet is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly) {
|
||||
final data = await wallet.getViewOnlyWalletData();
|
||||
if (context.mounted) {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (builder) => DesktopDialog(
|
||||
maxWidth: 614,
|
||||
maxHeight: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
"Wallet keys",
|
||||
style: STextStyles.desktopH3(
|
||||
context,
|
||||
),
|
||||
),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () {
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: DeleteViewOnlyWalletKeysView(
|
||||
walletId: widget.walletId,
|
||||
data: data,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else
|
||||
|
||||
// TODO: [prio=med] handle other types wallet deletion
|
||||
// All wallets currently are mnemonic based
|
||||
if (wallet is MnemonicInterface) {
|
||||
|
|
|
@ -39,6 +39,7 @@ import '../../../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface
|
|||
import '../../../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../widgets/custom_loading_overlay.dart';
|
||||
import '../../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../../widgets/desktop/primary_button.dart';
|
||||
|
@ -380,9 +381,12 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
wallet is OrdinalsInterface ||
|
||||
wallet is CashFusionInterface;
|
||||
|
||||
final isViewOnly = wallet is ViewOnlyOptionInterface && wallet.isViewOnly;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (Constants.enableExchange &&
|
||||
if (!isViewOnly &&
|
||||
Constants.enableExchange &&
|
||||
AppConfig.hasFeature(AppFeature.swap) &&
|
||||
showExchange)
|
||||
SecondaryButton(
|
||||
|
|
|
@ -31,6 +31,7 @@ import '../../../../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface
|
|||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
|
||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart';
|
||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../../widgets/custom_buttons/draggable_switch_button.dart';
|
||||
import '../../../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
|
@ -238,6 +239,8 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
|
|||
),
|
||||
);
|
||||
|
||||
final isViewOnly = wallet is ViewOnlyOptionInterface && wallet.isViewOnly;
|
||||
|
||||
return DesktopDialog(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
@ -257,7 +260,7 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
|
|||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
if (wallet.info.coin is Firo)
|
||||
if (!isViewOnly && wallet.info.coin is Firo)
|
||||
_MoreFeaturesItem(
|
||||
label: "Anonymize funds",
|
||||
detail: "Anonymize funds",
|
||||
|
@ -300,14 +303,14 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
|
|||
iconAsset: Assets.svg.monkey,
|
||||
onPressed: () async => widget.onMonkeyPressed?.call(),
|
||||
),
|
||||
if (wallet is CashFusionInterface)
|
||||
if (!isViewOnly && wallet is CashFusionInterface)
|
||||
_MoreFeaturesItem(
|
||||
label: "Fusion",
|
||||
detail: "Decentralized mixing protocol",
|
||||
iconAsset: Assets.svg.cashFusion,
|
||||
onPressed: () async => widget.onFusionPressed?.call(),
|
||||
),
|
||||
if (wallet is LibMoneroWallet)
|
||||
if (!isViewOnly && wallet is LibMoneroWallet)
|
||||
_MoreFeaturesItem(
|
||||
label: "Churn",
|
||||
detail: "Churning",
|
||||
|
|
|
@ -10,20 +10,22 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../frost_route_generator.dart';
|
||||
import '../../../../pages/send_view/frost_ms/frost_send_view.dart';
|
||||
import '../../../../pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart';
|
||||
import '../../my_stack_view.dart';
|
||||
import 'desktop_receive.dart';
|
||||
import 'desktop_send.dart';
|
||||
import 'desktop_token_send.dart';
|
||||
import '../../../../providers/global/wallets_provider.dart';
|
||||
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../widgets/custom_tab_view.dart';
|
||||
import '../../../../widgets/desktop/secondary_button.dart';
|
||||
import '../../../../widgets/frost_scaffold.dart';
|
||||
import '../../../../widgets/rounded_white_container.dart';
|
||||
import '../../my_stack_view.dart';
|
||||
import 'desktop_receive.dart';
|
||||
import 'desktop_send.dart';
|
||||
import 'desktop_token_send.dart';
|
||||
|
||||
class MyWallet extends ConsumerStatefulWidget {
|
||||
const MyWallet({
|
||||
|
@ -48,6 +50,7 @@ class _MyWalletState extends ConsumerState<MyWallet> {
|
|||
late final bool isEth;
|
||||
late final CryptoCurrency coin;
|
||||
late final bool isFrost;
|
||||
late final bool isViewOnly;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -60,11 +63,34 @@ class _MyWalletState extends ConsumerState<MyWallet> {
|
|||
titles.add("Transactions");
|
||||
}
|
||||
|
||||
isViewOnly = wallet is ViewOnlyOptionInterface && wallet.isViewOnly;
|
||||
if (isViewOnly) {
|
||||
titles.remove("Receive");
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isViewOnly) {
|
||||
return ListView(
|
||||
primary: false,
|
||||
children: [
|
||||
RoundedWhiteContainer(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: DesktopReceive(
|
||||
walletId: widget.walletId,
|
||||
contractAddress: widget.contractAddress,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return ListView(
|
||||
primary: false,
|
||||
children: [
|
||||
|
|
|
@ -26,6 +26,7 @@ import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
|||
import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../../../widgets/desktop/primary_button.dart';
|
||||
|
@ -100,7 +101,11 @@ class _UnlockWalletKeysDesktopState
|
|||
throw Exception("FIXME ~= see todo in code");
|
||||
}
|
||||
} else {
|
||||
words = await wallet.getMnemonicAsWords();
|
||||
if (wallet is ViewOnlyOptionInterface) {
|
||||
// TODO: is something needed here?
|
||||
} else {
|
||||
words = await wallet.getMnemonicAsWords();
|
||||
}
|
||||
}
|
||||
|
||||
KeyDataInterface? keyData;
|
||||
|
@ -347,7 +352,11 @@ class _UnlockWalletKeysDesktopState
|
|||
throw Exception("FIXME ~= see todo in code");
|
||||
}
|
||||
} else {
|
||||
words = await wallet.getMnemonicAsWords();
|
||||
if (wallet is ViewOnlyOptionInterface) {
|
||||
// TODO: is something needed here?
|
||||
} else {
|
||||
words = await wallet.getMnemonicAsWords();
|
||||
}
|
||||
}
|
||||
|
||||
KeyDataInterface? keyData;
|
||||
|
|
|
@ -182,17 +182,18 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
|
|||
: keyData != null
|
||||
? CustomTabView(
|
||||
titles: [
|
||||
"Mnemonic",
|
||||
if (words.isNotEmpty) "Mnemonic",
|
||||
if (keyData is XPrivData) "XPriv(s)",
|
||||
if (keyData is CWKeyData) "Keys",
|
||||
],
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: _Mnemonic(
|
||||
words: words,
|
||||
if (words.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: _Mnemonic(
|
||||
words: words,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (keyData is XPrivData)
|
||||
WalletXPrivs(
|
||||
xprivData: keyData as XPrivData,
|
||||
|
|
|
@ -37,6 +37,7 @@ import 'pages/add_wallet_views/new_wallet_options/new_wallet_options_view.dart';
|
|||
import 'pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart';
|
||||
import 'pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart';
|
||||
import 'pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart';
|
||||
import 'pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart';
|
||||
import 'pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart';
|
||||
import 'pages/add_wallet_views/select_wallet_for_token_view.dart';
|
||||
import 'pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart';
|
||||
|
@ -130,6 +131,7 @@ import 'pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_bac
|
|||
import 'pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_view.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/change_representative_view.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_view_only_wallet_keys_view.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart';
|
||||
|
@ -203,6 +205,7 @@ import 'wallets/crypto_currency/intermediate/frost_currency.dart';
|
|||
import 'wallets/models/tx_data.dart';
|
||||
import 'wallets/wallet/wallet.dart';
|
||||
import 'wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import 'wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import 'widgets/choose_coin_view.dart';
|
||||
import 'widgets/frost_scaffold.dart';
|
||||
|
||||
|
@ -1519,6 +1522,28 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case RestoreViewOnlyWalletView.routeName:
|
||||
if (args is ({
|
||||
String walletName,
|
||||
CryptoCurrency coin,
|
||||
DateTime? restoreFromDate,
|
||||
bool enableLelantusScanning,
|
||||
})) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => RestoreViewOnlyWalletView(
|
||||
walletName: args.walletName,
|
||||
coin: args.coin,
|
||||
restoreFromDate: args.restoreFromDate,
|
||||
enableLelantusScanning: args.enableLelantusScanning,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case NewWalletRecoveryPhraseView.routeName:
|
||||
if (args is Tuple2<Wallet, List<String>>) {
|
||||
return getRoute(
|
||||
|
@ -1927,6 +1952,21 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case DeleteViewOnlyWalletKeysView.routeName:
|
||||
if (args is ({String walletId, ViewOnlyWalletData data})) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => DeleteViewOnlyWalletKeysView(
|
||||
data: args.data,
|
||||
walletId: args.walletId,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
// exchange steps
|
||||
|
||||
case Step1View.routeName:
|
||||
|
|
|
@ -9,10 +9,15 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../themes/stack_colors.dart';
|
||||
import '../utilities/constants.dart';
|
||||
import '../utilities/text_styles.dart';
|
||||
import '../utilities/util.dart';
|
||||
import 'icon_widgets/clipboard_icon.dart';
|
||||
import 'icon_widgets/x_icon.dart';
|
||||
import 'textfield_icon_button.dart';
|
||||
|
||||
InputDecoration standardInputDecoration(
|
||||
String? labelText,
|
||||
|
@ -52,3 +57,113 @@ InputDecoration standardInputDecoration(
|
|||
focusedErrorBorder: InputBorder.none,
|
||||
);
|
||||
}
|
||||
|
||||
class FullTextField extends StatefulWidget {
|
||||
const FullTextField({
|
||||
super.key,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
required this.label,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final String label;
|
||||
|
||||
final TextEditingController? controller;
|
||||
final FocusNode? focusNode;
|
||||
|
||||
final void Function(String)? onChanged;
|
||||
|
||||
@override
|
||||
State<FullTextField> createState() => _FullTextFieldState();
|
||||
}
|
||||
|
||||
class _FullTextFieldState extends State<FullTextField> {
|
||||
late final TextEditingController controller;
|
||||
late final FocusNode focusNode;
|
||||
|
||||
bool _hasValue = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = widget.controller ?? TextEditingController();
|
||||
focusNode = widget.focusNode ?? FocusNode();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.controller == null) {
|
||||
controller.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
onChanged: (newValue) {
|
||||
widget.onChanged?.call(newValue);
|
||||
},
|
||||
focusNode: focusNode,
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
widget.label,
|
||||
focusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
top: 6,
|
||||
bottom: 8,
|
||||
right: 5,
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: controller.text.isEmpty
|
||||
? const EdgeInsets.only(right: 8)
|
||||
: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
TextFieldIconButton(
|
||||
onTap: () async {
|
||||
if (_hasValue) {
|
||||
controller.text = "";
|
||||
|
||||
setState(() {
|
||||
_hasValue = false;
|
||||
});
|
||||
} else {
|
||||
final data =
|
||||
await Clipboard.getData(Clipboard.kTextPlain);
|
||||
if (data?.text != null && data!.text!.isNotEmpty) {
|
||||
final content = data.text!.trim();
|
||||
|
||||
controller.text = content;
|
||||
setState(() {
|
||||
_hasValue = content.isNotEmpty;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
widget.onChanged?.call(controller.text);
|
||||
},
|
||||
child: _hasValue ? const XIcon() : const ClipboardIcon(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue