dev
dart 395 lines 16.1 KB
Raw
1 import 'package:cake_wallet/entities/default_settings_migration.dart';
2 import 'package:cake_wallet/entities/exchange_api_mode.dart';
3 import 'package:cake_wallet/entities/fiat_api_mode.dart';
4 import 'package:cake_wallet/entities/seed_phrase_length.dart';
5 import 'package:cake_wallet/entities/seed_type.dart';
6 import 'package:cake_wallet/generated/i18n.dart';
7 import 'package:cake_wallet/src/screens/base_page.dart';
8 import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
9 import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
10 import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
11 import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
12 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
13 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
14 import 'package:cake_wallet/src/widgets/primary_button.dart';
15 import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
16 import 'package:cake_wallet/utils/show_pop_up.dart';
17 import 'package:cake_wallet/utils/feature_flag.dart';
18 import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
19 import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
20 import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
21 import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
22 import 'package:cw_core/wallet_type.dart';
23 import 'package:cake_wallet/zcash/zcash_network_type.dart';
24 import 'package:flutter/material.dart';
25 import 'package:flutter_mobx/flutter_mobx.dart';
26
27 class AdvancedPrivacySettingsPage extends BasePage {
28 AdvancedPrivacySettingsPage({
29 required this.isFromRestore,
30 required this.isChildWallet,
31 required this.useTestnet,
32 required this.toggleUseTestnet,
33 required this.zcashNetwork,
34 required this.setZcashNetwork,
35 required this.advancedPrivacySettingsViewModel,
36 required this.nodeViewModel,
37 required this.seedSettingsViewModel,
38 });
39
40 final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel;
41 final NodeCreateOrEditViewModel nodeViewModel;
42 final SeedSettingsViewModel seedSettingsViewModel;
43
44 @override
45 String get title => S.current.privacy_settings;
46
47 final bool isFromRestore;
48 final bool isChildWallet;
49 final bool useTestnet;
50 final Function(bool? val) toggleUseTestnet;
51 final int zcashNetwork;
52 final void Function(int network) setZcashNetwork;
53
54 @override
55 Widget body(BuildContext context) => _AdvancedPrivacySettingsBody(
56 isFromRestore,
57 isChildWallet,
58 useTestnet,
59 toggleUseTestnet,
60 zcashNetwork,
61 setZcashNetwork,
62 advancedPrivacySettingsViewModel,
63 nodeViewModel,
64 seedSettingsViewModel,
65 );
66 }
67
68 class _AdvancedPrivacySettingsBody extends StatefulWidget {
69 const _AdvancedPrivacySettingsBody(
70 this.isFromRestore,
71 this.isChildWallet,
72 this.useTestnet,
73 this.toggleUseTestnet,
74 this.zcashNetwork,
75 this.setZcashNetwork,
76 this.privacySettingsViewModel,
77 this.nodeViewModel,
78 this.seedTypeViewModel,
79 );
80
81 final AdvancedPrivacySettingsViewModel privacySettingsViewModel;
82 final NodeCreateOrEditViewModel nodeViewModel;
83 final SeedSettingsViewModel seedTypeViewModel;
84
85 final bool isFromRestore;
86 final bool isChildWallet;
87 final bool useTestnet;
88 final Function(bool? val) toggleUseTestnet;
89 final int zcashNetwork;
90 final void Function(int network) setZcashNetwork;
91
92 @override
93 _AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState();
94 }
95
96 class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBody> {
97 final TextEditingController passphraseController = TextEditingController();
98 final TextEditingController confirmPassphraseController = TextEditingController();
99 final _formKey = GlobalKey<NodeFormState>();
100 final _passphraseFormKey = GlobalKey<FormState>();
101 bool? testnetValue;
102 int? zcashNetworkValue;
103
104 bool obscurePassphrase = true;
105
106 @override
107 void initState() {
108 passphraseController.text = widget.seedTypeViewModel.passphrase ?? '';
109 confirmPassphraseController.text = widget.seedTypeViewModel.passphrase ?? '';
110
111 if (widget.isChildWallet) {
112 if (widget.privacySettingsViewModel.type == WalletType.bitcoin) {
113 widget.seedTypeViewModel.setBitcoinSeedType(BitcoinSeedType.bip39);
114 }
115
116 if (widget.privacySettingsViewModel.type == WalletType.nano) {
117 widget.seedTypeViewModel.setNanoSeedType(NanoSeedType.bip39);
118 }
119 }
120 super.initState();
121 }
122
123 @override
124 Widget build(BuildContext context) {
125 if (testnetValue == null && widget.useTestnet) {
126 testnetValue = widget.useTestnet;
127 }
128 zcashNetworkValue ??= widget.zcashNetwork;
129
130 return Container(
131 padding: EdgeInsets.only(top: 24),
132 child: ScrollableWithBottomSection(
133 contentPadding: EdgeInsets.only(bottom: 24),
134 content: Column(
135 crossAxisAlignment: CrossAxisAlignment.center,
136 children: [
137 Observer(builder: (_) {
138 return SettingsChoicesCell(
139 ChoicesListItem<FiatApiMode>(
140 title: S.current.fiat_api,
141 items: FiatApiMode.all,
142 selectedItem: widget.privacySettingsViewModel.fiatApiMode,
143 onItemSelected: (FiatApiMode mode) =>
144 widget.privacySettingsViewModel.setFiatApiMode(mode),
145 ),
146 );
147 }),
148 Observer(builder: (_) {
149 return SettingsChoicesCell(
150 ChoicesListItem<ExchangeApiMode>(
151 title: S.current.swap,
152 items: ExchangeApiMode.all,
153 selectedItem: widget.privacySettingsViewModel.exchangeStatus,
154 onItemSelected: (ExchangeApiMode mode) =>
155 widget.privacySettingsViewModel.setExchangeApiMode(mode),
156 ),
157 );
158 }),
159 if (widget.privacySettingsViewModel.isMoneroSeedTypeOptionsEnabled &&
160 !widget.isChildWallet)
161 Observer(builder: (_) {
162 return SettingsChoicesCell(
163 ChoicesListItem<MoneroSeedType>(
164 title: S.current.seedtype,
165 items: MoneroSeedType.all,
166 selectedItem: widget.seedTypeViewModel.moneroSeedType,
167 onItemSelected: widget.seedTypeViewModel.setMoneroSeedType,
168 displayItem: (seedType) => seedType.shortTitle ?? seedType.toString(),
169 ),
170 );
171 }),
172 if (widget.privacySettingsViewModel.isBitcoinSeedTypeOptionsEnabled)
173 Observer(builder: (_) {
174 return SettingsChoicesCell(
175 ChoicesListItem<BitcoinSeedType>(
176 title: S.current.seedtype,
177 items: BitcoinSeedType.all,
178 selectedItem: widget.seedTypeViewModel.bitcoinSeedType,
179 onItemSelected: (type) {
180 if (widget.isChildWallet && type != BitcoinSeedType.bip39) {
181 showAlertForSelectingNonBIP39DerivationTypeForChildWallets();
182 } else {
183 widget.seedTypeViewModel.setBitcoinSeedType(type);
184 }
185 },
186 ),
187 );
188 }),
189 if (widget.privacySettingsViewModel.isNanoSeedTypeOptionsEnabled)
190 Observer(builder: (_) {
191 return SettingsChoicesCell(
192 ChoicesListItem<NanoSeedType>(
193 title: S.current.seedtype,
194 items: NanoSeedType.all,
195 selectedItem: widget.seedTypeViewModel.nanoSeedType,
196 onItemSelected: (type) {
197 if (widget.isChildWallet && type != NanoSeedType.bip39) {
198 showAlertForSelectingNonBIP39DerivationTypeForChildWallets();
199 } else {
200 widget.seedTypeViewModel.setNanoSeedType(type);
201 }
202 },
203 ),
204 );
205 }),
206 if (!widget.isFromRestore)
207 Observer(builder: (_) {
208 if (widget.privacySettingsViewModel.hasSeedPhraseLengthOption)
209 return SettingsPickerCell<SeedPhraseLength>(
210 title: S.current.seed_phrase_length,
211 items: SeedPhraseLength.values,
212 selectedItem: widget.privacySettingsViewModel.seedPhraseLength,
213 onItemSelected: (SeedPhraseLength length) {
214 widget.privacySettingsViewModel.setSeedPhraseLength(length);
215 },
216 );
217 return Container();
218 }),
219 if (widget.privacySettingsViewModel.hasPassphraseOption && !widget.isFromRestore)
220 Padding(
221 padding: EdgeInsets.all(24),
222 child: Form(
223 key: _passphraseFormKey,
224 child: Column(
225 children: [
226 BaseTextFormField(
227 hintText: S.of(context).passphrase,
228 controller: passphraseController,
229 obscureText: obscurePassphrase,
230 suffixIcon: GestureDetector(
231 onTap: () => setState(() {
232 obscurePassphrase = !obscurePassphrase;
233 }),
234 child: Icon(
235 Icons.remove_red_eye,
236 ),
237 ),
238 ),
239 const SizedBox(height: 10),
240 BaseTextFormField(
241 hintText: S.of(context).confirm_passphrase,
242 controller: confirmPassphraseController,
243 obscureText: obscurePassphrase,
244 validator: (text) {
245 if (text == passphraseController.text) {
246 return null;
247 }
248
249 return S.of(context).passphrases_doesnt_match;
250 },
251 suffixIcon: GestureDetector(
252 onTap: () => setState(() {
253 obscurePassphrase = !obscurePassphrase;
254 }),
255 child: Icon(
256 Icons.remove_red_eye,
257 ),
258 ),
259 ),
260 ],
261 ),
262 ),
263 ),
264 Observer(builder: (_) {
265 return Column(
266 children: [
267 SettingsSwitcherCell(
268 title: S.current.disable_bulletin,
269 value: widget.privacySettingsViewModel.disableBulletin,
270 onValueChange: (BuildContext _, bool value) {
271 widget.privacySettingsViewModel.setDisableBulletin(value);
272 },
273 ),
274 if (widget.privacySettingsViewModel.canUseBlinkProtection)
275 SettingsSwitcherCell(
276 title: S.current.use_blink_protection,
277 value: widget.privacySettingsViewModel.useBlinkProtection,
278 onValueChange: (BuildContext _, bool value) {
279 widget.privacySettingsViewModel.setUseBlinkProtection(value);
280 },
281 ),
282 SettingsSwitcherCell(
283 title: S.current.add_custom_node,
284 value: widget.privacySettingsViewModel.addCustomNode,
285 onValueChange: (_, __) => widget.privacySettingsViewModel.toggleAddCustomNode(),
286 ),
287 if (widget.privacySettingsViewModel.addCustomNode)
288 Padding(
289 padding: EdgeInsets.only(left: 24, right: 24, top: 24),
290 child: NodeForm(
291 key: _formKey,
292 nodeViewModel: widget.nodeViewModel,
293 ),
294 )
295 ],
296 );
297 }),
298 if (FeatureFlag.hasDevOptions &&
299 widget.privacySettingsViewModel.type == WalletType.zcash)
300 SettingsChoicesCell(
301 ChoicesListItem<int>(
302 title: 'Zcash network',
303 items: ZcashNetworkType.values,
304 selectedItem: zcashNetworkValue ?? ZcashNetworkType.mainnet,
305 displayItem: ZcashNetworkType.label,
306 onItemSelected: (final network) {
307 setState(() => zcashNetworkValue = network);
308 widget.setZcashNetwork(network);
309 },
310 ),
311 ),
312 if (widget.privacySettingsViewModel.type == WalletType.bitcoin ||
313 widget.privacySettingsViewModel.type == WalletType.decred)
314 Builder(builder: (_) {
315 final val = testnetValue ?? false;
316 return SettingsSwitcherCell(
317 title: S.current.use_testnet,
318 value: val,
319 onValueChange: (_, __) {
320 setState(() {
321 testnetValue = !val;
322 });
323 widget.toggleUseTestnet.call(testnetValue);
324 });
325 }),
326 ],
327 ),
328 bottomSectionPadding: EdgeInsets.all(24),
329 bottomSection: Column(
330 children: [
331 const SizedBox(height: 4),
332 LayoutBuilder(
333 builder: (_, constraints) => SizedBox(
334 width: constraints.maxWidth * 0.8,
335 child: Text(
336 S.of(context).settings_can_be_changed_later,
337 textAlign: TextAlign.center,
338 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
339 color: Theme.of(context).colorScheme.onSurfaceVariant,
340 ),
341 ),
342 ),
343 ),
344 const SizedBox(height: 24),
345 LoadingPrimaryButton(
346 onPressed: () {
347 if (widget.privacySettingsViewModel.addCustomNode) {
348 if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
349 return;
350 }
351
352 widget.nodeViewModel.save();
353 }
354 if (testnetValue == true &&
355 widget.privacySettingsViewModel.type == WalletType.bitcoin) {
356 // TODO: add type (mainnet/testnet) to Node class so when switching wallets the node can be switched to a matching type
357 // Currently this is so you can create a working testnet wallet but you need to keep switching back the node if you use multiple wallets at once
358 widget.nodeViewModel.address = publicBitcoinTestnetElectrumAddress;
359 widget.nodeViewModel.port = publicBitcoinTestnetElectrumPort;
360
361 widget.nodeViewModel.save();
362 }
363 if (_passphraseFormKey.currentState != null &&
364 !_passphraseFormKey.currentState!.validate()) {
365 return;
366 }
367
368 widget.seedTypeViewModel.setPassphrase(passphraseController.text);
369
370 Navigator.pop(context);
371 },
372 text: S.of(context).continue_text,
373 color: Theme.of(context).colorScheme.primary,
374 textColor: Theme.of(context).colorScheme.onPrimary,
375 ),
376 SizedBox(height: 16),
377 ],
378 ),
379 ),
380 );
381 }
382
383 void showAlertForSelectingNonBIP39DerivationTypeForChildWallets() {
384 showPopUp<void>(
385 context: context,
386 builder: (BuildContext context) {
387 return AlertWithOneAction(
388 alertTitle: S.current.seedtype_alert_title,
389 alertContent: S.current.seedtype_alert_content,
390 buttonText: S.of(context).ok,
391 buttonAction: () => Navigator.of(context).pop(),
392 );
393 });
394 }
395 }