| 
									
										
										
										
											2022-09-16 21:52:08 +08:00
										 |  |  | import 'package:flutter/material.dart'; | 
					
						
							|  |  |  | import 'package:flutter/services.dart'; | 
					
						
							| 
									
										
										
										
											2023-02-19 15:47:52 +01:00
										 |  |  | import 'package:get/get.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-16 21:52:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import '../../common.dart'; | 
					
						
							|  |  |  | import '../../models/platform_model.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-19 15:47:52 +01:00
										 |  |  | abstract class ValidationRule { | 
					
						
							|  |  |  |   String get name; | 
					
						
							|  |  |  |   bool validate(String value); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class LengthRangeValidationRule extends ValidationRule { | 
					
						
							|  |  |  |   final int _min; | 
					
						
							|  |  |  |   final int _max; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   LengthRangeValidationRule(this._min, this._max); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   String get name => translate('length %min% to %max%') | 
					
						
							|  |  |  |       .replaceAll('%min%', _min.toString()) | 
					
						
							|  |  |  |       .replaceAll('%max%', _max.toString()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   bool validate(String value) { | 
					
						
							|  |  |  |     return value.length >= _min && value.length <= _max; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RegexValidationRule extends ValidationRule { | 
					
						
							|  |  |  |   final String _name; | 
					
						
							|  |  |  |   final RegExp _regex; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   RegexValidationRule(this._name, this._regex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   String get name => translate(_name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   bool validate(String value) { | 
					
						
							|  |  |  |     return value.isNotEmpty ? value.contains(_regex) : false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-16 21:52:08 +08:00
										 |  |  | void changeIdDialog() { | 
					
						
							|  |  |  |   var newId = ""; | 
					
						
							|  |  |  |   var msg = ""; | 
					
						
							|  |  |  |   var isInProgress = false; | 
					
						
							|  |  |  |   TextEditingController controller = TextEditingController(); | 
					
						
							| 
									
										
										
										
											2023-02-19 15:47:52 +01:00
										 |  |  |   final RxString rxId = controller.text.trim().obs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   final rules = [ | 
					
						
							|  |  |  |     RegexValidationRule('starts with a letter', RegExp(r'^[a-zA-Z]')), | 
					
						
							|  |  |  |     LengthRangeValidationRule(6, 16), | 
					
						
							|  |  |  |     RegexValidationRule('allowed characters', RegExp(r'^\w*$')) | 
					
						
							|  |  |  |   ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-16 21:52:08 +08:00
										 |  |  |   gFFI.dialogManager.show((setState, close) { | 
					
						
							|  |  |  |     submit() async { | 
					
						
							|  |  |  |       debugPrint("onSubmit"); | 
					
						
							|  |  |  |       newId = controller.text.trim(); | 
					
						
							| 
									
										
										
										
											2023-02-19 15:47:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       final Iterable violations = rules.where((r) => !r.validate(newId)); | 
					
						
							|  |  |  |       if (violations.isNotEmpty) { | 
					
						
							|  |  |  |         setState(() { | 
					
						
							| 
									
										
										
										
											2023-03-04 23:23:10 +01:00
										 |  |  |           msg = isDesktop | 
					
						
							|  |  |  |               ? '${translate('Prompt')}:  ${violations.map((r) => r.name).join(', ')}' | 
					
						
							|  |  |  |               : violations.map((r) => r.name).join(', '); | 
					
						
							| 
									
										
										
										
											2023-02-19 15:47:52 +01:00
										 |  |  |         }); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-16 21:52:08 +08:00
										 |  |  |       setState(() { | 
					
						
							|  |  |  |         msg = ""; | 
					
						
							|  |  |  |         isInProgress = true; | 
					
						
							|  |  |  |         bind.mainChangeId(newId: newId); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       var status = await bind.mainGetAsyncStatus(); | 
					
						
							|  |  |  |       while (status == " ") { | 
					
						
							|  |  |  |         await Future.delayed(const Duration(milliseconds: 100)); | 
					
						
							|  |  |  |         status = await bind.mainGetAsyncStatus(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (status.isEmpty) { | 
					
						
							|  |  |  |         // ok
 | 
					
						
							|  |  |  |         close(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       setState(() { | 
					
						
							|  |  |  |         isInProgress = false; | 
					
						
							| 
									
										
										
										
											2023-03-04 23:23:10 +01:00
										 |  |  |         msg = isDesktop | 
					
						
							|  |  |  |             ? '${translate('Prompt')}: ${translate(status)}' | 
					
						
							|  |  |  |             : translate(status); | 
					
						
							| 
									
										
										
										
											2022-09-16 21:52:08 +08:00
										 |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return CustomAlertDialog( | 
					
						
							|  |  |  |       title: Text(translate("Change ID")), | 
					
						
							|  |  |  |       content: Column( | 
					
						
							|  |  |  |         crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |         children: [ | 
					
						
							|  |  |  |           Text(translate("id_change_tip")), | 
					
						
							|  |  |  |           const SizedBox( | 
					
						
							|  |  |  |             height: 12.0, | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |           TextField( | 
					
						
							|  |  |  |             decoration: InputDecoration( | 
					
						
							| 
									
										
										
										
											2023-02-19 15:47:52 +01:00
										 |  |  |                 labelText: translate('Your new ID'), | 
					
						
							| 
									
										
										
										
											2023-03-04 23:23:10 +01:00
										 |  |  |                 border: isDesktop ? const OutlineInputBorder() : null, | 
					
						
							| 
									
										
										
										
											2023-02-19 15:47:52 +01:00
										 |  |  |                 errorText: msg.isEmpty ? null : translate(msg), | 
					
						
							|  |  |  |                 suffixText: '${rxId.value.length}/16', | 
					
						
							|  |  |  |                 suffixStyle: const TextStyle(fontSize: 12, color: Colors.grey)), | 
					
						
							| 
									
										
										
										
											2022-09-16 21:52:08 +08:00
										 |  |  |             inputFormatters: [ | 
					
						
							|  |  |  |               LengthLimitingTextInputFormatter(16), | 
					
						
							|  |  |  |               // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
 | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |             controller: controller, | 
					
						
							| 
									
										
										
										
											2023-02-12 09:03:13 +08:00
										 |  |  |             autofocus: true, | 
					
						
							| 
									
										
										
										
											2023-02-19 15:47:52 +01:00
										 |  |  |             onChanged: (value) { | 
					
						
							|  |  |  |               setState(() { | 
					
						
							|  |  |  |                 rxId.value = value.trim(); | 
					
						
							|  |  |  |                 msg = ''; | 
					
						
							|  |  |  |               }); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |           const SizedBox( | 
					
						
							|  |  |  |             height: 8.0, | 
					
						
							| 
									
										
										
										
											2022-09-16 21:52:08 +08:00
										 |  |  |           ), | 
					
						
							| 
									
										
										
										
											2023-03-04 23:23:10 +01:00
										 |  |  |           isDesktop | 
					
						
							|  |  |  |               ? Obx(() => Wrap( | 
					
						
							|  |  |  |                     runSpacing: 8, | 
					
						
							|  |  |  |                     spacing: 4, | 
					
						
							|  |  |  |                     children: rules.map((e) { | 
					
						
							|  |  |  |                       var checked = e.validate(rxId.value); | 
					
						
							|  |  |  |                       return Chip( | 
					
						
							|  |  |  |                           label: Text( | 
					
						
							|  |  |  |                             e.name, | 
					
						
							|  |  |  |                             style: TextStyle( | 
					
						
							|  |  |  |                                 color: checked | 
					
						
							|  |  |  |                                     ? const Color(0xFF0A9471) | 
					
						
							|  |  |  |                                     : Color.fromARGB(255, 198, 86, 157)), | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                           backgroundColor: checked | 
					
						
							|  |  |  |                               ? const Color(0xFFD0F7ED) | 
					
						
							|  |  |  |                               : Color.fromARGB(255, 247, 205, 232)); | 
					
						
							|  |  |  |                     }).toList(), | 
					
						
							|  |  |  |                   )).marginOnly(bottom: 8) | 
					
						
							|  |  |  |               : SizedBox.shrink(), | 
					
						
							| 
									
										
										
										
											2022-09-16 21:52:08 +08:00
										 |  |  |           Offstage( | 
					
						
							|  |  |  |               offstage: !isInProgress, child: const LinearProgressIndicator()) | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       actions: [ | 
					
						
							| 
									
										
										
										
											2023-01-15 19:46:16 +08:00
										 |  |  |         dialogButton("Cancel", onPressed: close, isOutline: true), | 
					
						
							|  |  |  |         dialogButton("OK", onPressed: submit), | 
					
						
							| 
									
										
										
										
											2022-09-16 21:52:08 +08:00
										 |  |  |       ], | 
					
						
							|  |  |  |       onSubmit: submit, | 
					
						
							|  |  |  |       onCancel: close, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-26 11:21:40 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | void changeWhiteList({Function()? callback}) async { | 
					
						
							|  |  |  |   var newWhiteList = (await bind.mainGetOption(key: 'whitelist')).split(','); | 
					
						
							|  |  |  |   var newWhiteListField = newWhiteList.join('\n'); | 
					
						
							|  |  |  |   var controller = TextEditingController(text: newWhiteListField); | 
					
						
							|  |  |  |   var msg = ""; | 
					
						
							|  |  |  |   var isInProgress = false; | 
					
						
							|  |  |  |   gFFI.dialogManager.show((setState, close) { | 
					
						
							|  |  |  |     return CustomAlertDialog( | 
					
						
							|  |  |  |       title: Text(translate("IP Whitelisting")), | 
					
						
							|  |  |  |       content: Column( | 
					
						
							|  |  |  |         crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |         children: [ | 
					
						
							|  |  |  |           Text(translate("whitelist_sep")), | 
					
						
							|  |  |  |           const SizedBox( | 
					
						
							|  |  |  |             height: 8.0, | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |           Row( | 
					
						
							|  |  |  |             children: [ | 
					
						
							|  |  |  |               Expanded( | 
					
						
							|  |  |  |                 child: TextField( | 
					
						
							|  |  |  |                     maxLines: null, | 
					
						
							|  |  |  |                     decoration: InputDecoration( | 
					
						
							| 
									
										
										
										
											2023-03-04 22:45:19 +01:00
										 |  |  |                       border: isDesktop ? const OutlineInputBorder() : null, | 
					
						
							| 
									
										
										
										
											2022-09-26 11:21:40 +08:00
										 |  |  |                       errorText: msg.isEmpty ? null : translate(msg), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                     controller: controller, | 
					
						
							| 
									
										
										
										
											2023-02-12 09:03:13 +08:00
										 |  |  |                     autofocus: true), | 
					
						
							| 
									
										
										
										
											2022-09-26 11:21:40 +08:00
										 |  |  |               ), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |           const SizedBox( | 
					
						
							|  |  |  |             height: 4.0, | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |           Offstage( | 
					
						
							|  |  |  |               offstage: !isInProgress, child: const LinearProgressIndicator()) | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       actions: [ | 
					
						
							| 
									
										
										
										
											2023-01-15 19:46:16 +08:00
										 |  |  |         dialogButton("Cancel", onPressed: close, isOutline: true), | 
					
						
							|  |  |  |         dialogButton("Clear", onPressed: () async { | 
					
						
							|  |  |  |           await bind.mainSetOption(key: 'whitelist', value: ''); | 
					
						
							|  |  |  |           callback?.call(); | 
					
						
							|  |  |  |           close(); | 
					
						
							|  |  |  |         }, isOutline: true), | 
					
						
							|  |  |  |         dialogButton( | 
					
						
							|  |  |  |           "OK", | 
					
						
							|  |  |  |           onPressed: () async { | 
					
						
							|  |  |  |             setState(() { | 
					
						
							|  |  |  |               msg = ""; | 
					
						
							|  |  |  |               isInProgress = true; | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |             newWhiteListField = controller.text.trim(); | 
					
						
							|  |  |  |             var newWhiteList = ""; | 
					
						
							|  |  |  |             if (newWhiteListField.isEmpty) { | 
					
						
							|  |  |  |               // pass
 | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |               final ips = newWhiteListField.trim().split(RegExp(r"[\s,;\n]+")); | 
					
						
							|  |  |  |               // test ip
 | 
					
						
							|  |  |  |               final ipMatch = RegExp( | 
					
						
							|  |  |  |                   r"^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)(\/([1-9]|[1-2][0-9]|3[0-2])){0,1}$"); | 
					
						
							|  |  |  |               final ipv6Match = RegExp( | 
					
						
							|  |  |  |                   r"^(((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7})(\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}$"); | 
					
						
							|  |  |  |               for (final ip in ips) { | 
					
						
							|  |  |  |                 if (!ipMatch.hasMatch(ip) && !ipv6Match.hasMatch(ip)) { | 
					
						
							|  |  |  |                   msg = "${translate("Invalid IP")} $ip"; | 
					
						
							|  |  |  |                   setState(() { | 
					
						
							|  |  |  |                     isInProgress = false; | 
					
						
							|  |  |  |                   }); | 
					
						
							|  |  |  |                   return; | 
					
						
							| 
									
										
										
										
											2022-09-26 11:21:40 +08:00
										 |  |  |                 } | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2023-01-15 19:46:16 +08:00
										 |  |  |               newWhiteList = ips.join(','); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             await bind.mainSetOption(key: 'whitelist', value: newWhiteList); | 
					
						
							|  |  |  |             callback?.call(); | 
					
						
							|  |  |  |             close(); | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         ), | 
					
						
							| 
									
										
										
										
											2022-09-26 11:21:40 +08:00
										 |  |  |       ], | 
					
						
							|  |  |  |       onCancel: close, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-29 13:07:20 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | Future<String> changeDirectAccessPort( | 
					
						
							|  |  |  |     String currentIP, String currentPort) async { | 
					
						
							|  |  |  |   final controller = TextEditingController(text: currentPort); | 
					
						
							|  |  |  |   await gFFI.dialogManager.show((setState, close) { | 
					
						
							|  |  |  |     return CustomAlertDialog( | 
					
						
							|  |  |  |       title: Text(translate("Change Local Port")), | 
					
						
							|  |  |  |       content: Column( | 
					
						
							|  |  |  |         crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |         children: [ | 
					
						
							|  |  |  |           const SizedBox(height: 8.0), | 
					
						
							|  |  |  |           Row( | 
					
						
							|  |  |  |             children: [ | 
					
						
							|  |  |  |               Expanded( | 
					
						
							|  |  |  |                 child: TextField( | 
					
						
							|  |  |  |                     maxLines: null, | 
					
						
							|  |  |  |                     keyboardType: TextInputType.number, | 
					
						
							|  |  |  |                     decoration: InputDecoration( | 
					
						
							|  |  |  |                         hintText: '21118', | 
					
						
							|  |  |  |                         isCollapsed: true, | 
					
						
							|  |  |  |                         prefix: Text('$currentIP : '), | 
					
						
							|  |  |  |                         suffix: IconButton( | 
					
						
							|  |  |  |                             padding: EdgeInsets.zero, | 
					
						
							|  |  |  |                             icon: const Icon(Icons.clear, size: 16), | 
					
						
							|  |  |  |                             onPressed: () => controller.clear())), | 
					
						
							|  |  |  |                     inputFormatters: [ | 
					
						
							|  |  |  |                       FilteringTextInputFormatter.allow(RegExp( | 
					
						
							|  |  |  |                           r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')), | 
					
						
							|  |  |  |                     ], | 
					
						
							|  |  |  |                     controller: controller, | 
					
						
							| 
									
										
										
										
											2023-02-12 09:03:13 +08:00
										 |  |  |                     autofocus: true), | 
					
						
							| 
									
										
										
										
											2022-09-29 13:07:20 +08:00
										 |  |  |               ), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       actions: [ | 
					
						
							| 
									
										
										
										
											2023-01-15 19:46:16 +08:00
										 |  |  |         dialogButton("Cancel", onPressed: close, isOutline: true), | 
					
						
							|  |  |  |         dialogButton("OK", onPressed: () async { | 
					
						
							|  |  |  |           await bind.mainSetOption( | 
					
						
							|  |  |  |               key: 'direct-access-port', value: controller.text); | 
					
						
							|  |  |  |           close(); | 
					
						
							|  |  |  |         }), | 
					
						
							| 
									
										
										
										
											2022-09-29 13:07:20 +08:00
										 |  |  |       ], | 
					
						
							|  |  |  |       onCancel: close, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   return controller.text; | 
					
						
							|  |  |  | } |