Merge remote-tracking branch 'origin/staging' into hideex

This commit is contained in:
sneurlax 2024-07-05 14:37:22 -05:00
commit 38defea3d1
29 changed files with 744 additions and 156 deletions

View file

@ -0,0 +1,21 @@
import 'key_data_interface.dart';
class CWKeyData with KeyDataInterface {
CWKeyData({
required this.walletId,
required String? privateSpendKey,
required String? privateViewKey,
required String? publicSpendKey,
required String? publicViewKey,
}) : keys = List.unmodifiable([
(label: "Public View Key", key: publicViewKey),
(label: "Private View Key", key: privateViewKey),
(label: "Public Spend Key", key: publicSpendKey),
(label: "Private Spend Key", key: privateSpendKey),
]);
@override
final String walletId;
final List<({String label, String key})> keys;
}

View file

@ -0,0 +1,3 @@
mixin KeyDataInterface {
String get walletId;
}

View file

@ -0,0 +1,17 @@
import '../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
import 'key_data_interface.dart';
class XPrivData with KeyDataInterface {
XPrivData({
required this.walletId,
required this.fingerprint,
required List<XPriv> xprivs,
}) : xprivs = List.unmodifiable(xprivs);
@override
final String walletId;
final String fingerprint;
final List<XPriv> xprivs;
}

View file

@ -12,6 +12,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:wakelock/wakelock.dart';
import '../../pages_desktop_specific/cashfusion/sub_widgets/fusion_progress.dart'; import '../../pages_desktop_specific/cashfusion/sub_widgets/fusion_progress.dart';
import '../../providers/cash_fusion/fusion_progress_ui_state_provider.dart'; import '../../providers/cash_fusion/fusion_progress_ui_state_provider.dart';
@ -84,6 +85,8 @@ class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
message: "Stopping fusion", message: "Stopping fusion",
); );
await Wakelock.disable();
return true; return true;
} else { } else {
return false; return false;
@ -96,6 +99,12 @@ class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
super.initState(); super.initState();
} }
@override
void dispose() {
Wakelock.disable();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool _succeeded = final bool _succeeded =
@ -108,6 +117,8 @@ class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
.watch(fusionProgressUIStateProvider(widget.walletId)) .watch(fusionProgressUIStateProvider(widget.walletId))
.fusionRoundsCompleted; .fusionRoundsCompleted;
Wakelock.enable();
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
return await _requestAndProcessCancel(); return await _requestAndProcessCancel();

View file

@ -175,7 +175,7 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
PrimaryButton( PrimaryButton(
label: "Generate transaction", label: "Generate transaction",
enabled: _userVerifyContinue && enabled: _userVerifyContinue &&
!fieldIsEmptyFlags.reduce((v, e) => v |= e), !fieldIsEmptyFlags.fold(false, (v, e) => v |= e),
onPressed: () async { onPressed: () async {
// collect Share strings // collect Share strings
final sharesCollected = controllers.map((e) => e.text).toList(); final sharesCollected = controllers.map((e) => e.text).toList();

View file

@ -0,0 +1,199 @@
import 'dart:async';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../../models/keys/cw_key_data.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 '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/detail_item.dart';
import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart';
class CNWalletKeys extends StatefulWidget {
const CNWalletKeys({
super.key,
required this.cwKeyData,
required this.walletId,
this.clipboardInterface = const ClipboardWrapper(),
});
final CWKeyData cwKeyData;
final String walletId;
final ClipboardInterface clipboardInterface;
@override
State<CNWalletKeys> createState() => _CNWalletKeysState();
}
class _CNWalletKeysState extends State<CNWalletKeys> {
late String _currentDropDownValue;
String _current(String key) =>
widget.cwKeyData.keys.firstWhere((e) => e.label == key).key;
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.cwKeyData.keys.first.label;
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,
),
DetailItemBase(
horizontal: true,
borderColor: Util.isDesktop
? Theme.of(context).extension<StackColors>()!.textFieldDefaultBG
: null,
title: Text(
"Selected key",
style: STextStyles.itemSubtitle(context),
),
detail: SizedBox(
width: Util.isDesktop ? 200 : 170,
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
value: _currentDropDownValue,
items: [
...widget.cwKeyData.keys.map(
(e) => DropdownMenuItem(
value: e.label,
child: Text(
e.label,
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

@ -13,9 +13,11 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../../app_config.dart'; import '../../../../app_config.dart';
import '../../../../models/keys/cw_key_data.dart';
import '../../../../models/keys/key_data_interface.dart';
import '../../../../models/keys/xpriv_data.dart';
import '../../../../notifications/show_flush_bar.dart'; import '../../../../notifications/show_flush_bar.dart';
import '../../../../themes/stack_colors.dart'; import '../../../../themes/stack_colors.dart';
import '../../../../utilities/address_utils.dart'; import '../../../../utilities/address_utils.dart';
@ -25,17 +27,19 @@ import '../../../../utilities/constants.dart';
import '../../../../utilities/text_styles.dart'; import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart'; import '../../../../utilities/util.dart';
import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
import '../../../../widgets/background.dart'; import '../../../../widgets/background.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/custom_buttons/blue_text_button.dart'; import '../../../../widgets/custom_buttons/blue_text_button.dart';
import '../../../../widgets/custom_buttons/simple_copy_button.dart'; import '../../../../widgets/custom_buttons/simple_copy_button.dart';
import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/desktop/secondary_button.dart';
import '../../../../widgets/detail_item.dart'; import '../../../../widgets/detail_item.dart';
import '../../../../widgets/qr.dart'; import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/rounded_white_container.dart';
import '../../../../widgets/stack_dialog.dart'; import '../../../../widgets/stack_dialog.dart';
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.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_view/transaction_views/transaction_details_view.dart';
import 'cn_wallet_keys.dart';
import 'wallet_xprivs.dart'; import 'wallet_xprivs.dart';
class WalletBackupView extends ConsumerWidget { class WalletBackupView extends ConsumerWidget {
@ -44,8 +48,7 @@ class WalletBackupView extends ConsumerWidget {
required this.walletId, required this.walletId,
required this.mnemonic, required this.mnemonic,
this.frostWalletData, this.frostWalletData,
this.clipboardInterface = const ClipboardWrapper(), this.keyData,
this.xprivData,
}); });
static const String routeName = "/walletBackup"; static const String routeName = "/walletBackup";
@ -58,8 +61,7 @@ class WalletBackupView extends ConsumerWidget {
String keys, String keys,
({String config, String keys})? prevGen, ({String config, String keys})? prevGen,
})? frostWalletData; })? frostWalletData;
final ClipboardInterface clipboardInterface; final KeyDataInterface? keyData;
final ({List<XPriv> xprivs, String fingerprint})? xprivData;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@ -81,57 +83,29 @@ class WalletBackupView extends ConsumerWidget {
style: STextStyles.navBarTitle(context), style: STextStyles.navBarTitle(context),
), ),
actions: [ actions: [
if (xprivData != null) if (keyData != null)
Padding( Padding(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: CustomTextButton( child: CustomTextButton(
text: "xpriv(s)", text: switch (keyData.runtimeType) {
const (XPrivData) => "xpriv(s)",
const (CWKeyData) => "keys",
_ => throw UnimplementedError(
"Don't forget to add your KeyDataInterface here! ${keyData.runtimeType}",
),
},
onTap: () { onTap: () {
Navigator.pushNamed( Navigator.pushNamed(
context, context,
MobileXPrivsView.routeName, MobileKeyDataView.routeName,
arguments: ( arguments: (
walletId: walletId, walletId: walletId,
xprivData: xprivData!, keyData: keyData!,
), ),
); );
}, },
), ),
), ),
if (!frost && xprivData == null)
Padding(
padding: const EdgeInsets.all(10),
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(" ")));
if (context.mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
),
);
}
},
),
),
),
], ],
), ),
body: Padding( body: Padding(
@ -152,19 +126,22 @@ class WalletBackupView extends ConsumerWidget {
} }
class _Mnemonic extends ConsumerWidget { class _Mnemonic extends ConsumerWidget {
const _Mnemonic({super.key, required this.walletId, required this.mnemonic}); const _Mnemonic({
super.key,
required this.walletId,
required this.mnemonic,
this.clipboardInterface = const ClipboardWrapper(),
});
final String walletId; final String walletId;
final List<String> mnemonic; final List<String> mnemonic;
final ClipboardInterface clipboardInterface;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const SizedBox(
height: 4,
),
Text( Text(
ref.watch(pWalletName(walletId)), ref.watch(pWalletName(walletId)),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -212,10 +189,28 @@ class _Mnemonic extends ConsumerWidget {
const SizedBox( const SizedBox(
height: 12, height: 12,
), ),
TextButton( SecondaryButton(
style: Theme.of(context) label: "Copy",
.extension<StackColors>()! onPressed: () async {
.getPrimaryEnabledButtonStyle(context), await clipboardInterface
.setData(ClipboardData(text: mnemonic.join(" ")));
if (context.mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
),
);
}
},
),
const SizedBox(
height: 12,
),
PrimaryButton(
label: "Show QR Code",
onPressed: () { onPressed: () {
final String data = AddressUtils.encodeQRSeedData(mnemonic); final String data = AddressUtils.encodeQRSeedData(mnemonic);
@ -284,10 +279,6 @@ class _Mnemonic extends ConsumerWidget {
}, },
); );
}, },
child: Text(
"Show QR Code",
style: STextStyles.button(context),
),
), ),
], ],
); );
@ -301,8 +292,6 @@ class _FrostKeys extends StatelessWidget {
this.frostWalletData, this.frostWalletData,
}); });
static const String routeName = "/walletBackup";
final String walletId; final String walletId;
final ({ final ({
String myName, String myName,
@ -433,19 +422,19 @@ class _FrostKeys extends StatelessWidget {
} }
} }
class MobileXPrivsView extends StatelessWidget { class MobileKeyDataView extends StatelessWidget {
const MobileXPrivsView({ const MobileKeyDataView({
super.key, super.key,
required this.walletId, required this.walletId,
this.clipboardInterface = const ClipboardWrapper(), this.clipboardInterface = const ClipboardWrapper(),
required this.xprivData, required this.keyData,
}); });
static const String routeName = "/mobileXPrivView"; static const String routeName = "/mobileXPrivView";
final String walletId; final String walletId;
final ClipboardInterface clipboardInterface; final ClipboardInterface clipboardInterface;
final ({List<XPriv> xprivs, String fingerprint}) xprivData; final KeyDataInterface keyData;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -459,7 +448,13 @@ class MobileXPrivsView extends StatelessWidget {
}, },
), ),
title: Text( title: Text(
"Wallet xpriv(s)", "Wallet ${switch (keyData.runtimeType) {
const (XPrivData) => "xpriv(s)",
const (CWKeyData) => "keys",
_ => throw UnimplementedError(
"Don't forget to add your KeyDataInterface here!",
),
}}",
style: STextStyles.navBarTitle(context), style: STextStyles.navBarTitle(context),
), ),
), ),
@ -475,10 +470,19 @@ class MobileXPrivsView extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Expanded( Expanded(
child: WalletXPrivs( child: switch (keyData.runtimeType) {
walletId: walletId, const (XPrivData) => WalletXPrivs(
xprivData: xprivData, walletId: walletId,
), xprivData: keyData as XPrivData,
),
const (CWKeyData) => CNWalletKeys(
walletId: walletId,
cwKeyData: keyData as CWKeyData,
),
_ => throw UnimplementedError(
"Don't forget to add your KeyDataInterface here!",
),
},
), ),
const SizedBox( const SizedBox(
height: 16, height: 16,

View file

@ -16,6 +16,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import '../../../../models/keys/xpriv_data.dart';
import '../../../../notifications/show_flush_bar.dart'; import '../../../../notifications/show_flush_bar.dart';
import '../../../../themes/stack_colors.dart'; import '../../../../themes/stack_colors.dart';
import '../../../../utilities/assets.dart'; import '../../../../utilities/assets.dart';
@ -23,7 +24,6 @@ import '../../../../utilities/clipboard_interface.dart';
import '../../../../utilities/constants.dart'; import '../../../../utilities/constants.dart';
import '../../../../utilities/text_styles.dart'; import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart'; import '../../../../utilities/util.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/detail_item.dart'; import '../../../../widgets/detail_item.dart';
import '../../../../widgets/qr.dart'; import '../../../../widgets/qr.dart';
@ -37,7 +37,7 @@ class WalletXPrivs extends ConsumerStatefulWidget {
this.clipboardInterface = const ClipboardWrapper(), this.clipboardInterface = const ClipboardWrapper(),
}); });
final ({List<XPriv> xprivs, String fingerprint}) xprivData; final XPrivData xprivData;
final String walletId; final String walletId;
final ClipboardInterface clipboardInterface; final ClipboardInterface clipboardInterface;

View file

@ -18,6 +18,7 @@ import 'package:tuple/tuple.dart';
import '../../../db/hive/db.dart'; import '../../../db/hive/db.dart';
import '../../../db/sqlite/firo_cache.dart'; import '../../../db/sqlite/firo_cache.dart';
import '../../../models/epicbox_config_model.dart'; import '../../../models/epicbox_config_model.dart';
import '../../../models/keys/key_data_interface.dart';
import '../../../notifications/show_flush_bar.dart'; import '../../../notifications/show_flush_bar.dart';
import '../../../providers/global/wallets_provider.dart'; import '../../../providers/global/wallets_provider.dart';
import '../../../providers/ui/transaction_filter_provider.dart'; import '../../../providers/ui/transaction_filter_provider.dart';
@ -35,6 +36,7 @@ import '../../../wallets/crypto_currency/intermediate/frost_currency.dart';
import '../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../wallets/crypto_currency/intermediate/nano_currency.dart';
import '../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
import '../../../wallets/wallet/impl/epiccash_wallet.dart'; import '../../../wallets/wallet/impl/epiccash_wallet.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.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/mnemonic_interface.dart';
import '../../../widgets/background.dart'; import '../../../widgets/background.dart';
@ -261,10 +263,6 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
// TODO: [prio=med] take wallets that don't have a mnemonic into account // TODO: [prio=med] take wallets that don't have a mnemonic into account
({
List<XPriv> xprivs,
String fingerprint
})? xprivData;
List<String>? mnemonic; List<String>? mnemonic;
({ ({
String myName, String myName,
@ -306,8 +304,11 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
await wallet.getMnemonicAsWords(); await wallet.getMnemonicAsWords();
} }
KeyDataInterface? keyData;
if (wallet is ExtendedKeysInterface) { if (wallet is ExtendedKeysInterface) {
xprivData = await wallet.getXPrivs(); keyData = await wallet.getXPrivs();
} else if (wallet is CwBasedInterface) {
keyData = await wallet.getKeys();
} }
if (context.mounted) { if (context.mounted) {
@ -323,7 +324,7 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
mnemonic: mnemonic ?? [], mnemonic: mnemonic ?? [],
frostWalletData: frostWalletData:
frostWalletData, frostWalletData,
xprivData: xprivData, keyData: keyData,
), ),
showBackButton: true, showBackButton: true,
routeOnSuccess: routeOnSuccess:

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import '../../models/keys/key_data_interface.dart';
import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart';
import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart'; import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart';
import '../../providers/global/wallets_provider.dart'; import '../../providers/global/wallets_provider.dart';
@ -11,6 +12,7 @@ import '../../utilities/assets.dart';
import '../../utilities/text_styles.dart'; import '../../utilities/text_styles.dart';
import '../../utilities/util.dart'; import '../../utilities/util.dart';
import '../../wallets/isar/providers/wallet_info_provider.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.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/mnemonic_interface.dart';
import '../../widgets/background.dart'; import '../../widgets/background.dart';
@ -265,9 +267,11 @@ class _FiroRescanRecoveryErrorViewState
if (wallet is MnemonicInterface) { if (wallet is MnemonicInterface) {
final mnemonic = await wallet.getMnemonicAsWords(); final mnemonic = await wallet.getMnemonicAsWords();
({List<XPriv> xprivs, String fingerprint})? xprivData; KeyDataInterface? keyData;
if (wallet is ExtendedKeysInterface) { if (wallet is ExtendedKeysInterface) {
xprivData = await wallet.getXPrivs(); keyData = await wallet.getXPrivs();
} else if (wallet is CwBasedInterface) {
keyData = await wallet.getKeys();
} }
if (context.mounted) { if (context.mounted) {
@ -280,7 +284,7 @@ class _FiroRescanRecoveryErrorViewState
routeOnSuccessArguments: ( routeOnSuccessArguments: (
walletId: widget.walletId, walletId: widget.walletId,
mnemonic: mnemonic, mnemonic: mnemonic,
xprivData: xprivData, keyData: keyData,
), ),
showBackButton: true, showBackButton: true,
routeOnSuccess: WalletBackupView.routeName, routeOnSuccess: WalletBackupView.routeName,

View file

@ -1,7 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:wakelock/wakelock.dart';
import '../../../providers/cash_fusion/fusion_progress_ui_state_provider.dart'; import '../../../providers/cash_fusion/fusion_progress_ui_state_provider.dart';
import '../../../providers/global/prefs_provider.dart'; import '../../../providers/global/prefs_provider.dart';
@ -137,6 +139,8 @@ class _FusionDialogViewState extends ConsumerState<FusionDialogView> {
message: "Stopping fusion", message: "Stopping fusion",
); );
await Wakelock.disable();
return true; return true;
} else { } else {
return false; return false;
@ -150,6 +154,12 @@ class _FusionDialogViewState extends ConsumerState<FusionDialogView> {
super.initState(); super.initState();
} }
@override
dispose() {
Wakelock.disable();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool _succeeded = final bool _succeeded =
@ -162,6 +172,10 @@ class _FusionDialogViewState extends ConsumerState<FusionDialogView> {
.watch(fusionProgressUIStateProvider(widget.walletId)) .watch(fusionProgressUIStateProvider(widget.walletId))
.fusionRoundsCompleted; .fusionRoundsCompleted;
if (!Platform.isLinux) {
Wakelock.enable();
}
return DesktopDialog( return DesktopDialog(
maxHeight: 600, maxHeight: 600,
child: SingleChildScrollView( child: SingleChildScrollView(

View file

@ -338,7 +338,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
onTap: () { onTap: () {
clipboard.setData( clipboard.setData(
ClipboardData( ClipboardData(
text: ref.watch(pWalletReceivingAddress(walletId)), text: address,
), ),
); );
showFloatingFlushBar( showFloatingFlushBar(

View file

@ -19,6 +19,7 @@ import '../../../../../providers/global/wallets_provider.dart';
import '../../../../../themes/stack_colors.dart'; import '../../../../../themes/stack_colors.dart';
import '../../../../../utilities/assets.dart'; import '../../../../../utilities/assets.dart';
import '../../../../../utilities/text_styles.dart'; import '../../../../../utilities/text_styles.dart';
import '../../../../../utilities/util.dart';
import '../../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../../wallets/isar/models/wallet_info.dart'; import '../../../../../wallets/isar/models/wallet_info.dart';
import '../../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../../wallets/isar/providers/wallet_info_provider.dart';
@ -32,7 +33,10 @@ import '../../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.da
import '../../../../../widgets/custom_buttons/draggable_switch_button.dart'; import '../../../../../widgets/custom_buttons/draggable_switch_button.dart';
import '../../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../../widgets/desktop/desktop_dialog_close_button.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 '../../../../../widgets/rounded_container.dart';
import '../../../../../widgets/stack_dialog.dart';
class MoreFeaturesDialog extends ConsumerStatefulWidget { class MoreFeaturesDialog extends ConsumerStatefulWidget {
const MoreFeaturesDialog({ const MoreFeaturesDialog({
@ -102,6 +106,147 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
} }
} }
bool _switchReuseAddressToggledLock = false; // Mutex.
Future<void> _switchReuseAddressToggled(bool newValue) async {
if (newValue) {
await showDialog(
context: context,
builder: (context) {
final isDesktop = Util.isDesktop;
return isDesktop
? DesktopDialog(
maxWidth: 576,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 32),
child: Text(
"Warning!",
style: STextStyles.desktopH3(context),
),
),
const DesktopDialogCloseButton(),
],
),
Padding(
padding: const EdgeInsets.only(
top: 8,
left: 32,
right: 32,
bottom: 32,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Reusing addresses reduces your privacy and security. Are you sure you want to reuse addresses by default?",
style: STextStyles.desktopTextSmall(context),
),
const SizedBox(
height: 43,
),
Row(
children: [
Expanded(
child: SecondaryButton(
buttonHeight: ButtonHeight.l,
onPressed: () {
Navigator.of(context).pop(false);
},
label: "Cancel",
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
buttonHeight: ButtonHeight.l,
onPressed: () {
Navigator.of(context).pop(true);
},
label: "Continue",
),
),
],
),
],
),
),
],
),
)
: StackDialog(
title: "Warning!",
message:
"Reusing addresses reduces your privacy and security. Are you sure you want to reuse addresses by default?",
leftButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonStyle(context),
child: Text(
"Cancel",
style: STextStyles.itemSubtitle12(context),
),
onPressed: () {
Navigator.of(context).pop(false);
},
),
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonStyle(context),
child: Text(
"Continue",
style: STextStyles.button(context),
),
onPressed: () {
Navigator.of(context).pop(true);
},
),
);
},
).then((confirmed) async {
if (_switchReuseAddressToggledLock) {
return;
}
_switchReuseAddressToggledLock = true; // Lock mutex.
try {
if (confirmed == true) {
await ref.read(pWalletInfo(widget.walletId)).updateOtherData(
newEntries: {
WalletInfoKeys.reuseAddress: true,
},
isar: ref.read(mainDBProvider).isar,
);
} else {
await ref.read(pWalletInfo(widget.walletId)).updateOtherData(
newEntries: {
WalletInfoKeys.reuseAddress: false,
},
isar: ref.read(mainDBProvider).isar,
);
}
} finally {
// ensure _switchReuseAddressToggledLock is set to false no matter what.
_switchReuseAddressToggledLock = false;
}
});
} else {
await ref.read(pWalletInfo(widget.walletId)).updateOtherData(
newEntries: {
WalletInfoKeys.reuseAddress: false,
},
isar: ref.read(mainDBProvider).isar,
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final wallet = ref.watch( final wallet = ref.watch(
@ -253,6 +398,38 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
], ],
), ),
), ),
// reuseAddress preference.
_MoreFeaturesItemBase(
child: Row(
children: [
const SizedBox(width: 3),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: ref.watch(
pWalletInfo(widget.walletId)
.select((value) => value.otherData),
)[WalletInfoKeys.reuseAddress] as bool? ??
false,
onValueChanged: _switchReuseAddressToggled,
),
),
const SizedBox(
width: 16,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Reuse receiving address by default",
style: STextStyles.w600_20(context),
),
],
),
],
),
),
const SizedBox( const SizedBox(
height: 28, height: 28,
), ),

View file

@ -14,6 +14,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import '../../../../models/keys/key_data_interface.dart';
import '../../../../notifications/show_flush_bar.dart'; import '../../../../notifications/show_flush_bar.dart';
import '../../../../providers/desktop/storage_crypto_handler_provider.dart'; import '../../../../providers/desktop/storage_crypto_handler_provider.dart';
import '../../../../providers/providers.dart'; import '../../../../providers/providers.dart';
@ -22,6 +23,7 @@ import '../../../../utilities/assets.dart';
import '../../../../utilities/constants.dart'; import '../../../../utilities/constants.dart';
import '../../../../utilities/text_styles.dart'; import '../../../../utilities/text_styles.dart';
import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.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/mnemonic_interface.dart';
import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog.dart';
@ -85,7 +87,6 @@ class _UnlockWalletKeysDesktopState
final wallet = ref.read(pWallets).getWallet(widget.walletId); final wallet = ref.read(pWallets).getWallet(widget.walletId);
({String keys, String config})? frostData; ({String keys, String config})? frostData;
List<String>? words; List<String>? words;
({List<XPriv> xprivs, String fingerprint})? xprivData;
// TODO: [prio=low] handle wallets that don't have a mnemonic // TODO: [prio=low] handle wallets that don't have a mnemonic
// All wallets currently are mnemonic based // All wallets currently are mnemonic based
@ -102,8 +103,11 @@ class _UnlockWalletKeysDesktopState
words = await wallet.getMnemonicAsWords(); words = await wallet.getMnemonicAsWords();
} }
KeyDataInterface? keyData;
if (wallet is ExtendedKeysInterface) { if (wallet is ExtendedKeysInterface) {
xprivData = await wallet.getXPrivs(); keyData = await wallet.getXPrivs();
} else if (wallet is CwBasedInterface) {
keyData = await wallet.getKeys();
} }
if (mounted) { if (mounted) {
@ -113,7 +117,7 @@ class _UnlockWalletKeysDesktopState
mnemonic: words ?? [], mnemonic: words ?? [],
walletId: widget.walletId, walletId: widget.walletId,
frostData: frostData, frostData: frostData,
xprivData: xprivData, keyData: keyData,
), ),
); );
} }
@ -327,10 +331,6 @@ class _UnlockWalletKeysDesktopState
({String keys, String config})? frostData; ({String keys, String config})? frostData;
List<String>? words; List<String>? words;
({
List<XPriv> xprivs,
String fingerprint
})? xprivData;
final wallet = final wallet =
ref.read(pWallets).getWallet(widget.walletId); ref.read(pWallets).getWallet(widget.walletId);
@ -350,11 +350,14 @@ class _UnlockWalletKeysDesktopState
words = await wallet.getMnemonicAsWords(); words = await wallet.getMnemonicAsWords();
} }
KeyDataInterface? keyData;
if (wallet is ExtendedKeysInterface) { if (wallet is ExtendedKeysInterface) {
xprivData = await wallet.getXPrivs(); keyData = await wallet.getXPrivs();
} else if (wallet is CwBasedInterface) {
keyData = await wallet.getKeys();
} }
if (mounted) { if (context.mounted) {
await Navigator.of(context) await Navigator.of(context)
.pushReplacementNamed( .pushReplacementNamed(
WalletKeysDesktopPopup.routeName, WalletKeysDesktopPopup.routeName,
@ -362,7 +365,7 @@ class _UnlockWalletKeysDesktopState
mnemonic: words ?? [], mnemonic: words ?? [],
walletId: widget.walletId, walletId: widget.walletId,
frostData: frostData, frostData: frostData,
xprivData: xprivData, keyData: keyData,
), ),
); );
} }

View file

@ -14,8 +14,12 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../models/keys/cw_key_data.dart';
import '../../../../models/keys/key_data_interface.dart';
import '../../../../models/keys/xpriv_data.dart';
import '../../../../notifications/show_flush_bar.dart'; import '../../../../notifications/show_flush_bar.dart';
import '../../../../pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.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/cn_wallet_keys.dart';
import '../../../../pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.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 '../../../../pages/wallet_view/transaction_views/transaction_details_view.dart';
import '../../../../themes/stack_colors.dart'; import '../../../../themes/stack_colors.dart';
@ -23,7 +27,6 @@ import '../../../../utilities/address_utils.dart';
import '../../../../utilities/assets.dart'; import '../../../../utilities/assets.dart';
import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/clipboard_interface.dart';
import '../../../../utilities/text_styles.dart'; import '../../../../utilities/text_styles.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
import '../../../../widgets/custom_tab_view.dart'; import '../../../../widgets/custom_tab_view.dart';
import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
@ -39,14 +42,14 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
required this.walletId, required this.walletId,
this.frostData, this.frostData,
this.clipboardInterface = const ClipboardWrapper(), this.clipboardInterface = const ClipboardWrapper(),
this.xprivData, this.keyData,
}); });
final List<String> words; final List<String> words;
final String walletId; final String walletId;
final ({String keys, String config})? frostData; final ({String keys, String config})? frostData;
final ClipboardInterface clipboardInterface; final ClipboardInterface clipboardInterface;
final ({List<XPriv> xprivs, String fingerprint})? xprivData; final KeyDataInterface? keyData;
static const String routeName = "walletKeysDesktopPopup"; static const String routeName = "walletKeysDesktopPopup";
@ -176,9 +179,13 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
), ),
], ],
) )
: xprivData != null : keyData != null
? CustomTabView( ? CustomTabView(
titles: const ["Mnemonic", "XPriv(s)"], titles: [
"Mnemonic",
if (keyData is XPrivData) "XPriv(s)",
if (keyData is CWKeyData) "Keys",
],
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(top: 16), padding: const EdgeInsets.only(top: 16),
@ -186,10 +193,16 @@ class WalletKeysDesktopPopup extends ConsumerWidget {
words: words, words: words,
), ),
), ),
WalletXPrivs( if (keyData is XPrivData)
xprivData: xprivData!, WalletXPrivs(
walletId: walletId, xprivData: keyData as XPrivData,
), walletId: walletId,
),
if (keyData is CWKeyData)
CNWalletKeys(
cwKeyData: keyData as CWKeyData,
walletId: walletId,
),
], ],
) )
: _Mnemonic( : _Mnemonic(
@ -264,22 +277,6 @@ class _Mnemonic extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: SecondaryButton( child: SecondaryButton(
label: "Show QR code",
onPressed: () {
// TODO: address utils
final String value = AddressUtils.encodeQRSeedData(words);
Navigator.of(context).pushNamed(
QRCodeDesktopPopupContent.routeName,
arguments: value,
);
},
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Copy", label: "Copy",
onPressed: () async { onPressed: () async {
await clipboardInterface.setData( await clipboardInterface.setData(
@ -298,6 +295,22 @@ class _Mnemonic extends StatelessWidget {
}, },
), ),
), ),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Show QR code",
onPressed: () {
// TODO: address utils
final String value = AddressUtils.encodeQRSeedData(words);
Navigator.of(context).pushNamed(
QRCodeDesktopPopupContent.routeName,
arguments: value,
);
},
),
),
], ],
), ),
), ),

View file

@ -22,6 +22,7 @@ import 'models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'models/isar/models/contact_entry.dart'; import 'models/isar/models/contact_entry.dart';
import 'models/isar/models/isar_models.dart'; import 'models/isar/models/isar_models.dart';
import 'models/isar/ordinal.dart'; import 'models/isar/ordinal.dart';
import 'models/keys/key_data_interface.dart';
import 'models/paynym/paynym_account_lite.dart'; import 'models/paynym/paynym_account_lite.dart';
import 'models/send_view_auto_fill_data.dart'; import 'models/send_view_auto_fill_data.dart';
import 'pages/add_wallet_views/add_token_view/add_custom_token_view.dart'; import 'pages/add_wallet_views/add_token_view/add_custom_token_view.dart';
@ -1280,14 +1281,14 @@ class RouteGenerator {
} else if (args is ({ } else if (args is ({
String walletId, String walletId,
List<String> mnemonic, List<String> mnemonic,
({List<XPriv> xprivs, String fingerprint})? xprivData, KeyDataInterface? keyData,
})) { })) {
return getRoute( return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute, shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => WalletBackupView( builder: (_) => WalletBackupView(
walletId: args.walletId, walletId: args.walletId,
mnemonic: args.mnemonic, mnemonic: args.mnemonic,
xprivData: args.xprivData, keyData: args.keyData,
), ),
settings: RouteSettings( settings: RouteSettings(
name: settings.name, name: settings.name,
@ -1296,7 +1297,7 @@ class RouteGenerator {
} else if (args is ({ } else if (args is ({
String walletId, String walletId,
List<String> mnemonic, List<String> mnemonic,
({List<XPriv> xprivs, String fingerprint})? xprivData, KeyDataInterface? keyData,
({ ({
String myName, String myName,
String config, String config,
@ -1310,7 +1311,7 @@ class RouteGenerator {
walletId: args.walletId, walletId: args.walletId,
mnemonic: args.mnemonic, mnemonic: args.mnemonic,
frostWalletData: args.frostWalletData, frostWalletData: args.frostWalletData,
xprivData: args.xprivData, keyData: args.keyData,
), ),
settings: RouteSettings( settings: RouteSettings(
name: settings.name, name: settings.name,
@ -1319,16 +1320,16 @@ class RouteGenerator {
} }
return _routeError("${settings.name} invalid args: ${args.toString()}"); return _routeError("${settings.name} invalid args: ${args.toString()}");
case MobileXPrivsView.routeName: case MobileKeyDataView.routeName:
if (args is ({ if (args is ({
String walletId, String walletId,
({List<XPriv> xprivs, String fingerprint}) xprivData, KeyDataInterface keyData,
})) { })) {
return getRoute( return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute, shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => MobileXPrivsView( builder: (_) => MobileKeyDataView(
walletId: args.walletId, walletId: args.walletId,
xprivData: args.xprivData, keyData: args.keyData,
), ),
settings: RouteSettings( settings: RouteSettings(
name: settings.name, name: settings.name,
@ -2369,14 +2370,14 @@ class RouteGenerator {
List<String> mnemonic, List<String> mnemonic,
String walletId, String walletId,
({String keys, String config})? frostData, ({String keys, String config})? frostData,
({List<XPriv> xprivs, String fingerprint})? xprivData, KeyDataInterface? keyData,
})) { })) {
return FadePageRoute( return FadePageRoute(
WalletKeysDesktopPopup( WalletKeysDesktopPopup(
words: args.mnemonic, words: args.mnemonic,
walletId: args.walletId, walletId: args.walletId,
frostData: args.frostData, frostData: args.frostData,
xprivData: args.xprivData, keyData: args.keyData,
), ),
RouteSettings( RouteSettings(
name: settings.name, name: settings.name,
@ -2385,13 +2386,13 @@ class RouteGenerator {
} else if (args is ({ } else if (args is ({
List<String> mnemonic, List<String> mnemonic,
String walletId, String walletId,
({List<XPriv> xprivs, String fingerprint})? xprivData, KeyDataInterface? keyData,
})) { })) {
return FadePageRoute( return FadePageRoute(
WalletKeysDesktopPopup( WalletKeysDesktopPopup(
words: args.mnemonic, words: args.mnemonic,
walletId: args.walletId, walletId: args.walletId,
xprivData: args.xprivData, keyData: args.keyData,
), ),
RouteSettings( RouteSettings(
name: settings.name, name: settings.name,

View file

@ -511,4 +511,5 @@ abstract class WalletInfoKeys {
static const String firoSparkCacheSetTimestampCache = static const String firoSparkCacheSetTimestampCache =
"firoSparkCacheSetTimestampCacheKey"; "firoSparkCacheSetTimestampCacheKey";
static const String enableOptInRbf = "enableOptInRbfKey"; static const String enableOptInRbf = "enableOptInRbfKey";
static const String reuseAddress = "reuseAddressKey";
} }

View file

@ -133,21 +133,41 @@ class TxData {
.reduce((total, amount) => total += amount) .reduce((total, amount) => total += amount)
: null; : null;
Amount? get amountWithoutChange => Amount? get amountWithoutChange {
recipients != null && recipients!.isNotEmpty if (recipients != null && recipients!.isNotEmpty) {
? recipients! if (recipients!.where((e) => !e.isChange).isEmpty) {
.where((e) => !e.isChange) return Amount(
.map((e) => e.amount) rawValue: BigInt.zero,
.reduce((total, amount) => total += amount) fractionDigits: recipients!.first.amount.fractionDigits,
: null; );
} else {
return recipients!
.where((e) => !e.isChange)
.map((e) => e.amount)
.reduce((total, amount) => total += amount);
}
} else {
return null;
}
}
Amount? get amountSparkWithoutChange => Amount? get amountSparkWithoutChange {
sparkRecipients != null && sparkRecipients!.isNotEmpty if (sparkRecipients != null && sparkRecipients!.isNotEmpty) {
? sparkRecipients! if (sparkRecipients!.where((e) => !e.isChange).isEmpty) {
.where((e) => !e.isChange) return Amount(
.map((e) => e.amount) rawValue: BigInt.zero,
.reduce((total, amount) => total += amount) fractionDigits: sparkRecipients!.first.amount.fractionDigits,
: null; );
} else {
return sparkRecipients!
.where((e) => !e.isChange)
.map((e) => e.amount)
.reduce((total, amount) => total += amount);
}
} else {
return null;
}
}
int? get estimatedSatsPerVByte => fee != null && vSize != null int? get estimatedSatsPerVByte => fee != null && vSize != null
? (fee!.raw ~/ BigInt.from(vSize!)).toInt() ? (fee!.raw ~/ BigInt.from(vSize!)).toInt()

View file

@ -787,7 +787,9 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
for (final tuple in receiveResults) { for (final tuple in receiveResults) {
if (tuple.addresses.isEmpty) { if (tuple.addresses.isEmpty) {
await checkReceivingAddressForTransactions(); if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
await checkReceivingAddressForTransactions();
}
} else { } else {
highestReceivingIndexWithHistory = max( highestReceivingIndexWithHistory = max(
tuple.index, tuple.index,

View file

@ -25,6 +25,7 @@ import 'package:tuple/tuple.dart';
import '../../../db/hive/db.dart'; import '../../../db/hive/db.dart';
import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/isar/models/blockchain_data/address.dart';
import '../../../models/isar/models/blockchain_data/transaction.dart'; import '../../../models/isar/models/blockchain_data/transaction.dart';
import '../../../models/keys/cw_key_data.dart';
import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart'; import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart';
import '../../../services/event_bus/events/global/tor_status_changed_event.dart'; import '../../../services/event_bus/events/global/tor_status_changed_event.dart';
import '../../../services/event_bus/global_event_bus.dart'; import '../../../services/event_bus/global_event_bus.dart';
@ -235,6 +236,25 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
return; return;
} }
@override
Future<CWKeyData?> getKeys() async {
final base = (CwBasedInterface.cwWalletBase as MoneroWalletBase?);
if (base == null ||
base.walletInfo.name != walletId ||
CwBasedInterface.exitMutex.isLocked) {
return null;
}
return CWKeyData(
walletId: walletId,
publicViewKey: base.keys.publicViewKey,
privateViewKey: base.keys.privateViewKey,
publicSpendKey: base.keys.publicSpendKey,
privateSpendKey: base.keys.privateSpendKey,
);
}
@override @override
Future<void> updateTransactions() async { Future<void> updateTransactions() async {
final base = (CwBasedInterface.cwWalletBase as MoneroWalletBase?); final base = (CwBasedInterface.cwWalletBase as MoneroWalletBase?);

View file

@ -28,6 +28,7 @@ import 'package:tuple/tuple.dart';
import '../../../db/hive/db.dart'; import '../../../db/hive/db.dart';
import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/isar/models/blockchain_data/address.dart';
import '../../../models/isar/models/blockchain_data/transaction.dart'; import '../../../models/isar/models/blockchain_data/transaction.dart';
import '../../../models/keys/cw_key_data.dart';
import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart'; import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart';
import '../../../services/event_bus/events/global/tor_status_changed_event.dart'; import '../../../services/event_bus/events/global/tor_status_changed_event.dart';
import '../../../services/event_bus/global_event_bus.dart'; import '../../../services/event_bus/global_event_bus.dart';
@ -214,6 +215,25 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
return; return;
} }
@override
Future<CWKeyData?> getKeys() async {
final base = (CwBasedInterface.cwWalletBase as WowneroWalletBase?);
if (base == null ||
base.walletInfo.name != walletId ||
CwBasedInterface.exitMutex.isLocked) {
return null;
}
return CWKeyData(
walletId: walletId,
publicViewKey: base.keys.publicViewKey,
privateViewKey: base.keys.privateViewKey,
publicSpendKey: base.keys.publicSpendKey,
privateSpendKey: base.keys.privateSpendKey,
);
}
@override @override
Future<void> updateTransactions() async { Future<void> updateTransactions() async {
final base = (CwBasedInterface.cwWalletBase as WowneroWalletBase?); final base = (CwBasedInterface.cwWalletBase as WowneroWalletBase?);

View file

@ -522,8 +522,10 @@ abstract class Wallet<T extends CryptoCurrency> {
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is MultiAddressInterface) { if (this is MultiAddressInterface) {
await (this as MultiAddressInterface) if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
.checkReceivingAddressForTransactions(); await (this as MultiAddressInterface)
.checkReceivingAddressForTransactions();
}
} }
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));

View file

@ -14,6 +14,7 @@ import 'package:mutex/mutex.dart';
import '../../../models/balance.dart'; import '../../../models/balance.dart';
import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/isar/models/blockchain_data/address.dart';
import '../../../models/keys/cw_key_data.dart';
import '../../../models/paymint/fee_object_model.dart'; import '../../../models/paymint/fee_object_model.dart';
import '../../../services/event_bus/events/global/blocks_remaining_event.dart'; import '../../../services/event_bus/events/global/blocks_remaining_event.dart';
import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart'; import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart';
@ -24,6 +25,7 @@ import '../../../utilities/amount/amount.dart';
import '../../../utilities/logger.dart'; import '../../../utilities/logger.dart';
import '../../../utilities/stack_file_system.dart'; import '../../../utilities/stack_file_system.dart';
import '../../crypto_currency/intermediate/cryptonote_currency.dart'; import '../../crypto_currency/intermediate/cryptonote_currency.dart';
import '../../isar/models/wallet_info.dart';
import '../intermediate/cryptonote_wallet.dart'; import '../intermediate/cryptonote_wallet.dart';
import 'multi_address_interface.dart'; import 'multi_address_interface.dart';
@ -195,6 +197,8 @@ mixin CwBasedInterface<T extends CryptonoteCurrency> on CryptonoteWallet<T>
Address addressFor({required int index, int account = 0}); Address addressFor({required int index, int account = 0});
Future<CWKeyData?> getKeys();
// ============ Private ====================================================== // ============ Private ======================================================
Future<void> _refreshTxDataHelper() async { Future<void> _refreshTxDataHelper() async {
if (_txRefreshLock) return; if (_txRefreshLock) return;
@ -283,7 +287,9 @@ mixin CwBasedInterface<T extends CryptonoteCurrency> on CryptonoteWallet<T>
await updateTransactions(); await updateTransactions();
await updateBalance(); await updateBalance();
await checkReceivingAddressForTransactions(); if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
await checkReceivingAddressForTransactions();
}
if (cwWalletBase?.syncStatus is SyncedSyncStatus) { if (cwWalletBase?.syncStatus is SyncedSyncStatus) {
refreshMutex.release(); refreshMutex.release();
@ -339,6 +345,17 @@ mixin CwBasedInterface<T extends CryptonoteCurrency> on CryptonoteWallet<T>
@override @override
Future<void> checkReceivingAddressForTransactions() async { Future<void> checkReceivingAddressForTransactions() async {
if (info.otherData[WalletInfoKeys.reuseAddress] == true) {
try {
throw Exception();
} catch (_, s) {
Logging.instance.log(
"checkReceivingAddressForTransactions called but reuse address flag set: $s",
level: LogLevel.Error,
);
}
}
try { try {
int highestIndex = -1; int highestIndex = -1;
final entries = cwWalletBase?.transactionHistory?.transactions?.entries; final entries = cwWalletBase?.transactionHistory?.transactions?.entries;
@ -377,8 +394,10 @@ mixin CwBasedInterface<T extends CryptonoteCurrency> on CryptonoteWallet<T>
// we need to update the address // we need to update the address
await mainDB.updateAddress(existing, newReceivingAddress); await mainDB.updateAddress(existing, newReceivingAddress);
} }
// keep checking until address with no tx history is set as current if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
await checkReceivingAddressForTransactions(); // keep checking until address with no tx history is set as current
await checkReceivingAddressForTransactions();
}
} }
} on SocketException catch (se, s) { } on SocketException catch (se, s) {
Logging.instance.log( Logging.instance.log(

View file

@ -23,6 +23,7 @@ import '../../../utilities/logger.dart';
import '../../../utilities/paynym_is_api.dart'; import '../../../utilities/paynym_is_api.dart';
import '../../crypto_currency/coins/firo.dart'; import '../../crypto_currency/coins/firo.dart';
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
import '../../isar/models/wallet_info.dart';
import '../../models/tx_data.dart'; import '../../models/tx_data.dart';
import '../impl/bitcoin_wallet.dart'; import '../impl/bitcoin_wallet.dart';
import '../impl/firo_wallet.dart'; import '../impl/firo_wallet.dart';
@ -1315,6 +1316,17 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
@override @override
Future<void> checkReceivingAddressForTransactions() async { Future<void> checkReceivingAddressForTransactions() async {
if (info.otherData[WalletInfoKeys.reuseAddress] == true) {
try {
throw Exception();
} catch (_, s) {
Logging.instance.log(
"checkReceivingAddressForTransactions called but reuse address flag set: $s",
level: LogLevel.Error,
);
}
}
try { try {
final currentReceiving = await getCurrentReceivingAddress(); final currentReceiving = await getCurrentReceivingAddress();
@ -1336,9 +1348,12 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
if (needsGenerate) { if (needsGenerate) {
await generateNewReceivingAddress(); await generateNewReceivingAddress();
// TODO: get rid of this? Could cause problems (long loading/infinite loop or something) // TODO: [prio=low] Make sure we scan all addresses but only show one.
// keep checking until address with no tx history is set as current if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
await checkReceivingAddressForTransactions(); // TODO: get rid of this? Could cause problems (long loading/infinite loop or something)
// keep checking until address with no tx history is set as current
await checkReceivingAddressForTransactions();
}
} }
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(

View file

@ -1,3 +1,4 @@
import '../../../models/keys/xpriv_data.dart';
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
import 'electrumx_interface.dart'; import 'electrumx_interface.dart';
@ -42,7 +43,7 @@ mixin ExtendedKeysInterface<T extends ElectrumXCurrencyInterface>
); );
} }
Future<({List<XPriv> xprivs, String fingerprint})> getXPrivs() async { Future<XPrivData> getXPrivs() async {
final paths = cryptoCurrency.supportedDerivationPathTypes.map( final paths = cryptoCurrency.supportedDerivationPathTypes.map(
(e) => ( (e) => (
path: e, path: e,
@ -71,7 +72,8 @@ mixin ExtendedKeysInterface<T extends ElectrumXCurrencyInterface>
); );
}); });
return ( return XPrivData(
walletId: walletId,
fingerprint: fingerprint, fingerprint: fingerprint,
xprivs: [ xprivs: [
( (

View file

@ -171,7 +171,12 @@ dependencies:
convert: ^3.1.1 convert: ^3.1.1
flutter_hooks: ^0.20.3 flutter_hooks: ^0.20.3
meta: ^1.9.1 meta: ^1.9.1
coinlib_flutter: ^2.0.0 # coinlib_flutter: ^2.1.0-rc.1
coinlib_flutter:
git:
url: https://github.com/peercoin/coinlib.git
ref: b88e68e2e10ffe54d802deeed6b9e83da7721a1f
path: coinlib_flutter
electrum_adapter: electrum_adapter:
git: git:
url: https://github.com/cypherstack/electrum_adapter.git url: https://github.com/cypherstack/electrum_adapter.git
@ -180,7 +185,7 @@ dependencies:
solana: solana:
git: # TODO [prio=low]: Revert to official package once Tor support is merged upstream. git: # TODO [prio=low]: Revert to official package once Tor support is merged upstream.
url: https://github.com/cypherstack/espresso-cash-public.git url: https://github.com/cypherstack/espresso-cash-public.git
ref: a83e375678eb22fe544dc125d29bbec0fb833882 ref: 706be5f166d31736c443cca4cd311b7fcfc2a9bf
path: packages/solana path: packages/solana
calendar_date_picker2: ^1.0.2 calendar_date_picker2: ^1.0.2
sqlite3: ^2.4.3 sqlite3: ^2.4.3
@ -210,6 +215,20 @@ flutter_native_splash:
android_disable_fullscreen: true android_disable_fullscreen: true
dependency_overrides: dependency_overrides:
# coin lib git for testing while waiting for publishing
coinlib:
git:
url: https://github.com/peercoin/coinlib.git
ref: b88e68e2e10ffe54d802deeed6b9e83da7721a1f
path: coinlib
coinlib_flutter:
git:
url: https://github.com/peercoin/coinlib.git
ref: b88e68e2e10ffe54d802deeed6b9e83da7721a1f
path: coinlib_flutter
# adding here due to pure laziness # adding here due to pure laziness
tor_ffi_plugin: tor_ffi_plugin:
git: git:

View file

@ -2,7 +2,7 @@
$KEYS = "..\lib\external_api_keys.dart" $KEYS = "..\lib\external_api_keys.dart"
if (-not (Test-Path $KEYS)) { if (-not (Test-Path $KEYS)) {
Write-Host "prebuild.ps1: creating template lib/external_api_keys.dart file" Write-Host "prebuild.ps1: creating template lib/external_api_keys.dart file"
"const kChangeNowApiKey = '';" + "`nconst kSimpleSwapApiKey = '';" | Out-File $KEYS -Encoding UTF8 "const kChangeNowApiKey = '';" + "`nconst kSimpleSwapApiKey = '';" + "`nconst kNanswapApiKey = '';" + "`nconst kNanoSwapRpcApiKey = '';" | Out-File $KEYS -Encoding UTF8
} }
# Create template wallet test parameter files if they don't already exist # Create template wallet test parameter files if they don't already exist

View file

@ -4,7 +4,7 @@
KEYS=../lib/external_api_keys.dart KEYS=../lib/external_api_keys.dart
if ! test -f "$KEYS"; then if ! test -f "$KEYS"; then
echo 'prebuild.sh: creating template lib/external_api_keys.dart file' echo 'prebuild.sh: creating template lib/external_api_keys.dart file'
printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";\n' > $KEYS printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";\nconst kNanswapApiKey = "";\nconst kNanoSwapRpcApiKey = "";\n' > $KEYS
fi fi
# Create template wallet test parameter files if they don't already exist # Create template wallet test parameter files if they don't already exist