| 1 | import 'package:cake_wallet/core/node_address_validator.dart'; |
| 2 | import 'package:cake_wallet/core/node_port_validator.dart'; |
| 3 | import 'package:cake_wallet/core/socks_proxy_node_address_validator.dart'; |
| 4 | import 'package:cake_wallet/entities/new_ui_entities/list_item/list_Item_checkbox.dart'; |
| 5 | import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_text_field.dart'; |
| 6 | import 'package:cake_wallet/generated/i18n.dart'; |
| 7 | import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart'; |
| 8 | import 'package:cake_wallet/utils/feature_flag.dart'; |
| 9 | import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; |
| 10 | import 'package:flutter/material.dart'; |
| 11 | import 'package:flutter_mobx/flutter_mobx.dart'; |
| 12 | import 'package:cw_core/wallet_type.dart'; |
| 13 | import 'package:fluttertoast/fluttertoast.dart'; |
| 14 | import 'package:mobx/mobx.dart'; |
| 15 | |
| 16 | class NodeForm extends StatefulWidget { |
| 17 | NodeForm({ |
| 18 | super.key, |
| 19 | required this.nodeViewModel, |
| 20 | }); |
| 21 | |
| 22 | final NodeCreateOrEditViewModel nodeViewModel; |
| 23 | |
| 24 | @override |
| 25 | State<StatefulWidget> createState() => NodeFormState(vm: nodeViewModel); |
| 26 | } |
| 27 | |
| 28 | class NodeFormState extends State<NodeForm> { |
| 29 | NodeFormState({required this.vm}); |
| 30 | |
| 31 | final Map<String, TextEditingController> _controllers = {}; |
| 32 | |
| 33 | @override |
| 34 | void initState() { |
| 35 | super.initState(); |
| 36 | |
| 37 | for (final key in vm.textFieldKeys) { |
| 38 | _controllers[key] = TextEditingController(); |
| 39 | |
| 40 | _controllers[key]!.text = vm.getTextValue(key); |
| 41 | _controllers[key]!.addListener(() { |
| 42 | final text = _controllers[key]!.text; |
| 43 | vm.updateViewModelFromText(key, text); |
| 44 | }); |
| 45 | } |
| 46 | |
| 47 | _setupReactions(); |
| 48 | } |
| 49 | |
| 50 | @override |
| 51 | void dispose() { |
| 52 | for (final controller in _controllers.values) { |
| 53 | controller.dispose(); |
| 54 | } |
| 55 | super.dispose(); |
| 56 | } |
| 57 | |
| 58 | final NodeCreateOrEditViewModel vm; |
| 59 | final _formKey = GlobalKey<FormState>(); |
| 60 | |
| 61 | @override |
| 62 | Widget build(BuildContext context) { |
| 63 | return Observer( |
| 64 | builder: (_) => Form( |
| 65 | key: _formKey, |
| 66 | child: NewListSections( |
| 67 | sections: { |
| 68 | if (!(vm.editingNode?.isBuiltin ?? false)) ...{ |
| 69 | 'main': [ |
| 70 | ListItemTextField( |
| 71 | keyValue: vm.nodeLabelUIKey, |
| 72 | label: 'Node label', |
| 73 | initialValue: vm.label, |
| 74 | ), |
| 75 | ListItemTextField( |
| 76 | keyValue: vm.nodeAddressUIKey, |
| 77 | label: S.current.node_address, |
| 78 | initialValue: vm.address, |
| 79 | validator: vm.walletType == WalletType.decred |
| 80 | ? NodeAddressValidatorDecredBlankException() |
| 81 | : NodeAddressValidator(), |
| 82 | ), |
| 83 | if (vm.hasPathSupport) |
| 84 | ListItemTextField( |
| 85 | keyValue: vm.nodePathUIKey, |
| 86 | label: '/path', |
| 87 | initialValue: vm.path, |
| 88 | validator: NodePathValidator(), |
| 89 | ), |
| 90 | ListItemTextField( |
| 91 | keyValue: vm.nodePortUIKey, |
| 92 | label: S.current.node_port, |
| 93 | initialValue: '', |
| 94 | //keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false), |
| 95 | validator: NodePortValidator(), |
| 96 | ), |
| 97 | if (vm.hasAuthCredentials) ...[ |
| 98 | ListItemTextField( |
| 99 | keyValue: vm.nodeUsernameUIKey, |
| 100 | label: S.current.login, |
| 101 | initialValue: vm.login, |
| 102 | ), |
| 103 | ListItemTextField( |
| 104 | keyValue: vm.nodePasswordUIKey, |
| 105 | label: S.current.password, |
| 106 | initialValue: vm.password, |
| 107 | ), |
| 108 | ] |
| 109 | ], |
| 110 | 'advanced': [ |
| 111 | ListItemCheckbox( |
| 112 | keyValue: vm.useSSLUIKey, |
| 113 | label: S.current.use_ssl, |
| 114 | value: vm.useSSL, |
| 115 | onChanged: (value) => vm.useSSL = value), |
| 116 | ListItemCheckbox( |
| 117 | keyValue: vm.nodeTrustedUIKey, |
| 118 | label: S.current.trusted, |
| 119 | value: vm.trusted, |
| 120 | onChanged: (value) => vm.trusted = value, |
| 121 | ), |
| 122 | if (vm.usesEmbeddedProxy) |
| 123 | ListItemCheckbox( |
| 124 | keyValue: vm.nodeEmbeddedTorProxyUIKey, |
| 125 | label: 'Embedded Tor SOCKS Proxy', |
| 126 | value: vm.usesEmbeddedProxy, |
| 127 | onChanged: (_) {}, |
| 128 | ), |
| 129 | if (FeatureFlag.isAutomaticNodeSwitchingEnabled) |
| 130 | ListItemCheckbox( |
| 131 | keyValue: vm.autoSwitchingUIKey, |
| 132 | label: S.current.enable_for_auto_switching, |
| 133 | value: vm.isEnabledForAutoSwitching, |
| 134 | onChanged: (value) => vm.isEnabledForAutoSwitching = value), |
| 135 | ], |
| 136 | }, |
| 137 | "proxy": [ |
| 138 | ListItemCheckbox( |
| 139 | keyValue: vm.useSocksProxyUIKey, |
| 140 | label: 'Use SOCKS Proxy', |
| 141 | value: vm.useSocksProxy, |
| 142 | onChanged: (value) { |
| 143 | vm.socksProxyAddress = ''; |
| 144 | vm.useSocksProxy = value; |
| 145 | }, |
| 146 | ), |
| 147 | if (vm.useSocksProxy) |
| 148 | ListItemTextField( |
| 149 | keyValue: vm.socksProxyAddressUIKey, |
| 150 | label: 'host:port', |
| 151 | initialValue: vm.socksProxyAddress, |
| 152 | validator: SocksProxyNodeAddressValidator(), |
| 153 | ), |
| 154 | ] |
| 155 | }, |
| 156 | controllers: _controllers, |
| 157 | getCheckboxValue: vm.getCheckboxValue, |
| 158 | updateCheckboxValue: vm.updateCheckboxValue, |
| 159 | tapHandlers: tapHandlers), |
| 160 | ), |
| 161 | ); |
| 162 | } |
| 163 | |
| 164 | Map<String, VoidCallback> get tapHandlers => { |
| 165 | 'node_regular_with_drill_in_row_key': () => _showToast('Regular with drill-in row tapped'), |
| 166 | 'node_tall_row_key': () => _showToast('Tall row tapped'), |
| 167 | 'node_regular_with_trailing_row_key': () => _showToast('Regular with trailing row tapped'), |
| 168 | 'node_item_selector_row_key': () => _showToast('Item selector row tapped'), |
| 169 | }; |
| 170 | |
| 171 | void _showToast(String msg) async { |
| 172 | try { |
| 173 | await Fluttertoast.showToast( |
| 174 | msg: msg, |
| 175 | backgroundColor: Color.fromRGBO(0, 0, 0, 0.85), |
| 176 | ); |
| 177 | } catch (_) {} |
| 178 | } |
| 179 | |
| 180 | void _setupReactions() { |
| 181 | _bindController(() => vm.label, vm.nodeLabelUIKey); |
| 182 | _bindController(() => vm.address, vm.nodeAddressUIKey); |
| 183 | _bindController(() => vm.port, vm.nodePortUIKey); |
| 184 | _bindController(() => vm.path, vm.nodePathUIKey); |
| 185 | |
| 186 | if (vm.hasAuthCredentials) { |
| 187 | _bindController(() => vm.login, vm.nodeUsernameUIKey); |
| 188 | _bindController(() => vm.password, vm.nodePasswordUIKey); |
| 189 | } |
| 190 | |
| 191 | if (vm.hasPathSupport) { |
| 192 | _bindController(() => vm.path, vm.nodePathUIKey); |
| 193 | } |
| 194 | |
| 195 | _bindController( |
| 196 | () => vm.socksProxyAddress, |
| 197 | vm.socksProxyAddressUIKey, |
| 198 | ); |
| 199 | } |
| 200 | |
| 201 | void _bindController<T>( |
| 202 | T Function() observe, |
| 203 | String uiKey, |
| 204 | ) { |
| 205 | reaction<T>((_) => observe(), (value) { |
| 206 | final controller = _controllers[uiKey]; |
| 207 | if (controller != null && controller.text != value.toString()) { |
| 208 | controller.text = value.toString(); |
| 209 | } |
| 210 | }); |
| 211 | } |
| 212 | |
| 213 | bool validate() => _formKey.currentState?.validate() ?? false; |
| 214 | } |