LCW-1191-ios-node-qr-issue (#2568)
* refactor: improve `NodeCreateOrEditPage` and `NodeForm` structure and state management * fix: eliminate `widget.nodeViewModel` references in constructor
Konstantin Ullrich committed
Oct 8, 2025 at 15:59 UTC
82dfd6afa2c7e8b98db6e5d70a005156a10c40c1
2 files changed
+249
-313
lib/src/screens/nodes/node_create_or_edit_page.dart
+109
-161
@@ -1,91 +1,45 @@
1
import 'package:cake_wallet/core/execution_state.dart';
2
+import 'package:cake_wallet/generated/i18n.dart';
3
+import 'package:cake_wallet/src/screens/base_page.dart';
4
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
5
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
6
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
7
+import 'package:cake_wallet/src/widgets/primary_button.dart';
8
+import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
9
import 'package:cake_wallet/utils/show_pop_up.dart';
10
+import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
11
import 'package:cw_core/node.dart';
12
import 'package:cw_core/wallet_type.dart';
13
import 'package:flutter/material.dart';
14
import 'package:flutter_mobx/flutter_mobx.dart';
15
import 'package:mobx/mobx.dart';
11
-import 'package:cake_wallet/generated/i18n.dart';
12
-import 'package:cake_wallet/src/widgets/primary_button.dart';
13
-import 'package:cake_wallet/src/screens/base_page.dart';
14
-import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
15
-import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
16
17
class NodeCreateOrEditPage extends BasePage {
18
- NodeCreateOrEditPage(
19
- {required this.nodeCreateOrEditViewModel, this.editingNode, this.isSelected, this.type})
20
- : _formKey = GlobalKey<FormState>(),
21
- _addressController = TextEditingController(),
22
- _pathController = TextEditingController(),
23
- _portController = TextEditingController(),
24
- _loginController = TextEditingController(),
25
- _passwordController = TextEditingController() {
26
- reaction((_) => nodeCreateOrEditViewModel.address, (String address) {
27
- if (address != _addressController.text) {
28
- _addressController.text = address;
29
- }
30
- });
31
-
32
- reaction((_) => nodeCreateOrEditViewModel.port, (String port) {
33
- if (port != _portController.text) {
34
- _portController.text = port;
35
- }
36
- });
37
-
38
- if (nodeCreateOrEditViewModel.hasAuthCredentials) {
39
- reaction((_) => nodeCreateOrEditViewModel.login, (String login) {
40
- if (login != _loginController.text) {
41
- _loginController.text = login;
42
- }
43
- });
44
-
45
- reaction((_) => nodeCreateOrEditViewModel.password, (String password) {
46
- if (password != _passwordController.text) {
47
- _passwordController.text = password;
48
- }
49
- });
50
- }
51
-
52
- _addressController
53
- .addListener(() => nodeCreateOrEditViewModel.address = _addressController.text);
54
- _pathController.addListener(() => nodeCreateOrEditViewModel.path = _pathController.text);
55
- _portController.addListener(() => nodeCreateOrEditViewModel.port = _portController.text);
56
- _loginController.addListener(() => nodeCreateOrEditViewModel.login = _loginController.text);
57
- _passwordController
58
- .addListener(() => nodeCreateOrEditViewModel.password = _passwordController.text);
59
- }
18
+ NodeCreateOrEditPage({
19
+ required this.nodeCreateOrEditViewModel,
20
+ this.editingNode,
21
+ this.isSelected,
22
+ this.type,
23
+ }) : _formKey = GlobalKey<FormState>();
24
25
final GlobalKey<FormState> _formKey;
62
- final TextEditingController _addressController;
63
- final TextEditingController _pathController;
64
- final TextEditingController _portController;
65
- final TextEditingController _loginController;
66
- final TextEditingController _passwordController;
26
+ final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
27
+ final Node? editingNode;
28
+ final bool? isSelected;
29
+ final WalletType? type;
30
31
@override
32
String get title => editingNode != null ? S.current.edit_node : S.current.node_new;
33
34
@override
35
Widget trailing(BuildContext context) => IconButton(
73
- onPressed: () async {
74
- await nodeCreateOrEditViewModel.scanQRCodeForNewNode(context);
75
- },
36
+ onPressed: () => nodeCreateOrEditViewModel.scanQRCodeForNewNode(context),
37
splashColor: Colors.transparent,
38
highlightColor: Colors.transparent,
39
hoverColor: Colors.transparent,
79
- icon: Image.asset(
80
- 'assets/images/qr_code_icon.png',
81
- ),
40
+ icon: Image.asset('assets/images/qr_code_icon.png'),
41
);
42
84
- final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
85
- final Node? editingNode;
86
- final bool? isSelected;
87
- final WalletType? type;
88
-
43
@override
44
Widget body(BuildContext context) {
45
reaction(
@@ -93,116 +47,110 @@ class NodeCreateOrEditPage extends BasePage {
47
(ExecutionState state) {
48
if (state is ExecutedSuccessfullyState) {
49
WidgetsBinding.instance.addPostFrameCallback(
96
- (_) {
97
- showPopUp<void>(
98
- context: context,
99
- builder: (BuildContext context) => AlertWithOneAction(
100
- alertTitle: S.of(context).new_node_testing,
101
- alertContent: state.payload as bool
102
- ? S.of(context).node_connection_successful
103
- : S.of(context).node_connection_failed,
104
- buttonText: S.of(context).ok,
105
- buttonAction: () => Navigator.of(context).pop(),
106
- ),
107
- );
108
- },
50
+ (_) => showPopUp<void>(
51
+ context: context,
52
+ builder: (context) => AlertWithOneAction(
53
+ alertTitle: S.of(context).new_node_testing,
54
+ alertContent: state.payload as bool
55
+ ? S.of(context).node_connection_successful
56
+ : S.of(context).node_connection_failed,
57
+ buttonText: S.of(context).ok,
58
+ buttonAction: () => Navigator.of(context).pop(),
59
+ ),
60
+ ),
61
);
62
}
63
64
if (state is FailureState) {
65
WidgetsBinding.instance.addPostFrameCallback(
114
- (_) {
115
- showPopUp<void>(
116
- context: context,
117
- builder: (BuildContext context) {
118
- return AlertWithOneAction(
119
- alertTitle: S.of(context).error,
120
- alertContent: state.error,
121
- buttonText: S.of(context).ok,
122
- buttonAction: () => Navigator.of(context).pop(),
123
- );
124
- },
125
- );
126
- },
66
+ (_) => showPopUp<void>(
67
+ context: context,
68
+ builder: (context) => AlertWithOneAction(
69
+ alertTitle: S.of(context).error,
70
+ alertContent: state.error,
71
+ buttonText: S.of(context).ok,
72
+ buttonAction: () => Navigator.of(context).pop(),
73
+ ),
74
+ ),
75
);
76
}
77
},
78
);
79
80
return Container(
133
- padding: EdgeInsets.only(left: 24, right: 24),
134
- child: ScrollableWithBottomSection(
135
- contentPadding: EdgeInsets.only(bottom: 24.0, top: 8),
136
- content: NodeForm(
137
- formKey: _formKey,
138
- nodeViewModel: nodeCreateOrEditViewModel,
139
- editingNode: editingNode,
140
- type: type,
141
- ),
142
- bottomSectionPadding: EdgeInsets.only(bottom: 24),
143
- bottomSection: Observer(
144
- builder: (_) => Row(
145
- mainAxisAlignment: MainAxisAlignment.center,
146
- children: <Widget>[
147
- Flexible(
148
- child: Container(
149
- padding: EdgeInsets.only(right: 8.0),
150
- child: LoadingPrimaryButton(
151
- onPressed: () async {
152
- final confirmed = await showPopUp<bool>(
153
- context: context,
154
- builder: (BuildContext context) {
155
- return AlertWithTwoActions(
156
- alertTitle: S.of(context).remove_node,
157
- alertContent: S.of(context).remove_node_message,
158
- rightButtonText: S.of(context).remove,
159
- leftButtonText: S.of(context).cancel,
160
- actionRightButton: () => Navigator.pop(context, true),
161
- actionLeftButton: () => Navigator.pop(context, false),
162
- );
163
- },
164
- ) ??
165
- false;
81
+ padding: const EdgeInsets.only(left: 24, right: 24),
82
+ child: ScrollableWithBottomSection(
83
+ contentPadding: const EdgeInsets.only(bottom: 24.0, top: 8),
84
+ content: NodeForm(
85
+ formKey: _formKey,
86
+ nodeViewModel: nodeCreateOrEditViewModel,
87
+ editingNode: editingNode,
88
+ type: type,
89
+ ),
90
+ bottomSectionPadding: const EdgeInsets.only(bottom: 24),
91
+ bottomSection: Observer(
92
+ builder: (_) => Row(
93
+ mainAxisAlignment: MainAxisAlignment.center,
94
+ children: <Widget>[
95
+ Flexible(
96
+ child: Container(
97
+ padding: const EdgeInsets.only(right: 8.0),
98
+ child: LoadingPrimaryButton(
99
+ onPressed: () async {
100
+ final confirmed = await showPopUp<bool>(
101
+ context: context,
102
+ builder: (context) => AlertWithTwoActions(
103
+ alertTitle: S.of(context).remove_node,
104
+ alertContent: S.of(context).remove_node_message,
105
+ rightButtonText: S.of(context).remove,
106
+ leftButtonText: S.of(context).cancel,
107
+ actionRightButton: () => Navigator.pop(context, true),
108
+ actionLeftButton: () => Navigator.pop(context, false),
109
+ ),
110
+ ) ??
111
+ false;
112
167
- if (confirmed) {
168
- await editingNode!.delete();
169
- Navigator.of(context).pop();
170
- }
171
- },
172
- text: S.of(context).delete,
173
- isDisabled: editingNode == null ||
174
- !nodeCreateOrEditViewModel.isReady ||
175
- (isSelected ?? false),
176
- color: Theme.of(context).colorScheme.errorContainer,
177
- textColor: Theme.of(context).colorScheme.onErrorContainer,
178
- ),
179
- ),
180
- ),
181
- Flexible(
182
- child: Container(
183
- padding: EdgeInsets.only(left: 8.0),
184
- child: PrimaryButton(
185
- onPressed: () async {
186
- if (_formKey.currentState != null &&
187
- !_formKey.currentState!.validate()) {
188
- return;
189
- }
113
+ if (confirmed) {
114
+ await editingNode!.delete();
115
+ Navigator.of(context).pop();
116
+ }
117
+ },
118
+ text: S.of(context).delete,
119
+ isDisabled: editingNode == null ||
120
+ !nodeCreateOrEditViewModel.isReady ||
121
+ (isSelected ?? false),
122
+ color: Theme.of(context).colorScheme.errorContainer,
123
+ textColor: Theme.of(context).colorScheme.onErrorContainer,
124
+ ),
125
+ ),
126
+ ),
127
+ Flexible(
128
+ child: Container(
129
+ padding: const EdgeInsets.only(left: 8.0),
130
+ child: PrimaryButton(
131
+ onPressed: () async {
132
+ if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
133
+ return;
134
+ }
135
191
- await nodeCreateOrEditViewModel.save(
192
- editingNode: editingNode, saveAsCurrent: isSelected ?? false);
193
- if (context.mounted) {
194
- Navigator.of(context).pop();
195
- }
196
- },
197
- text: S.of(context).save,
198
- color: Theme.of(context).colorScheme.primary,
199
- textColor: Theme.of(context).colorScheme.onPrimary,
200
- isDisabled: (!nodeCreateOrEditViewModel.isReady) ||
201
- (nodeCreateOrEditViewModel.connectionState is IsExecutingState),
202
- ),
203
- )),
204
- ],
205
- )),
206
- ));
136
+ await nodeCreateOrEditViewModel.save(
137
+ editingNode: editingNode, saveAsCurrent: isSelected ?? false);
138
+ if (context.mounted) {
139
+ Navigator.of(context).pop();
140
+ }
141
+ },
142
+ text: S.of(context).save,
143
+ color: Theme.of(context).colorScheme.primary,
144
+ textColor: Theme.of(context).colorScheme.onPrimary,
145
+ isDisabled: (!nodeCreateOrEditViewModel.isReady) ||
146
+ (nodeCreateOrEditViewModel.connectionState is IsExecutingState),
147
+ ),
148
+ ),
149
+ ),
150
+ ],
151
+ ),
152
+ ),
153
+ ),
154
+ );
155
}
156
}
lib/src/screens/nodes/widgets/node_form.dart
+140
-152
@@ -1,6 +1,7 @@
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/generated/i18n.dart';
5
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
6
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
7
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
@@ -8,15 +9,30 @@ import 'package:cw_core/node.dart';
9
import 'package:cw_core/wallet_type.dart';
10
import 'package:flutter/material.dart';
11
import 'package:flutter_mobx/flutter_mobx.dart';
11
-import 'package:cake_wallet/generated/i18n.dart';
12
import 'package:mobx/mobx.dart';
13
14
-class NodeForm extends StatelessWidget {
14
+class NodeForm extends StatefulWidget {
15
NodeForm({
16
required this.nodeViewModel,
17
required this.formKey,
18
this.editingNode,
19
this.type,
20
+ });
21
+
22
+ final NodeCreateOrEditViewModel nodeViewModel;
23
+ final GlobalKey<FormState> formKey;
24
+ final Node? editingNode;
25
+ final WalletType? type;
26
+
27
+ @override
28
+ State<StatefulWidget> createState() =>
29
+ _NodeFormState(nodeViewModel: nodeViewModel, editingNode: this.editingNode);
30
+}
31
+
32
+class _NodeFormState extends State<NodeForm> {
33
+ _NodeFormState({
34
+ required this.nodeViewModel,
35
+ Node? editingNode,
36
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
37
_pathController = TextEditingController(text: editingNode?.path.toString()),
38
_portController = TextEditingController(
@@ -28,28 +44,25 @@ class NodeForm extends StatelessWidget {
44
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress) {
45
if (editingNode != null) {
46
nodeViewModel
31
- ..setAddress((editingNode!.uri.host.toString()))
32
- ..setPath((editingNode!.path.toString()))
33
- ..setPort((editingNode!.uri.hasPort ? editingNode!.uri.port.toString() : ''))
34
- ..setPassword((editingNode!.password ?? ''))
35
- ..setLogin((editingNode!.login ?? ''))
36
- ..setSSL((editingNode!.isSSL))
37
- ..setTrusted((editingNode!.trusted))
38
- ..setIsEnabledForAutoSwitching((editingNode!.isEnabledForAutoSwitching))
39
- ..setSocksProxy((editingNode!.useSocksProxy))
40
- ..setSocksProxyAddress((editingNode!.socksProxyAddress ?? ''));
47
+ ..setAddress(editingNode.uri.host.toString())
48
+ ..setPath(editingNode.path.toString())
49
+ ..setPort(editingNode.uri.hasPort ? editingNode.uri.port.toString() : '')
50
+ ..setPassword(editingNode.password ?? '')
51
+ ..setLogin(editingNode.login ?? '')
52
+ ..setSSL(editingNode.isSSL)
53
+ ..setTrusted(editingNode.trusted)
54
+ ..setIsEnabledForAutoSwitching(editingNode.isEnabledForAutoSwitching)
55
+ ..setSocksProxy(editingNode.useSocksProxy)
56
+ ..setSocksProxyAddress(editingNode.socksProxyAddress ?? '');
57
}
58
+
59
if (nodeViewModel.hasAuthCredentials) {
60
reaction((_) => nodeViewModel.login, (String login) {
44
- if (login != _loginController.text) {
45
- _loginController.text = login;
46
- }
61
+ if (login != _loginController.text) _loginController.text = login;
62
});
63
64
reaction((_) => nodeViewModel.password, (String password) {
50
- if (password != _passwordController.text) {
51
- _passwordController.text = password;
52
- }
65
+ if (password != _passwordController.text) _passwordController.text = password;
66
});
67
}
68
reaction((_) => nodeViewModel.address, (String address) {
@@ -59,15 +72,11 @@ class NodeForm extends StatelessWidget {
72
});
73
74
reaction((_) => nodeViewModel.port, (String port) {
62
- if (port != _portController.text) {
63
- _portController.text = port;
64
- }
75
+ if (port != _portController.text) _portController.text = port;
76
});
77
78
reaction((_) => nodeViewModel.path, (String path) {
68
- if (path != _pathController.text) {
69
- _pathController.text = path;
70
- }
79
+ if (path != _pathController.text) _pathController.text = path;
80
});
81
82
_addressController.addListener(() => nodeViewModel.address = _addressController.text);
@@ -80,9 +89,6 @@ class NodeForm extends StatelessWidget {
89
}
90
91
final NodeCreateOrEditViewModel nodeViewModel;
83
- final GlobalKey<FormState> formKey;
84
- final Node? editingNode;
85
- final WalletType? type;
92
93
final TextEditingController _addressController;
94
final TextEditingController _pathController;
@@ -92,54 +98,46 @@ class NodeForm extends StatelessWidget {
98
final TextEditingController _socksAddressController;
99
100
@override
95
- Widget build(BuildContext context) {
96
- return Form(
97
- key: formKey,
98
- child: Column(
99
- children: <Widget>[
100
- Row(
101
- children: <Widget>[
102
- Expanded(
103
- child: BaseTextFormField(
104
- controller: _addressController,
105
- hintText: S.of(context).node_address,
106
- validator: type == WalletType.decred
107
- ? NodeAddressValidatorDecredBlankException()
108
- : NodeAddressValidator(),
109
- ),
110
- )
111
- ],
112
- ),
113
- SizedBox(height: 10.0),
101
+ Widget build(BuildContext context) => Form(
102
+ key: widget.formKey,
103
+ child: Column(children: <Widget>[
104
+ Row(children: <Widget>[
105
+ Expanded(
106
+ child: BaseTextFormField(
107
+ controller: _addressController,
108
+ hintText: S.of(context).node_address,
109
+ validator: widget.type == WalletType.decred
110
+ ? NodeAddressValidatorDecredBlankException()
111
+ : NodeAddressValidator(),
112
+ ),
113
+ )
114
+ ]),
115
+ const SizedBox(height: 10),
116
if (nodeViewModel.hasPathSupport) ...[
115
- Row(
116
- children: <Widget>[
117
- Expanded(
118
- child: BaseTextFormField(
119
- controller: _pathController,
120
- hintText: "/path",
121
- validator: NodePathValidator(),
122
- ),
123
- )
124
- ],
125
- ),
126
- SizedBox(height: 10.0),
127
- ],
128
- Row(
129
- children: <Widget>[
117
+ Row(children: <Widget>[
118
Expanded(
119
child: BaseTextFormField(
132
- controller: _portController,
133
- hintText: S.of(context).node_port,
134
- keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
135
- validator: NodePortValidator(),
120
+ controller: _pathController,
121
+ hintText: "/path",
122
+ validator: NodePathValidator(),
123
),
124
)
138
- ],
139
- ),
140
- SizedBox(height: 10.0),
125
+ ]),
126
+ const SizedBox(height: 10),
127
+ ],
128
+ Row(children: <Widget>[
129
+ Expanded(
130
+ child: BaseTextFormField(
131
+ controller: _portController,
132
+ hintText: S.of(context).node_port,
133
+ keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
134
+ validator: NodePortValidator(),
135
+ ),
136
+ )
137
+ ]),
138
+ const SizedBox(height: 10),
139
Padding(
142
- padding: EdgeInsets.only(top: 20),
140
+ padding: const EdgeInsets.only(top: 20),
141
child: Row(
142
mainAxisAlignment: MainAxisAlignment.start,
143
mainAxisSize: MainAxisSize.max,
@@ -156,9 +154,9 @@ class NodeForm extends StatelessWidget {
154
],
155
),
156
),
159
- SizedBox(height: 10.0),
157
+ const SizedBox(height: 10),
158
Padding(
161
- padding: EdgeInsets.only(top: 20),
159
+ padding: const EdgeInsets.only(top: 20),
160
child: Row(
161
mainAxisAlignment: MainAxisAlignment.start,
162
mainAxisSize: MainAxisSize.max,
@@ -175,31 +173,27 @@ class NodeForm extends StatelessWidget {
173
],
174
),
175
),
178
- SizedBox(height: 10.0),
176
+ const SizedBox(height: 10),
177
if (nodeViewModel.hasAuthCredentials) ...[
180
- Row(
181
- children: <Widget>[
182
- Expanded(
183
- child: BaseTextFormField(
184
- controller: _loginController,
185
- hintText: S.of(context).login,
186
- ),
187
- )
188
- ],
189
- ),
190
- SizedBox(height: 10.0),
191
- Row(
192
- children: <Widget>[
193
- Expanded(
194
- child: BaseTextFormField(
195
- controller: _passwordController,
196
- hintText: S.of(context).password,
197
- ),
198
- )
199
- ],
200
- ),
178
+ Row(children: <Widget>[
179
+ Expanded(
180
+ child: BaseTextFormField(
181
+ controller: _loginController,
182
+ hintText: S.of(context).login,
183
+ ),
184
+ )
185
+ ]),
186
+ const SizedBox(height: 10),
187
+ Row(children: <Widget>[
188
+ Expanded(
189
+ child: BaseTextFormField(
190
+ controller: _passwordController,
191
+ hintText: S.of(context).password,
192
+ ),
193
+ )
194
+ ]),
195
Padding(
202
- padding: EdgeInsets.only(top: 20),
196
+ padding: const EdgeInsets.only(top: 20),
197
child: Row(
198
mainAxisAlignment: MainAxisAlignment.start,
199
mainAxisSize: MainAxisSize.max,
@@ -217,66 +211,60 @@ class NodeForm extends StatelessWidget {
211
),
212
),
213
Observer(
220
- builder: (_) => Column(
214
+ builder: (_) => Column(children: [
215
+ if (nodeViewModel.usesEmbeddedProxy) ...[
216
+ Padding(
217
+ padding: const EdgeInsets.only(top: 10),
218
+ child: Row(
219
+ mainAxisAlignment: MainAxisAlignment.start,
220
+ mainAxisSize: MainAxisSize.max,
221
children: [
222
- if (nodeViewModel.usesEmbeddedProxy) ...[
223
- Padding(
224
- padding: EdgeInsets.only(top: 10),
225
- child: Row(
226
- mainAxisAlignment: MainAxisAlignment.start,
227
- mainAxisSize: MainAxisSize.max,
228
- children: [
229
- StandardCheckbox(
230
- value: nodeViewModel.usesEmbeddedProxy,
231
- gradientBackground: false,
232
- borderColor: Theme.of(context).dividerColor,
233
- iconColor: Theme.of(context).colorScheme.primary,
234
- onChanged: null,
235
- caption: 'Embedded Tor SOCKS Proxy',
236
- ),
237
- ],
238
- ),
239
- ),
240
- ],
241
- Padding(
242
- padding: EdgeInsets.only(top: 10),
243
- child: Row(
244
- mainAxisAlignment: MainAxisAlignment.start,
245
- mainAxisSize: MainAxisSize.max,
246
- children: [
247
- StandardCheckbox(
248
- value: nodeViewModel.useSocksProxy,
249
- borderColor: Theme.of(context).colorScheme.surfaceContainerHighest,
250
- iconColor: Theme.of(context).colorScheme.primary,
251
- onChanged: (value) {
252
- if (!value) {
253
- _socksAddressController.text = '';
254
- }
255
- nodeViewModel.useSocksProxy = value;
256
- },
257
- caption: 'SOCKS Proxy',
258
- ),
259
- ],
260
- ),
222
+ StandardCheckbox(
223
+ value: nodeViewModel.usesEmbeddedProxy,
224
+ gradientBackground: false,
225
+ borderColor: Theme.of(context).dividerColor,
226
+ iconColor: Theme.of(context).colorScheme.primary,
227
+ onChanged: null,
228
+ caption: 'Embedded Tor SOCKS Proxy',
229
),
262
- if (nodeViewModel.useSocksProxy) ...[
263
- SizedBox(height: 10.0),
264
- Row(
265
- children: <Widget>[
266
- Expanded(
267
- child: BaseTextFormField(
268
- controller: _socksAddressController,
269
- hintText: '[<ip>:]<port>',
270
- validator: SocksProxyNodeAddressValidator(),
271
- ))
272
- ],
273
- ),
274
- ]
230
],
276
- )),
231
+ ),
232
+ ),
233
+ ],
234
+ Padding(
235
+ padding: const EdgeInsets.only(top: 10),
236
+ child: Row(
237
+ mainAxisAlignment: MainAxisAlignment.start,
238
+ mainAxisSize: MainAxisSize.max,
239
+ children: [
240
+ StandardCheckbox(
241
+ value: nodeViewModel.useSocksProxy,
242
+ borderColor: Theme.of(context).colorScheme.surfaceContainerHighest,
243
+ iconColor: Theme.of(context).colorScheme.primary,
244
+ onChanged: (value) {
245
+ if (!value) _socksAddressController.text = '';
246
+ nodeViewModel.useSocksProxy = value;
247
+ },
248
+ caption: 'SOCKS Proxy',
249
+ ),
250
+ ],
251
+ ),
252
+ ),
253
+ if (nodeViewModel.useSocksProxy) ...[
254
+ const SizedBox(height: 10),
255
+ Row(children: <Widget>[
256
+ Expanded(
257
+ child: BaseTextFormField(
258
+ controller: _socksAddressController,
259
+ hintText: '[<ip>:]<port>',
260
+ validator: SocksProxyNodeAddressValidator(),
261
+ ),
262
+ )
263
+ ]),
264
+ ]
265
+ ]),
266
+ ),
267
]
278
- ],
279
- ),
280
- );
281
- }
268
+ ]),
269
+ );
270
}