mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 12:29:51 +00:00
* feat: add exodus style bip39 to monero legacy seed * feat: restore monero wallet from bip39 and add test * bug: fix wrong naming in CI * feat: add monero bip39 UI flow * fix: monero.dart generation * fix: skip monero_wallet_service tests till CI is fixed * ci: copy monero_libwallet2_api_c.so to /usr/lib for testing ci: reduce timeout for cw_monero tests * fix: monero wallet creation credentials default to bip39 if mnemonic are set * fix: do not skip monero wallets services test * fix: Include non bip39 monero wallets on Wallet Group * fix: null pointer stemming from missing language selector if seed is selected * fix: Fixes to Bip39 Creation and restore - Do not restore from 0 for fresh bip39 wallet - disallow restoring bip39 wallet without date or height * fix: Fixes to Bip39 restore - Refresh height is now getting set correctly - Add new create monero wallet tests - Add seed-language English for Bip39 Monero wallets - Fix seed-type naming * feat (cw_monero): Store monero wallet after bip39 creation * feat (cw_monero): remove prints from monero_wallet_service_test.dart * fix: exception during seed language autodetect * feat (cw_monero): Add support for passphrases on bip39 seeds * feat (cw_monero): Add support for passphrases on bip39 seeds * fix: seed language selection for recovering bip39 wallets * style: improve readability of isLegacySeedOnly in wallet_keys_view_model.dart * feat: hide monero seed type selector from advanced settings when creating a child wallet * fix(cw_monero): use named arguments for bip39_seed tests --------- Co-authored-by: cyan <cyjan@mrcyjanek.net>
424 lines
19 KiB
Dart
424 lines
19 KiB
Dart
import 'package:cake_wallet/core/execution_state.dart';
|
|
import 'package:cake_wallet/core/wallet_name_validator.dart';
|
|
import 'package:cake_wallet/entities/generate_name.dart';
|
|
import 'package:cake_wallet/entities/seed_type.dart';
|
|
import 'package:cake_wallet/generated/i18n.dart';
|
|
import 'package:cake_wallet/main.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/widgets/alert_with_one_action.dart';
|
|
import 'package:cake_wallet/src/widgets/picker.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/src/widgets/seed_language_picker.dart';
|
|
import 'package:cake_wallet/src/widgets/seed_language_selector.dart';
|
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|
import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
|
|
import 'package:cake_wallet/themes/extensions/send_page_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/seed_settings_view_model.dart';
|
|
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
|
import 'package:mobx/mobx.dart';
|
|
|
|
class NewWalletPage extends BasePage {
|
|
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');
|
|
|
|
final walletNameLightImage = Image.asset('assets/images/wallet_name_light.png');
|
|
|
|
@override
|
|
String get title => S.current.new_wallet;
|
|
|
|
@override
|
|
Function(BuildContext)? get pushToNextWidget => (context) {
|
|
FocusScopeNode currentFocus = FocusScope.of(context);
|
|
if (!currentFocus.hasPrimaryFocus) {
|
|
currentFocus.focusedChild?.unfocus();
|
|
}
|
|
};
|
|
|
|
@override
|
|
Widget body(BuildContext context) => WalletNameForm(
|
|
_walletNewVM,
|
|
currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage,
|
|
_seedSettingsViewModel,
|
|
isChildWallet,
|
|
);
|
|
}
|
|
|
|
class WalletNameForm extends StatefulWidget {
|
|
WalletNameForm(
|
|
this._walletNewVM,
|
|
this.walletImage,
|
|
this._seedSettingsViewModel,
|
|
this.isChildWallet,
|
|
);
|
|
|
|
final WalletNewVM _walletNewVM;
|
|
final bool isChildWallet;
|
|
final Image walletImage;
|
|
final SeedSettingsViewModel _seedSettingsViewModel;
|
|
|
|
@override
|
|
_WalletNameFormState createState() => _WalletNameFormState(_walletNewVM);
|
|
}
|
|
|
|
class _WalletNameFormState extends State<WalletNameForm> {
|
|
_WalletNameFormState(this._walletNewVM)
|
|
: _formKey = GlobalKey<FormState>(),
|
|
_languageSelectorKey = GlobalKey<SeedLanguageSelectorState>(),
|
|
_nameController = TextEditingController(),
|
|
_passwordController = _walletNewVM.hasWalletPassword ? TextEditingController() : null,
|
|
_repeatedPasswordController =
|
|
_walletNewVM.hasWalletPassword ? TextEditingController() : null;
|
|
|
|
static const aspectRatioImage = 1.22;
|
|
bool _formProcessing = false;
|
|
|
|
final GlobalKey<FormState> _formKey;
|
|
final GlobalKey<SeedLanguageSelectorState> _languageSelectorKey;
|
|
final WalletNewVM _walletNewVM;
|
|
final TextEditingController _nameController;
|
|
final TextEditingController? _passwordController;
|
|
final TextEditingController? _repeatedPasswordController;
|
|
ReactionDisposer? _stateReaction;
|
|
|
|
@override
|
|
void initState() {
|
|
_stateReaction ??= reaction((_) => _walletNewVM.state, (ExecutionState state) async {
|
|
if (state is ExecutedSuccessfullyState) {
|
|
if (widget.isChildWallet) {
|
|
Navigator.of(navigatorKey.currentContext ?? context)
|
|
.pushNamed(Routes.walletGroupExistingSeedDescriptionPage,
|
|
arguments: _walletNewVM.seedPhraseWordsLength);
|
|
} else {
|
|
Navigator.of(navigatorKey.currentContext ?? context)
|
|
.pushNamed(Routes.preSeedPage, arguments: _walletNewVM.seedPhraseWordsLength);
|
|
}
|
|
}
|
|
|
|
if (state is FailureState) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (mounted) {
|
|
showPopUp<void>(
|
|
context: context,
|
|
builder: (_) {
|
|
return AlertWithOneAction(
|
|
key: ValueKey('new_wallet_page_failure_dialog_key'),
|
|
buttonKey: ValueKey('new_wallet_page_failure_dialog_button_key'),
|
|
alertTitle: S.current.new_wallet,
|
|
alertContent: state.error,
|
|
buttonText: S.of(context).ok,
|
|
buttonAction: () => Navigator.of(context).pop(),
|
|
);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
_setSeedType(MoneroSeedType.defaultSeedType);
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: EdgeInsets.only(top: 24),
|
|
child: ScrollableWithBottomSection(
|
|
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
|
content: Center(
|
|
child: ConstrainedBox(
|
|
constraints:
|
|
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.only(left: 12, right: 12),
|
|
child: AspectRatio(
|
|
aspectRatio: aspectRatioImage,
|
|
child: FittedBox(child: widget.walletImage, fit: BoxFit.fill)),
|
|
),
|
|
Padding(
|
|
padding: EdgeInsets.only(top: 24),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
children: [
|
|
TextFormField(
|
|
key: ValueKey('new_wallet_page_wallet_name_textformfield_key'),
|
|
onChanged: (value) => _walletNewVM.name = value,
|
|
controller: _nameController,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontSize: 20.0,
|
|
fontWeight: FontWeight.w600,
|
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
|
decoration: InputDecoration(
|
|
hintStyle: TextStyle(
|
|
fontSize: 18.0,
|
|
fontWeight: FontWeight.w500,
|
|
color:
|
|
Theme.of(context).extension<NewWalletTheme>()!.hintTextColor),
|
|
hintText: S.of(context).wallet_name,
|
|
focusedBorder: UnderlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: Theme.of(context)
|
|
.extension<NewWalletTheme>()!
|
|
.underlineColor,
|
|
width: 1.0)),
|
|
enabledBorder: UnderlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: Theme.of(context)
|
|
.extension<NewWalletTheme>()!
|
|
.underlineColor,
|
|
width: 1.0),
|
|
),
|
|
suffixIcon: Semantics(
|
|
label: S.of(context).generate_name,
|
|
child: IconButton(
|
|
key: ValueKey(
|
|
'new_wallet_page_wallet_name_textformfield_generate_name_button_key'),
|
|
onPressed: () async {
|
|
final rName = await generateName();
|
|
FocusManager.instance.primaryFocus?.unfocus();
|
|
|
|
setState(() {
|
|
_nameController.text = rName;
|
|
_walletNewVM.name = rName;
|
|
_nameController.selection = TextSelection.fromPosition(
|
|
TextPosition(offset: _nameController.text.length));
|
|
});
|
|
},
|
|
icon: Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(6.0),
|
|
color: Theme.of(context).hintColor,
|
|
),
|
|
width: 34,
|
|
height: 34,
|
|
child: Image.asset(
|
|
'assets/images/refresh_icon.png',
|
|
color: Theme.of(context)
|
|
.extension<SendPageTheme>()!
|
|
.textFieldButtonIconColor,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
validator: WalletNameValidator(),
|
|
),
|
|
if (_walletNewVM.hasWalletPassword) ...[
|
|
TextFormField(
|
|
key: ValueKey('password'),
|
|
onChanged: (value) => _walletNewVM.walletPassword = value,
|
|
controller: _passwordController,
|
|
textAlign: TextAlign.center,
|
|
obscureText: true,
|
|
style: TextStyle(
|
|
fontSize: 20.0,
|
|
fontWeight: FontWeight.w600,
|
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
),
|
|
decoration: InputDecoration(
|
|
hintStyle: TextStyle(
|
|
fontSize: 18.0,
|
|
fontWeight: FontWeight.w500,
|
|
color:
|
|
Theme.of(context).extension<NewWalletTheme>()!.hintTextColor,
|
|
),
|
|
hintText: S.of(context).password,
|
|
focusedBorder: UnderlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: Theme.of(context)
|
|
.extension<NewWalletTheme>()!
|
|
.underlineColor,
|
|
width: 1.0,
|
|
),
|
|
),
|
|
enabledBorder: UnderlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: Theme.of(context)
|
|
.extension<NewWalletTheme>()!
|
|
.underlineColor,
|
|
width: 1.0,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
TextFormField(
|
|
key: ValueKey('repeat_wallet_password'),
|
|
onChanged: (value) => _walletNewVM.repeatedWalletPassword = value,
|
|
controller: _repeatedPasswordController,
|
|
textAlign: TextAlign.center,
|
|
obscureText: true,
|
|
style: TextStyle(
|
|
fontSize: 20.0,
|
|
fontWeight: FontWeight.w600,
|
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
|
),
|
|
decoration: InputDecoration(
|
|
hintStyle: TextStyle(
|
|
fontSize: 18.0,
|
|
fontWeight: FontWeight.w500,
|
|
color:
|
|
Theme.of(context).extension<NewWalletTheme>()!.hintTextColor,
|
|
),
|
|
hintText: S.of(context).repeat_wallet_password,
|
|
focusedBorder: UnderlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: Theme.of(context)
|
|
.extension<NewWalletTheme>()!
|
|
.underlineColor,
|
|
width: 1.0,
|
|
),
|
|
),
|
|
enabledBorder: UnderlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: Theme.of(context)
|
|
.extension<NewWalletTheme>()!
|
|
.underlineColor,
|
|
width: 1.0,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
if (_walletNewVM.showLanguageSelector) ...[
|
|
if (_walletNewVM.hasSeedType) ...[
|
|
Observer(
|
|
builder: (BuildContext build) => Padding(
|
|
padding: EdgeInsets.only(top: 24),
|
|
child: SelectButton(
|
|
key: ValueKey('new_wallet_page_monero_seed_type_button_key'),
|
|
text: widget._seedSettingsViewModel.moneroSeedType.title,
|
|
onTap: () async {
|
|
await showPopUp<void>(
|
|
context: context,
|
|
builder: (_) => Picker(
|
|
items: MoneroSeedType.all,
|
|
selectedAtIndex: isPolyseed ? 1 : 0,
|
|
onItemSelected: _setSeedType,
|
|
isSeparated: false,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
Observer(
|
|
builder: (BuildContext build) => Padding(
|
|
padding: EdgeInsets.only(top: 10),
|
|
child: SeedLanguageSelector(
|
|
key: _languageSelectorKey,
|
|
buttonKey: ValueKey('new_wallet_page_seed_language_selector_button_key'),
|
|
initialSelected: defaultSeedLanguage,
|
|
seedType: _walletNewVM.hasSeedType
|
|
? widget._seedSettingsViewModel.moneroSeedType
|
|
: MoneroSeedType.legacy,
|
|
),
|
|
),
|
|
)
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
bottomSectionPadding: EdgeInsets.all(24),
|
|
bottomSection: Column(
|
|
children: [
|
|
Observer(
|
|
builder: (context) {
|
|
return LoadingPrimaryButton(
|
|
key: ValueKey('new_wallet_page_confirm_button_key'),
|
|
onPressed: _confirmForm,
|
|
text: S.of(context).seed_language_next,
|
|
color: Theme.of(context).primaryColor,
|
|
textColor: Colors.white,
|
|
isLoading: _walletNewVM.state is IsExecutingState,
|
|
isDisabled: _walletNewVM.name.isEmpty,
|
|
);
|
|
},
|
|
),
|
|
const SizedBox(height: 25),
|
|
GestureDetector(
|
|
key: ValueKey('new_wallet_page_advanced_settings_button_key'),
|
|
onTap: () {
|
|
Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: {
|
|
"type": _walletNewVM.type,
|
|
"useTestnet": _walletNewVM.useTestnet,
|
|
"toggleTestnet": _walletNewVM.toggleUseTestnet,
|
|
"isChildWallet": widget.isChildWallet,
|
|
});
|
|
},
|
|
child: Text(S.of(context).advanced_settings),
|
|
),
|
|
],
|
|
)),
|
|
);
|
|
}
|
|
|
|
void _confirmForm() async {
|
|
if (_formProcessing) return;
|
|
_formProcessing = true;
|
|
try {
|
|
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
|
|
_formProcessing = false;
|
|
return;
|
|
}
|
|
if (_walletNewVM.nameExists(_walletNewVM.name)) {
|
|
await showPopUp<void>(
|
|
context: context,
|
|
builder: (_) {
|
|
return AlertWithOneAction(
|
|
alertTitle: '',
|
|
alertContent: S.of(context).wallet_name_exists,
|
|
buttonText: S.of(context).ok,
|
|
buttonAction: () => Navigator.of(context).pop());
|
|
});
|
|
} else {
|
|
await _walletNewVM.create(
|
|
options: _walletNewVM.hasLanguageSelector
|
|
? [
|
|
_languageSelectorKey.currentState?.selected ??
|
|
defaultSeedLanguage,
|
|
widget._seedSettingsViewModel.moneroSeedType
|
|
]
|
|
: null);
|
|
}
|
|
} catch (e) {
|
|
_formProcessing = false;
|
|
rethrow;
|
|
}
|
|
_formProcessing = false;
|
|
}
|
|
|
|
bool get isPolyseed => widget._seedSettingsViewModel.moneroSeedType == MoneroSeedType.polyseed;
|
|
|
|
void _setSeedType(MoneroSeedType item) {
|
|
widget._seedSettingsViewModel.setMoneroSeedType(item);
|
|
_languageSelectorKey.currentState?.selected = defaultSeedLanguage; // Reset Seed language
|
|
}
|
|
}
|