| 
									
										
										
										
											2023-03-07 19:10:37 +08:00
										 |  |  | import 'dart:io'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  | import 'package:file_picker/file_picker.dart'; | 
					
						
							|  |  |  | import 'package:flutter/material.dart'; | 
					
						
							|  |  |  | import 'package:flutter_hbb/common.dart'; | 
					
						
							| 
									
										
										
										
											2023-03-01 14:18:46 +08:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  | import 'package:flutter_hbb/models/platform_model.dart'; | 
					
						
							| 
									
										
										
										
											2023-03-01 14:18:46 +08:00
										 |  |  | import 'package:flutter_hbb/models/state_model.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  | import 'package:get/get.dart'; | 
					
						
							| 
									
										
										
										
											2023-06-04 19:59:09 +02:00
										 |  |  | import 'package:path/path.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  | import 'package:url_launcher/url_launcher_string.dart'; | 
					
						
							|  |  |  | import 'package:window_manager/window_manager.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class InstallPage extends StatefulWidget { | 
					
						
							|  |  |  |   const InstallPage({Key? key}) : super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<InstallPage> createState() => _InstallPageState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-01 14:18:46 +08:00
										 |  |  | class _InstallPageState extends State<InstallPage> { | 
					
						
							|  |  |  |   final tabController = DesktopTabController(tabType: DesktopTabType.main); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							|  |  |  |     Get.put<DesktopTabController>(tabController); | 
					
						
							| 
									
										
										
										
											2023-05-12 16:29:10 +08:00
										 |  |  |     const label = "install"; | 
					
						
							| 
									
										
										
										
											2023-03-01 14:18:46 +08:00
										 |  |  |     tabController.add(TabInfo( | 
					
						
							| 
									
										
										
										
											2023-05-12 16:29:10 +08:00
										 |  |  |         key: label, | 
					
						
							|  |  |  |         label: label, | 
					
						
							| 
									
										
										
										
											2023-03-01 14:18:46 +08:00
										 |  |  |         closable: false, | 
					
						
							|  |  |  |         page: _InstallPageBody( | 
					
						
							| 
									
										
										
										
											2023-05-12 16:29:10 +08:00
										 |  |  |           key: const ValueKey(label), | 
					
						
							| 
									
										
										
										
											2023-03-01 14:18:46 +08:00
										 |  |  |         ))); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void dispose() { | 
					
						
							|  |  |  |     super.dispose(); | 
					
						
							|  |  |  |     Get.delete<DesktopTabController>(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     return DragToResizeArea( | 
					
						
							|  |  |  |       resizeEdgeSize: stateGlobal.resizeEdgeSize.value, | 
					
						
							|  |  |  |       child: Container( | 
					
						
							|  |  |  |         child: Scaffold( | 
					
						
							|  |  |  |             backgroundColor: Theme.of(context).colorScheme.background, | 
					
						
							|  |  |  |             body: DesktopTab(controller: tabController)), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _InstallPageBody extends StatefulWidget { | 
					
						
							|  |  |  |   const _InstallPageBody({Key? key}) : super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<_InstallPageBody> createState() => _InstallPageBodyState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _InstallPageBodyState extends State<_InstallPageBody> | 
					
						
							|  |  |  |     with WindowListener { | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |   late final TextEditingController controller; | 
					
						
							|  |  |  |   final RxBool startmenu = true.obs; | 
					
						
							|  |  |  |   final RxBool desktopicon = true.obs; | 
					
						
							| 
									
										
										
										
											2023-03-08 11:14:29 +08:00
										 |  |  |   final RxBool driverCert = true.obs; | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |   final RxBool showProgress = false.obs; | 
					
						
							|  |  |  |   final RxBool btnEnabled = true.obs; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-05 16:56:30 +02:00
										 |  |  |   // todo move to theme.
 | 
					
						
							|  |  |  |   final buttonStyle = OutlinedButton.styleFrom( | 
					
						
							|  |  |  |     textStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.normal), | 
					
						
							|  |  |  |     padding: EdgeInsets.symmetric(vertical: 15, horizontal: 12), | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     windowManager.addListener(this); | 
					
						
							|  |  |  |     controller = TextEditingController(text: bind.installInstallPath()); | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void dispose() { | 
					
						
							|  |  |  |     windowManager.removeListener(this); | 
					
						
							|  |  |  |     super.dispose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void onWindowClose() { | 
					
						
							|  |  |  |     gFFI.close(); | 
					
						
							|  |  |  |     super.onWindowClose(); | 
					
						
							|  |  |  |     windowManager.setPreventClose(false); | 
					
						
							|  |  |  |     windowManager.close(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-06 13:38:29 +02:00
										 |  |  |   InkWell Option(RxBool option, {String label = ''}) { | 
					
						
							|  |  |  |     return InkWell( | 
					
						
							|  |  |  |       // todo mouseCursor: "SystemMouseCursors.forbidden" or no cursor on btnEnabled == false
 | 
					
						
							|  |  |  |       borderRadius: BorderRadius.circular(6), | 
					
						
							|  |  |  |       onTap: () => btnEnabled.value ? option.value = !option.value : null, | 
					
						
							|  |  |  |       child: Row( | 
					
						
							|  |  |  |         children: [ | 
					
						
							|  |  |  |           Obx( | 
					
						
							|  |  |  |             () => Checkbox( | 
					
						
							|  |  |  |               visualDensity: VisualDensity(horizontal: -4, vertical: -4), | 
					
						
							|  |  |  |               value: option.value, | 
					
						
							|  |  |  |               onChanged: (v) => | 
					
						
							|  |  |  |                   btnEnabled.value ? option.value = !option.value : null, | 
					
						
							|  |  |  |             ).marginOnly(right: 8), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |           Expanded( | 
					
						
							|  |  |  |             child: Text(translate(label)), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     final double em = 13; | 
					
						
							| 
									
										
										
										
											2023-02-27 17:54:18 +08:00
										 |  |  |     final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark; | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |     return Scaffold( | 
					
						
							| 
									
										
										
										
											2023-02-27 17:54:18 +08:00
										 |  |  |         backgroundColor: null, | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |         body: SingleChildScrollView( | 
					
						
							|  |  |  |           child: Column( | 
					
						
							| 
									
										
										
										
											2023-06-05 17:29:45 +02:00
										 |  |  |             crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |             children: [ | 
					
						
							| 
									
										
										
										
											2023-06-05 17:29:45 +02:00
										 |  |  |               Text(translate('Installation'), | 
					
						
							|  |  |  |                   style: Theme.of(context).textTheme.headlineMedium), | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |               Row( | 
					
						
							|  |  |  |                 children: [ | 
					
						
							| 
									
										
										
										
											2023-06-05 13:03:58 +02:00
										 |  |  |                   Text('${translate('Installation Path')}:') | 
					
						
							|  |  |  |                       .marginOnly(right: 10), | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |                   Expanded( | 
					
						
							| 
									
										
										
										
											2023-06-05 13:03:58 +02:00
										 |  |  |                     child: TextField( | 
					
						
							|  |  |  |                       controller: controller, | 
					
						
							|  |  |  |                       readOnly: true, | 
					
						
							|  |  |  |                       decoration: InputDecoration( | 
					
						
							|  |  |  |                         contentPadding: EdgeInsets.all(0.75 * em), | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                     ).marginOnly(right: 10), | 
					
						
							|  |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2023-06-05 16:56:30 +02:00
										 |  |  |                   Obx( | 
					
						
							|  |  |  |                     () => OutlinedButton.icon( | 
					
						
							|  |  |  |                       icon: Icon(Icons.folder_outlined, size: 16), | 
					
						
							| 
									
										
										
										
											2023-06-05 13:03:58 +02:00
										 |  |  |                       onPressed: btnEnabled.value ? selectInstallPath : null, | 
					
						
							|  |  |  |                       style: buttonStyle, | 
					
						
							| 
									
										
										
										
											2023-06-05 16:56:30 +02:00
										 |  |  |                       label: Text(translate('Change Path')), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   ) | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |                 ], | 
					
						
							|  |  |  |               ).marginSymmetric(vertical: 2 * em), | 
					
						
							| 
									
										
										
										
											2023-06-06 13:38:29 +02:00
										 |  |  |               Option(startmenu, label: 'Create start menu shortcuts') | 
					
						
							|  |  |  |                   .marginOnly(bottom: 7), | 
					
						
							|  |  |  |               Option(desktopicon, label: 'Create desktop icon'), | 
					
						
							| 
									
										
										
										
											2023-03-08 15:57:33 +08:00
										 |  |  |               Offstage( | 
					
						
							|  |  |  |                 offstage: !Platform.isWindows, | 
					
						
							| 
									
										
										
										
											2023-07-13 11:59:27 +08:00
										 |  |  |                 child: Option(driverCert, label: 'install_cert_tip'), | 
					
						
							| 
									
										
										
										
											2023-06-06 13:38:29 +02:00
										 |  |  |               ).marginOnly(top: 7), | 
					
						
							| 
									
										
										
										
											2023-06-06 11:28:20 +02:00
										 |  |  |               Container( | 
					
						
							|  |  |  |                   padding: EdgeInsets.all(12), | 
					
						
							|  |  |  |                   decoration: BoxDecoration( | 
					
						
							|  |  |  |                     color: isDarkTheme | 
					
						
							|  |  |  |                         ? Color.fromARGB(135, 87, 87, 90) | 
					
						
							|  |  |  |                         : Colors.grey[100], | 
					
						
							|  |  |  |                     borderRadius: BorderRadius.circular(8), | 
					
						
							|  |  |  |                     border: Border.all(color: Colors.grey), | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                   child: Row( | 
					
						
							|  |  |  |                     children: [ | 
					
						
							|  |  |  |                       Icon(Icons.info_outline_rounded, size: 32) | 
					
						
							|  |  |  |                           .marginOnly(right: 16), | 
					
						
							|  |  |  |                       Column( | 
					
						
							|  |  |  |                         crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |                         children: [ | 
					
						
							|  |  |  |                           Text(translate('agreement_tip')) | 
					
						
							|  |  |  |                               .marginOnly(bottom: em), | 
					
						
							|  |  |  |                           InkWell( | 
					
						
							|  |  |  |                             hoverColor: Colors.transparent, | 
					
						
							| 
									
										
										
										
											2023-08-18 16:13:24 +08:00
										 |  |  |                             onTap: () => launchUrlString( | 
					
						
							|  |  |  |                                 'https://rustdesk.com/privacy.html'), | 
					
						
							| 
									
										
										
										
											2023-06-06 11:28:20 +02:00
										 |  |  |                             child: Tooltip( | 
					
						
							| 
									
										
										
										
											2023-07-08 10:37:56 +02:00
										 |  |  |                               message: 'https://rustdesk.com/privacy.html', | 
					
						
							| 
									
										
										
										
											2023-06-06 11:28:20 +02:00
										 |  |  |                               child: Row(children: [ | 
					
						
							|  |  |  |                                 Icon(Icons.launch_outlined, size: 16) | 
					
						
							|  |  |  |                                     .marginOnly(right: 5), | 
					
						
							|  |  |  |                                 Text( | 
					
						
							|  |  |  |                                   translate('End-user license agreement'), | 
					
						
							|  |  |  |                                   style: const TextStyle( | 
					
						
							|  |  |  |                                       decoration: TextDecoration.underline), | 
					
						
							|  |  |  |                                 ) | 
					
						
							|  |  |  |                               ]), | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                         ], | 
					
						
							| 
									
										
										
										
											2023-06-05 08:55:00 +02:00
										 |  |  |                       ) | 
					
						
							| 
									
										
										
										
											2023-06-06 11:28:20 +02:00
										 |  |  |                     ], | 
					
						
							|  |  |  |                   )).marginSymmetric(vertical: 2 * em), | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |               Row( | 
					
						
							|  |  |  |                 children: [ | 
					
						
							|  |  |  |                   Expanded( | 
					
						
							| 
									
										
										
										
											2023-08-18 16:13:24 +08:00
										 |  |  |                     // NOT use Offstage to wrap LinearProgressIndicator
 | 
					
						
							|  |  |  |                     child: Obx(() => showProgress.value | 
					
						
							|  |  |  |                         ? LinearProgressIndicator().marginOnly(right: 10) | 
					
						
							|  |  |  |                         : Offstage()), | 
					
						
							| 
									
										
										
										
											2023-06-06 11:01:22 +02:00
										 |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2023-06-05 16:56:30 +02:00
										 |  |  |                   Obx( | 
					
						
							|  |  |  |                     () => OutlinedButton.icon( | 
					
						
							|  |  |  |                       icon: Icon(Icons.close_rounded, size: 16), | 
					
						
							|  |  |  |                       label: Text(translate('Cancel')), | 
					
						
							|  |  |  |                       onPressed: | 
					
						
							|  |  |  |                           btnEnabled.value ? () => windowManager.close() : null, | 
					
						
							|  |  |  |                       style: buttonStyle, | 
					
						
							|  |  |  |                     ).marginOnly(right: 10), | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                   Obx( | 
					
						
							|  |  |  |                     () => ElevatedButton.icon( | 
					
						
							|  |  |  |                       icon: Icon(Icons.done_rounded, size: 16), | 
					
						
							|  |  |  |                       label: Text(translate('Accept and Install')), | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |                       onPressed: btnEnabled.value ? install : null, | 
					
						
							| 
									
										
										
										
											2023-06-05 16:56:30 +02:00
										 |  |  |                       style: buttonStyle, | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |                   Offstage( | 
					
						
							|  |  |  |                     offstage: bind.installShowRunWithoutInstall(), | 
					
						
							| 
									
										
										
										
											2023-06-05 16:56:30 +02:00
										 |  |  |                     child: Obx( | 
					
						
							|  |  |  |                       () => OutlinedButton.icon( | 
					
						
							|  |  |  |                         icon: Icon(Icons.screen_share_outlined, size: 16), | 
					
						
							|  |  |  |                         label: Text(translate('Run without install')), | 
					
						
							|  |  |  |                         onPressed: btnEnabled.value | 
					
						
							|  |  |  |                             ? () => bind.installRunWithoutInstall() | 
					
						
							|  |  |  |                             : null, | 
					
						
							|  |  |  |                         style: buttonStyle, | 
					
						
							|  |  |  |                       ).marginOnly(left: 10), | 
					
						
							|  |  |  |                     ), | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |                   ), | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |               ) | 
					
						
							|  |  |  |             ], | 
					
						
							| 
									
										
										
										
											2023-06-05 17:07:51 +02:00
										 |  |  |           ).paddingSymmetric(horizontal: 4 * em, vertical: 3 * em), | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |         )); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void install() { | 
					
						
							| 
									
										
										
										
											2023-03-08 11:14:29 +08:00
										 |  |  |     do_install() { | 
					
						
							|  |  |  |       btnEnabled.value = false; | 
					
						
							|  |  |  |       showProgress.value = true; | 
					
						
							|  |  |  |       String args = ''; | 
					
						
							|  |  |  |       if (startmenu.value) args += ' startmenu'; | 
					
						
							|  |  |  |       if (desktopicon.value) args += ' desktopicon'; | 
					
						
							|  |  |  |       if (driverCert.value) args += ' driverCert'; | 
					
						
							|  |  |  |       bind.installInstallMe(options: args, path: controller.text); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (driverCert.isTrue) { | 
					
						
							|  |  |  |       final tag = 'install-info-install-cert-confirm'; | 
					
						
							|  |  |  |       final btns = [ | 
					
						
							| 
									
										
										
										
											2023-06-05 16:56:30 +02:00
										 |  |  |         OutlinedButton.icon( | 
					
						
							|  |  |  |           icon: Icon(Icons.close_rounded, size: 16), | 
					
						
							|  |  |  |           label: Text(translate('Cancel')), | 
					
						
							| 
									
										
										
										
											2023-03-08 11:14:29 +08:00
										 |  |  |           onPressed: () => gFFI.dialogManager.dismissByTag(tag), | 
					
						
							| 
									
										
										
										
											2023-06-05 16:56:30 +02:00
										 |  |  |           style: buttonStyle, | 
					
						
							| 
									
										
										
										
											2023-03-08 11:14:29 +08:00
										 |  |  |         ), | 
					
						
							| 
									
										
										
										
											2023-06-05 16:56:30 +02:00
										 |  |  |         ElevatedButton.icon( | 
					
						
							|  |  |  |           icon: Icon(Icons.done_rounded, size: 16), | 
					
						
							|  |  |  |           label: Text(translate('OK')), | 
					
						
							| 
									
										
										
										
											2023-03-08 11:14:29 +08:00
										 |  |  |           onPressed: () { | 
					
						
							|  |  |  |             gFFI.dialogManager.dismissByTag(tag); | 
					
						
							|  |  |  |             do_install(); | 
					
						
							|  |  |  |           }, | 
					
						
							| 
									
										
										
										
											2023-06-05 16:56:30 +02:00
										 |  |  |           style: buttonStyle, | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-03-08 11:14:29 +08:00
										 |  |  |       ]; | 
					
						
							|  |  |  |       gFFI.dialogManager.show( | 
					
						
							| 
									
										
										
										
											2023-05-08 12:34:19 +08:00
										 |  |  |         (setState, close, context) => CustomAlertDialog( | 
					
						
							| 
									
										
										
										
											2023-03-08 11:14:29 +08:00
										 |  |  |           title: null, | 
					
						
							|  |  |  |           content: SelectionArea( | 
					
						
							| 
									
										
										
										
											2023-03-08 15:57:33 +08:00
										 |  |  |               child: | 
					
						
							| 
									
										
										
										
											2023-08-07 21:38:54 +02:00
										 |  |  |                   msgboxContent('info', 'Warning', 'confirm_install_cert_tip')), | 
					
						
							| 
									
										
										
										
											2023-03-08 11:14:29 +08:00
										 |  |  |           actions: btns, | 
					
						
							|  |  |  |           onCancel: close, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         tag: tag, | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       do_install(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void selectInstallPath() async { | 
					
						
							|  |  |  |     String? install_path = await FilePicker.platform | 
					
						
							|  |  |  |         .getDirectoryPath(initialDirectory: controller.text); | 
					
						
							|  |  |  |     if (install_path != null) { | 
					
						
							| 
									
										
										
										
											2023-06-04 19:59:09 +02:00
										 |  |  |       controller.text = join(install_path, await bind.mainGetAppName()); | 
					
						
							| 
									
										
										
										
											2022-10-09 20:32:28 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |