CW-580: BIP39 Wallets Shared Seed Implementation: "One Seed - Multiple Wallets" (#1307)

* feat: Implement creating new BIP39 wallet with same seed used for other owned BIP39 wallets

* feat: Use same seed for BIP39 Wallets

* Update pre_existing_seeds_page.dart

* Feat: BIP39 Same seed wallet creation using the Common Parent Wallet Strategy

* feat: Finalize implementing preexisting seeds

* feat: Implement shared bip39 wallet seed for Bitcoin wallet type

* feat: Implement shared bip39 wallet seed for Litecoin wallet type

* feat: Implement shared bip39 wallet seed for BitcoinCash wallet type

* feat: Implement shared bip39 wallet seed for Nano wallet type, although disabled entry for now

* fix: Remove non bip39 seed wallet type from listing

* feat: Implement grouped and single wallets lists in wallets listing page and implement editing and saving group names

* fix: Issue where the ontap always references the leadwallet, also make shared seed wallets section header only display when the multi wallet groups list is not empty

* fix: Add translation and adjust the way the groups display

* feat: Activate bip39 as an option for creating Nano wallet types

* fix: Handle edgecase with creating new wallet with group address, handle case where only bip39 derivation type is allowed with child wallets, activate nano wallet type for shared seed

* chore: Modify the UI to fit adjustment made on figma

* fix: Disposed box triggering error in hive and causing wallet list view to display error

* fix: Switch wallet groups title in wallets list page and also fix issue with renaming groups

* Update lib/reactions/bip39_wallet_utils.dart [skip ci]

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Update lib/router.dart [skip ci]

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* fix: Review fixes: Combine New Wallet Page Type arguments into a single model

* fix: Review fixes: Add failure guard when fetching mnemonic for selected wallet in pre-existing wallets page

* fix: Review fixes - Add loading indicator when mnemonic is being selected for wallet

* fix: Review fixes - Modify variable name to avoid clashes

* fix: Review fixes - Access WalletManager through dependency injection instead of service location

* fix: Review fixes - Add testnet to convertWalletInfoToWalletlistItem function, and adjust according where used

* fix: Review fixes - Add walletPassword to nano, tron and wownero wallets and confirm it is properly handled as it should be

* fix: Remove leadWallet, modify filtering flow to reflect this and not depend on leadWallet, and adjust privacy settings

* fix: Review Fixes - Modify restore flow to reflect current nature of bip39 as default for majority of wallet types

* fix: QA Fixes - Modify preexisting page to display wallet group names if set, and display them in incremental order if not set

* fix: Add wallet group description page and rename pre-existingseeds page to wallet group display page

* fix: Product Fix - Rename pre-existing seeds file name to wallet group display filename

* fix: Product fix - Separate multiwallets groups from single wallets and display separately

* fix - Product Fix - Add empty state for wallet group listing when creating a new wallet, adjust CTAs across buttons relating to the flow also

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
David Adegoke
2024-09-20 21:25:08 +03:00
committed by GitHub
co-authored by Omar Hatem
parent 3a391f10a3
commit 4e2e5e708c
87 changed files with 2152 additions and 430 deletions
@@ -1,4 +1,6 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/desktop_dropdown_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -219,7 +221,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
widget._authService.authenticateAction(
context,
route: Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType,
arguments: NewWalletArguments(type: widget.walletListViewModel.currentWalletType),
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
@@ -9,10 +9,12 @@ import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
@@ -24,6 +26,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
class AdvancedPrivacySettingsPage extends BasePage {
AdvancedPrivacySettingsPage({
required this.isFromRestore,
required this.isChildWallet,
required this.useTestnet,
required this.toggleUseTestnet,
required this.advancedPrivacySettingsViewModel,
@@ -39,25 +42,40 @@ class AdvancedPrivacySettingsPage extends BasePage {
String get title => S.current.privacy_settings;
final bool isFromRestore;
final bool isChildWallet;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
@override
Widget body(BuildContext context) => _AdvancedPrivacySettingsBody(isFromRestore, useTestnet,
toggleUseTestnet, advancedPrivacySettingsViewModel, nodeViewModel, seedSettingsViewModel);
Widget body(BuildContext context) => _AdvancedPrivacySettingsBody(
isFromRestore,
isChildWallet,
useTestnet,
toggleUseTestnet,
advancedPrivacySettingsViewModel,
nodeViewModel,
seedSettingsViewModel,
);
}
class _AdvancedPrivacySettingsBody extends StatefulWidget {
const _AdvancedPrivacySettingsBody(this.isFromRestore, this.useTestnet, this.toggleUseTestnet,
this.privacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel,
{Key? key})
: super(key: key);
const _AdvancedPrivacySettingsBody(
this.isFromRestore,
this.isChildWallet,
this.useTestnet,
this.toggleUseTestnet,
this.privacySettingsViewModel,
this.nodeViewModel,
this.seedTypeViewModel, {
Key? key,
}) : super(key: key);
final AdvancedPrivacySettingsViewModel privacySettingsViewModel;
final NodeCreateOrEditViewModel nodeViewModel;
final SeedSettingsViewModel seedTypeViewModel;
final bool isFromRestore;
final bool isChildWallet;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
@@ -78,6 +96,16 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
passphraseController
.addListener(() => widget.seedTypeViewModel.setPassphrase(passphraseController.text));
if (widget.isChildWallet) {
if (widget.privacySettingsViewModel.type == WalletType.bitcoin) {
widget.seedTypeViewModel.setBitcoinSeedType(BitcoinSeedType.bip39);
}
if (widget.privacySettingsViewModel.type == WalletType.nano) {
widget.seedTypeViewModel.setNanoSeedType(NanoSeedType.bip39);
}
}
super.initState();
}
@@ -116,7 +144,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
),
);
}),
if (widget.privacySettingsViewModel.hasSeedTypeOption)
if (widget.privacySettingsViewModel.isMoneroSeedTypeOptionsEnabled)
Observer(builder: (_) {
return SettingsChoicesCell(
ChoicesListItem<MoneroSeedType>(
@@ -127,15 +155,37 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
),
);
}),
if ([WalletType.bitcoin, WalletType.litecoin]
.contains(widget.privacySettingsViewModel.type))
if (widget.privacySettingsViewModel.isBitcoinSeedTypeOptionsEnabled)
Observer(builder: (_) {
return SettingsChoicesCell(
ChoicesListItem<BitcoinSeedType>(
title: S.current.seedtype,
items: BitcoinSeedType.all,
selectedItem: widget.seedTypeViewModel.bitcoinSeedType,
onItemSelected: widget.seedTypeViewModel.setBitcoinSeedType,
onItemSelected: (type) {
if (widget.isChildWallet && type != BitcoinSeedType.bip39) {
showAlertForSelectingNonBIP39DerivationTypeForChildWallets();
} else {
widget.seedTypeViewModel.setBitcoinSeedType(type);
}
},
),
);
}),
if (widget.privacySettingsViewModel.isNanoSeedTypeOptionsEnabled)
Observer(builder: (_) {
return SettingsChoicesCell(
ChoicesListItem<NanoSeedType>(
title: S.current.seedtype,
items: NanoSeedType.all,
selectedItem: widget.seedTypeViewModel.nanoSeedType,
onItemSelected: (type) {
if (widget.isChildWallet && type != NanoSeedType.bip39) {
showAlertForSelectingNonBIP39DerivationTypeForChildWallets();
} else {
widget.seedTypeViewModel.setNanoSeedType(type);
}
},
),
);
}),
@@ -256,6 +306,19 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
);
}
void showAlertForSelectingNonBIP39DerivationTypeForChildWallets() {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.current.seedtype_alert_title,
alertContent: S.current.seedtype_alert_content,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
});
}
@override
void dispose() {
passphraseController
@@ -26,10 +26,15 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class NewWalletPage extends BasePage {
NewWalletPage(this._walletNewVM, this._seedSettingsViewModel);
NewWalletPage(
this._walletNewVM,
this._seedSettingsViewModel, {
this.isChildWallet = false,
});
final WalletNewVM _walletNewVM;
final SeedSettingsViewModel _seedSettingsViewModel;
final bool isChildWallet;
final walletNameImage = Image.asset('assets/images/wallet_name.png');
@@ -48,15 +53,23 @@ class NewWalletPage extends BasePage {
@override
Widget body(BuildContext context) => WalletNameForm(
_walletNewVM,
currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage,
_seedSettingsViewModel);
_walletNewVM,
currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage,
_seedSettingsViewModel,
isChildWallet,
);
}
class WalletNameForm extends StatefulWidget {
WalletNameForm(this._walletNewVM, this.walletImage, this._seedSettingsViewModel);
WalletNameForm(
this._walletNewVM,
this.walletImage,
this._seedSettingsViewModel,
this.isChildWallet,
);
final WalletNewVM _walletNewVM;
final bool isChildWallet;
final Image walletImage;
final SeedSettingsViewModel _seedSettingsViewModel;
@@ -338,7 +351,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: {
"type": _walletNewVM.type,
"useTestnet": _walletNewVM.useTestnet,
"toggleTestnet": _walletNewVM.toggleUseTestnet
"toggleTestnet": _walletNewVM.toggleUseTestnet,
"isChildWallet": widget.isChildWallet,
});
},
child: Text(S.of(context).advanced_settings),
@@ -1,6 +1,10 @@
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/bip39_wallet_utils.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart';
@@ -11,6 +15,7 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/new_wallet_type_view_model.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
import 'package:cw_core/wallet_type.dart';
@@ -18,21 +23,20 @@ import 'package:flutter/material.dart';
class NewWalletTypePage extends BasePage {
NewWalletTypePage({
required this.onTypeSelected,
required this.isCreate,
required this.isHardwareWallet,
required this.newWalletTypeViewModel,
required this.newWalletTypeArguments,
});
final void Function(BuildContext, WalletType) onTypeSelected;
final bool isCreate;
final bool isHardwareWallet;
final NewWalletTypeViewModel newWalletTypeViewModel;
final NewWalletTypeArguments newWalletTypeArguments;
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
@override
String get title =>
isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet;
String get title => newWalletTypeArguments.isCreate
? S.current.wallet_list_create_new_wallet
: S.current.wallet_list_restore_wallet;
@override
Function(BuildContext)? get pushToNextWidget => (context) {
@@ -44,24 +48,27 @@ class NewWalletTypePage extends BasePage {
@override
Widget body(BuildContext context) => WalletTypeForm(
onTypeSelected: onTypeSelected,
walletImage: currentTheme.type == ThemeType.dark ? walletTypeImage : walletTypeLightImage,
isCreate: isCreate,
isHardwareWallet: isHardwareWallet,
isCreate: newWalletTypeArguments.isCreate,
newWalletTypeViewModel: newWalletTypeViewModel,
onTypeSelected: newWalletTypeArguments.onTypeSelected,
isHardwareWallet: newWalletTypeArguments.isHardwareWallet,
);
}
class WalletTypeForm extends StatefulWidget {
WalletTypeForm({
required this.onTypeSelected,
required this.walletImage,
required this.isCreate,
required this.newWalletTypeViewModel,
this.onTypeSelected,
required this.isHardwareWallet,
});
final void Function(BuildContext, WalletType) onTypeSelected;
final Image walletImage;
final bool isCreate;
final Image walletImage;
final NewWalletTypeViewModel newWalletTypeViewModel;
final void Function(BuildContext, WalletType)? onTypeSelected;
final bool isHardwareWallet;
@override
@@ -179,6 +186,18 @@ class WalletTypeFormState extends State<WalletTypeForm> {
);
}
widget.onTypeSelected(context, selected!);
// If it's a restore flow, trigger the external callback
// If it's not a BIP39 Wallet or if there are no other wallets, route to the newWallet page
// Any other scenario, route to pre-existing seed page
if (!widget.isCreate) {
widget.onTypeSelected!(context, selected!);
} else if (!isBIP39Wallet(selected!) || !widget.newWalletTypeViewModel.hasExisitingWallet) {
Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: NewWalletArguments(type: selected!),
);
} else {
Navigator.of(context).pushNamed(Routes.walletGroupDescription, arguments: selected!);
}
}
}
@@ -0,0 +1,90 @@
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class WalletGroupDescriptionPage extends BasePage {
WalletGroupDescriptionPage({required this.selectedWalletType});
final WalletType selectedWalletType;
@override
String get title => S.current.wallet_group;
@override
Widget body(BuildContext context) {
return Container(
alignment: Alignment.center,
padding: EdgeInsets.all(24),
child: Column(
children: [
Image.asset(
'assets/images/wallet_group.png',
scale: 0.8,
),
SizedBox(height: 32),
Expanded(
child: Text.rich(
TextSpan(
children: [
TextSpan(text: '${S.of(context).wallet_group_description_one} '),
TextSpan(
text: '${S.of(context).wallet_group.toLowerCase()} ',
style: TextStyle(fontWeight: FontWeight.w700),
),
TextSpan(
text: '${S.of(context).wallet_group_description_two} ',
),
TextSpan(
text: '${S.of(context).choose_wallet_group} ',
style: TextStyle(fontWeight: FontWeight.w700),
),
TextSpan(
text: '${S.of(context).wallet_group_description_three} ',
),
TextSpan(
text: '${S.of(context).create_new_seed} ',
style: TextStyle(fontWeight: FontWeight.w700),
),
TextSpan(text: S.of(context).wallet_group_description_four),
],
),
textAlign: TextAlign.center,
style: TextStyle(
height: 1.5,
fontSize: 16,
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
),
),
),
PrimaryButton(
onPressed: () => Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: NewWalletArguments(type: selectedWalletType),
),
text: S.of(context).create_new_seed,
color: Theme.of(context).cardColor,
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
SizedBox(height: 12),
PrimaryButton(
onPressed: () => Navigator.of(context).pushNamed(
Routes.walletGroupsDisplayPage,
arguments: selectedWalletType,
),
text: S.of(context).choose_wallet_group,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
SizedBox(height: 32),
],
),
);
}
}
@@ -0,0 +1,192 @@
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/grouped_wallet_expansion_tile.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/view_model/wallet_groups_display_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import '../../../themes/extensions/cake_text_theme.dart';
class WalletGroupsDisplayPage extends BasePage {
WalletGroupsDisplayPage(this.walletGroupsDisplayViewModel);
final WalletGroupsDisplayViewModel walletGroupsDisplayViewModel;
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
@override
String get title => S.current.wallet_group;
@override
Widget body(BuildContext context) => WalletGroupsDisplayBody(
walletGroupsDisplayViewModel: walletGroupsDisplayViewModel,
);
}
class WalletGroupsDisplayBody extends StatelessWidget {
WalletGroupsDisplayBody({required this.walletGroupsDisplayViewModel});
final WalletGroupsDisplayViewModel walletGroupsDisplayViewModel;
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Observer(
builder: (context) {
return Column(
children: [
if (walletGroupsDisplayViewModel.hasNoFilteredWallet) ...{
WalletGroupEmptyStateWidget(),
},
...walletGroupsDisplayViewModel.multiWalletGroups.map(
(walletGroup) {
return Observer(builder: (context) {
final index = walletGroupsDisplayViewModel.multiWalletGroups
.indexOf(walletGroup);
final group = walletGroupsDisplayViewModel.multiWalletGroups[index];
final groupName =
group.groupName ?? '${S.of(context).wallet_group} ${index + 1}';
return GroupedWalletExpansionTile(
leadingWidget:
Icon(Icons.account_balance_wallet_outlined, size: 28),
borderRadius: BorderRadius.all(Radius.circular(16)),
title: groupName,
childWallets: group.wallets.map((walletInfo) {
return walletGroupsDisplayViewModel
.convertWalletInfoToWalletListItem(walletInfo);
}).toList(),
isSelected:
walletGroupsDisplayViewModel.selectedWalletGroup == group,
onTitleTapped: () =>
walletGroupsDisplayViewModel.selectWalletGroup(group),
onChildItemTapped: (_) =>
walletGroupsDisplayViewModel.selectWalletGroup(group),
);
});
},
).toList(),
...walletGroupsDisplayViewModel.singleWalletsList.map((singleWallet) {
return Observer(
builder: (context) {
final index = walletGroupsDisplayViewModel.singleWalletsList
.indexOf(singleWallet);
final wallet = walletGroupsDisplayViewModel.singleWalletsList[index];
return GroupedWalletExpansionTile(
borderRadius: BorderRadius.all(Radius.circular(16)),
title: wallet.name,
isSelected:
walletGroupsDisplayViewModel.selectedSingleWallet == wallet,
leadingWidget: Image.asset(
walletTypeToCryptoCurrency(wallet.type).iconPath!,
width: 32,
height: 32,
),
onTitleTapped: () =>
walletGroupsDisplayViewModel.selectSingleWallet(wallet),
);
},
);
}).toList(),
],
);
},
),
),
),
Observer(
builder: (context) {
return LoadingPrimaryButton(
isLoading: walletGroupsDisplayViewModel.isFetchingMnemonic,
onPressed: () {
if (walletGroupsDisplayViewModel.hasNoFilteredWallet) {
Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: NewWalletArguments(type: walletGroupsDisplayViewModel.type),
);
} else {
onTypeSelected(context);
}
},
text: walletGroupsDisplayViewModel.hasNoFilteredWallet
? S.of(context).create_new_seed
: S.of(context).seed_language_next,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isDisabled: !walletGroupsDisplayViewModel.hasNoFilteredWallet
? (walletGroupsDisplayViewModel.selectedWalletGroup == null &&
walletGroupsDisplayViewModel.selectedSingleWallet == null)
: false,
);
},
),
SizedBox(height: 32),
],
),
),
);
}
Future<void> onTypeSelected(BuildContext context) async {
final mnemonic = await walletGroupsDisplayViewModel.getSelectedWalletMnemonic();
Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: NewWalletArguments(
type: walletGroupsDisplayViewModel.type,
mnemonic: mnemonic,
parentAddress: walletGroupsDisplayViewModel.parentAddress,
isChildWallet: true,
),
);
}
}
class WalletGroupEmptyStateWidget extends StatelessWidget {
const WalletGroupEmptyStateWidget({
super.key,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Image.asset(
'assets/images/wallet_group.png',
scale: 0.8,
),
SizedBox(height: 32),
Text.rich(
TextSpan(
children: [
TextSpan(
text: '${S.of(context).wallet_group_empty_state_text_one} ',
),
TextSpan(
text: '${S.of(context).create_new_seed} ',
style: TextStyle(fontWeight: FontWeight.w700),
),
TextSpan(text: S.of(context).wallet_group_empty_state_text_two),
],
),
textAlign: TextAlign.center,
style: TextStyle(
height: 1.5,
fontSize: 16,
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
),
),
],
);
}
}
@@ -0,0 +1,119 @@
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class GroupedWalletExpansionTile extends StatelessWidget {
GroupedWalletExpansionTile({
required this.title,
required this.isSelected,
this.childWallets = const [],
this.onTitleTapped,
this.onChildItemTapped = _defaultVoidCallback,
this.leadingWidget,
this.trailingWidget,
this.childTrailingWidget,
this.decoration,
this.color,
this.textColor,
this.arrowColor,
this.borderRadius,
this.margin,
this.tileKey,
}) : super(key: tileKey);
final Key? tileKey;
final bool isSelected;
final VoidCallback? onTitleTapped;
final void Function(WalletListItem item) onChildItemTapped;
final String title;
final Widget? leadingWidget;
final Widget? trailingWidget;
final Widget Function(WalletListItem)? childTrailingWidget;
final List<WalletListItem> childWallets;
final Color? color;
final Color? textColor;
final Color? arrowColor;
final EdgeInsets? margin;
final Decoration? decoration;
final BorderRadius? borderRadius;
static void _defaultVoidCallback(WalletListItem ITEM) {}
@override
Widget build(BuildContext context) {
final backgroundColor = color ?? (isSelected ? Colors.green : Theme.of(context).cardColor);
final effectiveTextColor = textColor ??
(isSelected
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor);
final effectiveArrowColor = arrowColor ??
(isSelected
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
: Theme.of(context).extension<FilterTheme>()!.titlesColor);
return Container(
decoration: BoxDecoration(
borderRadius: borderRadius ?? BorderRadius.all(Radius.circular(30)),
color: backgroundColor,
),
margin: margin ?? const EdgeInsets.only(bottom: 12.0),
child: Theme(
data: Theme.of(context).copyWith(
dividerColor: Colors.transparent,
splashFactory: NoSplash.splashFactory,
),
child: ExpansionTile(
key: tileKey,
tilePadding: EdgeInsets.symmetric(vertical: 1, horizontal: 16),
iconColor: effectiveArrowColor,
collapsedIconColor: effectiveArrowColor,
leading: leadingWidget,
trailing: trailingWidget ?? (childWallets.isEmpty ? SizedBox.shrink() : null),
title: GestureDetector(
onTap: onTitleTapped,
child: Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: effectiveTextColor,
),
textAlign: TextAlign.left,
),
),
children: childWallets.map(
(item) {
final walletTypeToCrypto = walletTypeToCryptoCurrency(item.type);
return ListTile(
key: ValueKey(item.name),
trailing: childTrailingWidget?.call(item),
onTap: () => onChildItemTapped(item),
leading: Image.asset(
walletTypeToCrypto.iconPath!,
width: 32,
height: 32,
),
title: Text(
item.name,
maxLines: 1,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: effectiveTextColor,
),
),
);
},
).toList(),
),
),
);
}
}
@@ -18,9 +18,11 @@ class SelectButton extends StatelessWidget {
this.arrowColor,
this.borderColor,
this.deviceConnectionTypes,
this.borderRadius,
this.padding,
});
final Image? image;
final Widget? image;
final String text;
final double textSize;
final bool isSelected;
@@ -32,6 +34,8 @@ class SelectButton extends StatelessWidget {
final Color? textColor;
final Color? arrowColor;
final Color? borderColor;
final BorderRadius? borderRadius;
final EdgeInsets? padding;
@override
Widget build(BuildContext context) {
@@ -62,10 +66,10 @@ class SelectButton extends StatelessWidget {
child: Container(
width: double.infinity,
height: height,
padding: EdgeInsets.only(left: 30, right: 30),
padding: padding ?? EdgeInsets.only(left: 30, right: 30),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30)),
borderRadius: borderRadius ?? BorderRadius.all(Radius.circular(30)),
color: backgroundColor,
border: borderColor != null ? Border.all(color: borderColor!) : null,
),
@@ -281,16 +281,9 @@ class WalletRestorePage extends BasePage {
return false;
}
if ((walletRestoreViewModel.type == WalletType.litecoin) &&
(seedWords.length != WalletRestoreViewModelBase.electrumSeedMnemonicLength &&
seedWords.length != WalletRestoreViewModelBase.electrumShortSeedMnemonicLength)) {
return false;
}
// bip39:
const validSeedLengths = [12, 18, 24];
if (walletRestoreViewModel.type == WalletType.bitcoin &&
!(validSeedLengths.contains(seedWords.length))) {
if (!(validSeedLengths.contains(seedWords.length))) {
return false;
}
+60 -49
View File
@@ -1,20 +1,16 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
@@ -22,29 +18,29 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class WalletEditPage extends BasePage {
WalletEditPage(
{required this.walletEditViewModel,
required this.editingWallet,
required this.walletNewVM,
required this.authService})
: _formKey = GlobalKey<FormState>(),
WalletEditPage({
required this.pageArguments,
}) : _formKey = GlobalKey<FormState>(),
_labelController = TextEditingController(),
walletEditViewModel = pageArguments.walletEditViewModel!,
super() {
_labelController.text = editingWallet.name;
_labelController.text =
pageArguments.isWalletGroup ? pageArguments.groupName : pageArguments.editingWallet.name;
_labelController.addListener(() => walletEditViewModel.newName = _labelController.text);
}
final GlobalKey<FormState> _formKey;
final TextEditingController _labelController;
final WalletEditPageArguments pageArguments;
final WalletEditViewModel walletEditViewModel;
final WalletNewVM walletNewVM;
final WalletListItem editingWallet;
final AuthService authService;
@override
String get title => S.current.wallet_list_edit_wallet;
String get title => pageArguments.isWalletGroup
? S.current.wallet_list_edit_group_name
: S.current.wallet_list_edit_wallet;
Flushbar<void>? _progressBar;
@@ -57,11 +53,14 @@ class WalletEditPage extends BasePage {
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: BaseTextFormField(
controller: _labelController,
hintText: S.of(context).wallet_list_wallet_name,
validator: WalletNameValidator()))),
child: Center(
child: BaseTextFormField(
controller: _labelController,
hintText: S.of(context).wallet_list_wallet_name,
validator: WalletNameValidator(),
),
),
),
Observer(
builder: (_) {
final isLoading = walletEditViewModel.state is WalletEditRenamePending ||
@@ -69,24 +68,26 @@ class WalletEditPage extends BasePage {
return Row(
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
isDisabled: isLoading,
onPressed: () => _removeWallet(context),
text: S.of(context).delete,
color: Palette.red,
textColor: Colors.white),
if (!pageArguments.isWalletGroup)
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
isDisabled: isLoading,
onPressed: () => _removeWallet(context),
text: S.of(context).delete,
color: Palette.red,
textColor: Colors.white),
),
),
),
Flexible(
child: Container(
padding: EdgeInsets.only(left: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
if (_formKey.currentState?.validate() ?? false) {
if (walletNewVM.nameExists(walletEditViewModel.newName)) {
if (pageArguments.walletNewVM!
.nameExists(walletEditViewModel.newName)) {
showPopUp<void>(
context: context,
builder: (_) {
@@ -102,29 +103,33 @@ class WalletEditPage extends BasePage {
try {
bool confirmed = false;
if (SettingsStoreBase
.walletPasswordDirectInput) {
if (SettingsStoreBase.walletPasswordDirectInput) {
await Navigator.of(context).pushNamed(
Routes.walletUnlockLoadable,
arguments: WalletUnlockArguments(
authPasswordHandler:
(String password) async {
await walletEditViewModel
.changeName(editingWallet,
password: password);
authPasswordHandler: (String password) async {
await walletEditViewModel.changeName(
pageArguments.editingWallet,
password: password,
isWalletGroup: pageArguments.isWalletGroup,
groupParentAddress: pageArguments.parentAddress,
);
},
callback: (bool
isAuthenticatedSuccessfully,
callback: (bool isAuthenticatedSuccessfully,
AuthPageState auth) async {
if (isAuthenticatedSuccessfully) {
auth.close();
confirmed = true;
}
},
walletName: editingWallet.name,
walletType: editingWallet.type));
walletName: pageArguments.editingWallet.name,
walletType: pageArguments.editingWallet.type));
} else {
await walletEditViewModel.changeName(editingWallet);
await walletEditViewModel.changeName(
pageArguments.editingWallet,
isWalletGroup: pageArguments.isWalletGroup,
groupParentAddress: pageArguments.parentAddress,
);
confirmed = true;
}
@@ -154,7 +159,9 @@ class WalletEditPage extends BasePage {
}
Future<void> _removeWallet(BuildContext context) async {
authService.authenticateAction(context, onAuthSuccess: (isAuthenticatedSuccessfully) async {
pageArguments.authService!.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) {
return;
}
@@ -173,7 +180,8 @@ class WalletEditPage extends BasePage {
builder: (BuildContext dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).delete_wallet,
alertContent: S.of(context).delete_wallet_confirm_message(editingWallet.name),
alertContent:
S.of(context).delete_wallet_confirm_message(pageArguments.editingWallet.name),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).delete,
actionLeftButton: () => Navigator.of(dialogContext).pop(),
@@ -187,13 +195,16 @@ class WalletEditPage extends BasePage {
Navigator.of(context).pop();
try {
changeProcessText(context, S.of(context).wallet_list_removing_wallet(editingWallet.name));
await walletEditViewModel.remove(editingWallet);
changeProcessText(
context, S.of(context).wallet_list_removing_wallet(pageArguments.editingWallet.name));
await walletEditViewModel.remove(pageArguments.editingWallet);
hideProgressText();
} catch (e) {
changeProcessText(
context,
S.of(context).wallet_list_failed_to_remove(editingWallet.name, e.toString()),
S
.of(context)
.wallet_list_failed_to_remove(pageArguments.editingWallet.name, e.toString()),
);
}
}
@@ -0,0 +1,54 @@
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
import 'package:flutter/material.dart';
class EditWalletButtonWidget extends StatelessWidget {
const EditWalletButtonWidget({
required this.width,
required this.onTap,
this.isGroup = false,
super.key,
});
final bool isGroup;
final double width;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Container(
width: width,
child: Row(
children: [
GestureDetector(
onTap: onTap,
child: Center(
child: Container(
height: 40,
width: 44,
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsBackgroundColor,
),
child: Icon(
Icons.edit,
size: 14,
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
),
),
),
),
if (isGroup) ...{
SizedBox(width: 6),
Icon(
Icons.keyboard_arrow_down,
size: 24,
color: Theme.of(context).extension<FilterTheme>()!.titlesColor,
),
},
],
),
);
}
}
@@ -7,11 +7,13 @@ class FilteredList extends StatefulWidget {
required this.list,
required this.itemBuilder,
required this.updateFunction,
this.shrinkWrap = false,
});
final ObservableList<dynamic> list;
final Widget Function(BuildContext, int) itemBuilder;
final Function updateFunction;
final bool shrinkWrap;
@override
FilteredListState createState() => FilteredListState();
@@ -22,6 +24,7 @@ class FilteredListState extends State<FilteredList> {
Widget build(BuildContext context) {
return Observer(
builder: (_) => ReorderableListView.builder(
shrinkWrap: widget.shrinkWrap,
physics: const BouncingScrollPhysics(),
itemBuilder: widget.itemBuilder,
itemCount: widget.list.length,
+146 -110
View File
@@ -1,5 +1,9 @@
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
import 'package:cake_wallet/entities/wallet_list_order_types.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_list_widget.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/grouped_wallet_expansion_tile.dart';
import 'package:cake_wallet/src/screens/wallet_list/edit_wallet_button_widget.dart';
import 'package:cake_wallet/src/screens/wallet_list/filtered_list.dart';
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
import 'package:cake_wallet/store/settings_store.dart';
@@ -7,8 +11,6 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@@ -23,7 +25,6 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
class WalletListPage extends BasePage {
WalletListPage({required this.walletListViewModel, required this.authService});
@@ -128,112 +129,143 @@ class WalletListBodyState extends State<WalletListBody> {
child: Column(
children: [
Expanded(
child: Container(
child: Observer(
builder: (_) => FilteredList(
list: widget.walletListViewModel.wallets,
updateFunction: widget.walletListViewModel.reorderAccordingToWalletList,
itemBuilder: (__, index) {
final wallet = widget.walletListViewModel.wallets[index];
final currentColor = wallet.isCurrent
? Theme.of(context)
.extension<WalletListTheme>()!
.createNewWalletButtonBackgroundColor
: Theme.of(context).colorScheme.background;
final row = GestureDetector(
key: ValueKey(wallet.name),
onTap: () => wallet.isCurrent ? null : _loadWallet(wallet),
child: Container(
height: tileHeight,
width: double.infinity,
child: Row(
children: <Widget>[
Container(
height: tileHeight,
width: 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(4),
bottomRight: Radius.circular(4)),
color: currentColor),
),
Expanded(
child: Container(
height: tileHeight,
padding: EdgeInsets.only(left: 20, right: 20),
color: Theme.of(context).colorScheme.background,
alignment: Alignment.centerLeft,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
wallet.isEnabled
? _imageFor(
type: wallet.type,
isTestnet: wallet.isTestnet,
)
: nonWalletTypeIcon,
SizedBox(width: 10),
Flexible(
child: Text(
wallet.name,
maxLines: null,
softWrap: true,
style: TextStyle(
fontSize: DeviceInfo.instance.isDesktop ? 18 : 20,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
),
),
),
],
),
),
),
],
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.walletListViewModel.multiWalletGroups.isNotEmpty) ...{
Padding(
padding: const EdgeInsets.only(left: 24),
child: Text(
S.current.shared_seed_wallet_groups,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
);
return wallet.isCurrent
? row
: Row(
key: ValueKey(wallet.name),
children: [
Expanded(child: row),
GestureDetector(
onTap: () => Navigator.of(context).pushNamed(Routes.walletEdit,
arguments: [widget.walletListViewModel, wallet]),
child: Container(
padding: EdgeInsets.only(
right: DeviceInfo.instance.isMobile ? 20 : 40),
child: Center(
child: Container(
height: 40,
width: 44,
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context)
.extension<ReceivePageTheme>()!
.iconsBackgroundColor,
),
child: Icon(
Icons.edit,
size: 14,
color: Theme.of(context)
.extension<ReceivePageTheme>()!
.iconsColor,
),
),
),
),
),
SizedBox(height: 16),
Container(
child: Observer(
builder: (_) => FilteredList(
shrinkWrap: true,
list: widget.walletListViewModel.multiWalletGroups,
updateFunction: widget.walletListViewModel.reorderAccordingToWalletList,
itemBuilder: (context, index) {
final group = widget.walletListViewModel.multiWalletGroups[index];
final groupName = group.groupName ??
'${S.current.wallet_group} ${index + 1}';
return GroupedWalletExpansionTile(
borderRadius: BorderRadius.all(Radius.circular(16)),
margin: EdgeInsets.only(left: 20, right: 20, bottom: 12),
title: groupName,
tileKey: ValueKey('group_wallets_expansion_tile_widget_$index'),
leadingWidget: Icon(
Icons.account_balance_wallet_outlined,
size: 28,
),
],
);
trailingWidget: EditWalletButtonWidget(
width: 74,
isGroup: true,
onTap: () {
final wallet = widget.walletListViewModel
.convertWalletInfoToWalletListItem(group.wallets.first);
Navigator.of(context).pushNamed(
Routes.walletEdit,
arguments: WalletEditPageArguments(
walletListViewModel: widget.walletListViewModel,
editingWallet: wallet,
isWalletGroup: true,
groupName: groupName,
parentAddress: group.parentAddress,
),
);
},
),
childWallets: group.wallets.map((walletInfo) {
return widget.walletListViewModel
.convertWalletInfoToWalletListItem(walletInfo);
}).toList(),
isSelected: false,
onChildItemTapped: (wallet) =>
wallet.isCurrent ? null : _loadWallet(wallet),
childTrailingWidget: (item) {
return item.isCurrent
? SizedBox.shrink()
: EditWalletButtonWidget(
width: 44,
onTap: () => Navigator.of(context).pushNamed(
Routes.walletEdit,
arguments: WalletEditPageArguments(
walletListViewModel: widget.walletListViewModel,
editingWallet: item,
),
),
);
},
);
},
),
),
),
SizedBox(height: 24),
},
),
if (widget.walletListViewModel.singleWalletsList.isNotEmpty) ...{
Padding(
padding: const EdgeInsets.only(left: 24),
child: Text(
S.current.single_seed_wallets_group,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
),
SizedBox(height: 16),
Container(
child: Observer(
builder: (_) => FilteredList(
shrinkWrap: true,
list: widget.walletListViewModel.singleWalletsList,
updateFunction: widget.walletListViewModel.reorderAccordingToWalletList,
itemBuilder: (context, index) {
final wallet = widget.walletListViewModel.singleWalletsList[index];
return GroupedWalletExpansionTile(
tileKey: ValueKey('single_wallets_expansion_tile_widget_$index'),
leadingWidget: Image.asset(
walletTypeToCryptoCurrency(wallet.type).iconPath!,
width: 32,
height: 32,
),
title: wallet.name,
isSelected: false,
borderRadius: BorderRadius.all(Radius.circular(16)),
margin: EdgeInsets.only(left: 20, right: 20, bottom: 12),
onTitleTapped: () => wallet.isCurrent ? null : _loadWallet(wallet),
trailingWidget: wallet.isCurrent
? null
: EditWalletButtonWidget(
width: 44,
onTap: () {
Navigator.of(context).pushNamed(
Routes.walletEdit,
arguments: WalletEditPageArguments(
walletListViewModel: widget.walletListViewModel,
editingWallet: wallet,
),
);
},
),
);
},
),
),
),
},
],
),
),
),
@@ -249,14 +281,18 @@ class WalletListBodyState extends State<WalletListBody> {
widget.authService.authenticateAction(
context,
route: Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType,
arguments: NewWalletArguments(
type: widget.walletListViewModel.currentWalletType,
),
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType,
arguments: NewWalletArguments(
type: widget.walletListViewModel.currentWalletType,
),
);
}
} else {
@@ -340,15 +376,15 @@ class WalletListBodyState extends State<WalletListBody> {
Future<void> _loadWallet(WalletListItem wallet) async {
if (SettingsStoreBase.walletPasswordDirectInput) {
Navigator.of(context).pushNamed(
Routes.walletUnlockLoadable,
Navigator.of(context).pushNamed(Routes.walletUnlockLoadable,
arguments: WalletUnlockArguments(
callback: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
if (isAuthenticatedSuccessfully) {
auth.close();
setState(() {});
}
}, walletName: wallet.name,
},
walletName: wallet.name,
walletType: wallet.type));
return;
}