| 1 | import 'package:cake_wallet/core/execution_state.dart'; |
| 2 | import 'package:cake_wallet/core/wallet_name_validator.dart'; |
| 3 | import 'package:cw_core/generate_name.dart'; |
| 4 | import 'package:cake_wallet/entities/seed_type.dart'; |
| 5 | import 'package:cake_wallet/generated/i18n.dart'; |
| 6 | import 'package:cake_wallet/main.dart'; |
| 7 | import 'package:cake_wallet/routes.dart'; |
| 8 | import 'package:cake_wallet/src/screens/base_page.dart'; |
| 9 | import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; |
| 10 | import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; |
| 11 | import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; |
| 12 | import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; |
| 13 | import 'package:cake_wallet/src/widgets/gradient_background.dart'; |
| 14 | import 'package:cake_wallet/src/widgets/picker.dart'; |
| 15 | import 'package:cake_wallet/src/widgets/primary_button.dart'; |
| 16 | import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart'; |
| 17 | import 'package:cake_wallet/src/widgets/seed_language_picker.dart'; |
| 18 | import 'package:cake_wallet/src/widgets/seed_language_selector.dart'; |
| 19 | import 'package:cake_wallet/utils/responsive_layout_util.dart'; |
| 20 | import 'package:cake_wallet/utils/show_pop_up.dart'; |
| 21 | import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; |
| 22 | import 'package:cake_wallet/view_model/wallet_new_vm.dart'; |
| 23 | import 'package:cw_core/wallet_type.dart'; |
| 24 | import 'package:flutter/material.dart'; |
| 25 | import 'package:flutter_mobx/flutter_mobx.dart'; |
| 26 | import 'package:mobx/mobx.dart'; |
| 27 | |
| 28 | class NewWalletPage extends BasePage { |
| 29 | NewWalletPage( |
| 30 | this._walletNewVM, |
| 31 | this._seedSettingsViewModel, { |
| 32 | this.isChildWallet = false, |
| 33 | }); |
| 34 | |
| 35 | final WalletNewVM _walletNewVM; |
| 36 | final SeedSettingsViewModel _seedSettingsViewModel; |
| 37 | final bool isChildWallet; |
| 38 | |
| 39 | final welcomeImageLight = 'assets/new-ui/hero/wallet_type_light.svg'; |
| 40 | final welcomeImageDark = 'assets/new-ui/hero/wallet_type_dark.svg'; |
| 41 | |
| 42 | @override |
| 43 | bool get gradientBackground => true; |
| 44 | |
| 45 | @override |
| 46 | Widget Function(BuildContext, Widget) get rootWrapper => |
| 47 | (BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold); |
| 48 | |
| 49 | @override |
| 50 | String get title => S.current.new_wallet; |
| 51 | |
| 52 | @override |
| 53 | Function(BuildContext)? get pushToNextWidget => (context) { |
| 54 | FocusScopeNode currentFocus = FocusScope.of(context); |
| 55 | if (!currentFocus.hasPrimaryFocus) { |
| 56 | currentFocus.focusedChild?.unfocus(); |
| 57 | } |
| 58 | }; |
| 59 | |
| 60 | @override |
| 61 | Widget body(BuildContext context) => WalletNameForm( |
| 62 | _walletNewVM, |
| 63 | currentTheme.isDark ? welcomeImageDark : welcomeImageLight, |
| 64 | _seedSettingsViewModel, |
| 65 | isChildWallet, |
| 66 | ); |
| 67 | } |
| 68 | |
| 69 | class WalletNameForm extends StatefulWidget { |
| 70 | WalletNameForm( |
| 71 | this._walletNewVM, |
| 72 | this.walletImage, |
| 73 | this._seedSettingsViewModel, |
| 74 | this.isChildWallet, |
| 75 | ); |
| 76 | |
| 77 | final WalletNewVM _walletNewVM; |
| 78 | final bool isChildWallet; |
| 79 | final String walletImage; |
| 80 | final SeedSettingsViewModel _seedSettingsViewModel; |
| 81 | |
| 82 | @override |
| 83 | _WalletNameFormState createState() => _WalletNameFormState(_walletNewVM); |
| 84 | } |
| 85 | |
| 86 | class _WalletNameFormState extends State<WalletNameForm> { |
| 87 | _WalletNameFormState(this._walletNewVM) |
| 88 | : _formKey = GlobalKey<FormState>(), |
| 89 | _languageSelectorKey = GlobalKey<SeedLanguageSelectorState>(), |
| 90 | _nameController = TextEditingController(), |
| 91 | _passwordController = _walletNewVM.hasWalletPassword ? TextEditingController() : null, |
| 92 | _repeatedPasswordController = |
| 93 | _walletNewVM.hasWalletPassword ? TextEditingController() : null; |
| 94 | |
| 95 | static const aspectRatioImage = 1.5; |
| 96 | bool _formProcessing = false; |
| 97 | |
| 98 | final GlobalKey<FormState> _formKey; |
| 99 | final GlobalKey<SeedLanguageSelectorState> _languageSelectorKey; |
| 100 | final WalletNewVM _walletNewVM; |
| 101 | final TextEditingController _nameController; |
| 102 | final TextEditingController? _passwordController; |
| 103 | final TextEditingController? _repeatedPasswordController; |
| 104 | ReactionDisposer? _stateReaction; |
| 105 | |
| 106 | @override |
| 107 | void initState() { |
| 108 | _stateReaction ??= reaction((_) => _walletNewVM.state, (ExecutionState state) async { |
| 109 | if (state is ExecutedSuccessfullyState) { |
| 110 | if (widget.isChildWallet) { |
| 111 | Navigator.of(navigatorKey.currentContext ?? context) |
| 112 | .pushNamed(Routes.walletGroupExistingSeedDescriptionPage); |
| 113 | } else { |
| 114 | Navigator.of(navigatorKey.currentContext ?? context).pushNamed(Routes.preSeedPage); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | if (state is FailureState) { |
| 119 | WidgetsBinding.instance.addPostFrameCallback((_) { |
| 120 | if (mounted) { |
| 121 | showPopUp<void>( |
| 122 | context: context, |
| 123 | builder: (_) { |
| 124 | return AlertWithOneAction( |
| 125 | key: ValueKey('new_wallet_page_failure_dialog_key'), |
| 126 | buttonKey: ValueKey('new_wallet_page_failure_dialog_button_key'), |
| 127 | alertTitle: S.current.new_wallet, |
| 128 | alertContent: state.error, |
| 129 | buttonText: S.of(context).ok, |
| 130 | buttonAction: () => Navigator.of(context).pop(), |
| 131 | ); |
| 132 | }); |
| 133 | } |
| 134 | }); |
| 135 | } |
| 136 | }); |
| 137 | |
| 138 | _setSeedType(MoneroSeedType.defaultSeedType); |
| 139 | super.initState(); |
| 140 | } |
| 141 | |
| 142 | @override |
| 143 | Widget build(BuildContext context) { |
| 144 | return Padding( |
| 145 | padding: EdgeInsets.only(top: 24), |
| 146 | child: ScrollableWithBottomSection( |
| 147 | contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), |
| 148 | content: Center( |
| 149 | child: ConstrainedBox( |
| 150 | constraints: |
| 151 | BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), |
| 152 | child: Column( |
| 153 | crossAxisAlignment: CrossAxisAlignment.center, |
| 154 | children: [ |
| 155 | Padding( |
| 156 | padding: EdgeInsets.only(left: 24, right: 12), |
| 157 | child: AspectRatio( |
| 158 | aspectRatio: aspectRatioImage, |
| 159 | child: FittedBox( |
| 160 | child: CakeImageWidget(imageUrl: widget.walletImage), |
| 161 | fit: BoxFit.contain, |
| 162 | ), |
| 163 | ), |
| 164 | ), |
| 165 | SizedBox(height: 48), |
| 166 | Padding( |
| 167 | padding: EdgeInsets.only(top: 24), |
| 168 | child: Form( |
| 169 | key: _formKey, |
| 170 | child: Column( |
| 171 | children: [ |
| 172 | BaseTextFormField( |
| 173 | key: ValueKey('new_wallet_page_wallet_name_textformfield_key'), |
| 174 | onChanged: (value) => _walletNewVM.name = value, |
| 175 | controller: _nameController, |
| 176 | textStyle: Theme.of(context).textTheme.titleMedium?.copyWith( |
| 177 | color: Theme.of(context).colorScheme.onSurface, |
| 178 | ), |
| 179 | placeholderTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( |
| 180 | color: Theme.of(context).colorScheme.onSurfaceVariant, |
| 181 | height: 1.4, |
| 182 | fontSize: 16.0, |
| 183 | ), |
| 184 | hintText: S.of(context).wallet_name, |
| 185 | suffixIcon: Semantics( |
| 186 | label: S.of(context).generate_name, |
| 187 | child: IconButton( |
| 188 | key: ValueKey( |
| 189 | 'new_wallet_page_wallet_name_textformfield_generate_name_button_key', |
| 190 | ), |
| 191 | onPressed: () async { |
| 192 | final rName = await generateName(); |
| 193 | FocusManager.instance.primaryFocus?.unfocus(); |
| 194 | |
| 195 | setState(() { |
| 196 | _nameController.text = rName; |
| 197 | _walletNewVM.name = rName; |
| 198 | _nameController.selection = TextSelection.fromPosition( |
| 199 | TextPosition(offset: _nameController.text.length), |
| 200 | ); |
| 201 | }); |
| 202 | }, |
| 203 | icon: Container( |
| 204 | padding: const EdgeInsets.all(8), |
| 205 | decoration: BoxDecoration( |
| 206 | borderRadius: BorderRadius.circular(12.0), |
| 207 | color: Theme.of(context).colorScheme.surface, |
| 208 | ), |
| 209 | width: 34, |
| 210 | height: 34, |
| 211 | child: Image.asset('assets/images/refresh_icon.png', |
| 212 | color: Theme.of(context).colorScheme.onSurfaceVariant), |
| 213 | ), |
| 214 | ), |
| 215 | ), |
| 216 | validator: WalletNameValidator(), |
| 217 | ), |
| 218 | if (_walletNewVM.hasWalletPassword) ...[ |
| 219 | BaseTextFormField( |
| 220 | key: ValueKey('password'), |
| 221 | onChanged: (value) => _walletNewVM.walletPassword = value, |
| 222 | controller: _passwordController, |
| 223 | textAlign: TextAlign.center, |
| 224 | obscureText: true, |
| 225 | textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( |
| 226 | fontSize: 20.0, |
| 227 | fontWeight: FontWeight.w600, |
| 228 | color: Theme.of(context).colorScheme.onSurface, |
| 229 | ), |
| 230 | placeholderTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( |
| 231 | color: Theme.of(context).colorScheme.onSurfaceVariant, |
| 232 | height: 1.4, |
| 233 | fontSize: 18.0, |
| 234 | fontWeight: FontWeight.w500, |
| 235 | ), |
| 236 | hintText: S.of(context).password, |
| 237 | ), |
| 238 | BaseTextFormField( |
| 239 | key: ValueKey('repeat_wallet_password'), |
| 240 | onChanged: (value) => _walletNewVM.repeatedWalletPassword = value, |
| 241 | controller: _repeatedPasswordController, |
| 242 | textAlign: TextAlign.center, |
| 243 | obscureText: true, |
| 244 | textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( |
| 245 | fontSize: 20.0, |
| 246 | fontWeight: FontWeight.w600, |
| 247 | color: Theme.of(context).colorScheme.onSurface, |
| 248 | ), |
| 249 | placeholderTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( |
| 250 | color: Theme.of(context).colorScheme.onSurfaceVariant, |
| 251 | height: 1.4, |
| 252 | fontSize: 18.0, |
| 253 | fontWeight: FontWeight.w500, |
| 254 | ), |
| 255 | hintText: S.of(context).repeat_wallet_password, |
| 256 | ), |
| 257 | ], |
| 258 | ], |
| 259 | ), |
| 260 | ), |
| 261 | ), |
| 262 | if (_walletNewVM.showLanguageSelector) ...[ |
| 263 | if (_walletNewVM.hasSeedType) ...[ |
| 264 | Observer( |
| 265 | builder: (BuildContext build) => Padding( |
| 266 | padding: EdgeInsets.only(top: 24), |
| 267 | child: SelectButton( |
| 268 | key: ValueKey('new_wallet_page_monero_seed_type_button_key'), |
| 269 | text: widget._seedSettingsViewModel.moneroSeedType.title, |
| 270 | borderRadius: BorderRadius.circular(18), |
| 271 | onTap: () async { |
| 272 | await showPopUp<void>( |
| 273 | context: context, |
| 274 | builder: (_) => Picker( |
| 275 | items: MoneroSeedType.all |
| 276 | .where((e) => // exclude bip39 in case of Wownero |
| 277 | widget._walletNewVM.type != WalletType.wownero || |
| 278 | e.raw != MoneroSeedType.bip39.raw) |
| 279 | .toList(), |
| 280 | selectedAtIndex: isPolyseed ? 1 : 0, |
| 281 | onItemSelected: _setSeedType, |
| 282 | isSeparated: false, |
| 283 | ), |
| 284 | ); |
| 285 | }, |
| 286 | ), |
| 287 | ), |
| 288 | ), |
| 289 | ], |
| 290 | Observer( |
| 291 | builder: (BuildContext build) => Padding( |
| 292 | padding: EdgeInsets.only(top: 10), |
| 293 | child: SeedLanguageSelector( |
| 294 | borderRadius: BorderRadius.circular(18), |
| 295 | key: _languageSelectorKey, |
| 296 | buttonKey: ValueKey('new_wallet_page_seed_language_selector_button_key'), |
| 297 | initialSelected: defaultSeedLanguage, |
| 298 | seedType: _walletNewVM.hasSeedType |
| 299 | ? widget._seedSettingsViewModel.moneroSeedType |
| 300 | : MoneroSeedType.legacy, |
| 301 | ), |
| 302 | ), |
| 303 | ) |
| 304 | ], |
| 305 | ], |
| 306 | ), |
| 307 | ), |
| 308 | ), |
| 309 | bottomSectionPadding: EdgeInsets.all(24), |
| 310 | bottomSection: Column( |
| 311 | children: [ |
| 312 | PrimaryButton( |
| 313 | key: ValueKey('new_wallet_page_advanced_settings_button_key'), |
| 314 | onPressed: () { |
| 315 | Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: { |
| 316 | "type": _walletNewVM.type, |
| 317 | "useTestnet": _walletNewVM.useTestnet, |
| 318 | "toggleTestnet": _walletNewVM.toggleUseTestnet, |
| 319 | "zcashNetwork": _walletNewVM.zcashNetwork, |
| 320 | "setZcashNetwork": _walletNewVM.setZcashNetwork, |
| 321 | "isChildWallet": widget.isChildWallet, |
| 322 | }); |
| 323 | }, |
| 324 | text: S.of(context).advanced_settings, |
| 325 | color: Theme.of(context).colorScheme.surfaceContainer, |
| 326 | textColor: Theme.of(context).colorScheme.onSecondaryContainer, |
| 327 | ), |
| 328 | const SizedBox(height: 12), |
| 329 | Observer( |
| 330 | builder: (context) { |
| 331 | return LoadingPrimaryButton( |
| 332 | key: ValueKey('new_wallet_page_confirm_button_key'), |
| 333 | onPressed: _confirmForm, |
| 334 | text: S.of(context).seed_language_next, |
| 335 | color: Theme.of(context).colorScheme.primary, |
| 336 | textColor: Theme.of(context).colorScheme.onPrimary, |
| 337 | isLoading: _walletNewVM.state is IsExecutingState, |
| 338 | isDisabled: _walletNewVM.name.isEmpty, |
| 339 | ); |
| 340 | }, |
| 341 | ), |
| 342 | ], |
| 343 | ), |
| 344 | ), |
| 345 | ); |
| 346 | } |
| 347 | |
| 348 | void _confirmForm() async { |
| 349 | if (_formProcessing) return; |
| 350 | _formProcessing = true; |
| 351 | try { |
| 352 | if (_formKey.currentState != null && !_formKey.currentState!.validate()) { |
| 353 | _formProcessing = false; |
| 354 | return; |
| 355 | } |
| 356 | if (await _walletNewVM.nameExists(_walletNewVM.name)) { |
| 357 | await showPopUp<void>( |
| 358 | context: context, |
| 359 | builder: (_) { |
| 360 | return AlertWithOneAction( |
| 361 | alertTitle: '', |
| 362 | alertContent: S.of(context).wallet_name_exists, |
| 363 | buttonText: S.of(context).ok, |
| 364 | buttonAction: () { |
| 365 | if (mounted && Navigator.of(context).canPop()) { |
| 366 | Navigator.of(context).pop(); |
| 367 | } |
| 368 | }); |
| 369 | }); |
| 370 | } else { |
| 371 | await _walletNewVM.create( |
| 372 | options: _walletNewVM.hasLanguageSelector |
| 373 | ? [ |
| 374 | _languageSelectorKey.currentState?.selected ?? defaultSeedLanguage, |
| 375 | widget._seedSettingsViewModel.moneroSeedType |
| 376 | ] |
| 377 | : null); |
| 378 | } |
| 379 | } catch (e) { |
| 380 | _formProcessing = false; |
| 381 | rethrow; |
| 382 | } |
| 383 | _formProcessing = false; |
| 384 | } |
| 385 | |
| 386 | bool get isPolyseed => widget._seedSettingsViewModel.moneroSeedType == MoneroSeedType.polyseed; |
| 387 | |
| 388 | void _setSeedType(MoneroSeedType item) { |
| 389 | widget._seedSettingsViewModel.setMoneroSeedType(item); |
| 390 | _languageSelectorKey.currentState?.selected = defaultSeedLanguage; // Reset Seed language |
| 391 | } |
| 392 | } |