| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  | import 'package:settings_ui/settings_ui.dart'; | 
					
						
							|  |  |  | import 'package:flutter/material.dart'; | 
					
						
							|  |  |  | import 'package:url_launcher/url_launcher.dart'; | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  | import 'package:provider/provider.dart'; | 
					
						
							|  |  |  | import 'dart:convert'; | 
					
						
							|  |  |  | import 'package:http/http.dart' as http; | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  | import '../common.dart'; | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  | import '../widgets/dialog.dart'; | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  | import '../models/model.dart'; | 
					
						
							|  |  |  | import 'home_page.dart'; | 
					
						
							| 
									
										
										
										
											2022-04-15 17:50:15 +08:00
										 |  |  | import 'scan_page.dart'; | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  | class SettingsPage extends StatefulWidget implements PageShape { | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  |   @override | 
					
						
							| 
									
										
										
										
											2022-03-23 15:28:21 +08:00
										 |  |  |   final title = translate("Settings"); | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   final icon = Icon(Icons.settings); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							| 
									
										
										
										
											2022-04-15 17:50:15 +08:00
										 |  |  |   final appBarActions = [ScanButton()]; | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   _SettingsState createState() => _SettingsState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _SettingsState extends State<SettingsPage> { | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  |   static const url = 'https://rustdesk.com/'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  |     Provider.of<FfiModel>(context); | 
					
						
							|  |  |  |     final username = getUsername(); | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  |     return SettingsList( | 
					
						
							|  |  |  |       sections: [ | 
					
						
							|  |  |  |         SettingsSection( | 
					
						
							| 
									
										
										
										
											2022-04-05 00:51:47 +08:00
										 |  |  |           title: Text(translate("Account")), | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  |           tiles: [ | 
					
						
							|  |  |  |             SettingsTile.navigation( | 
					
						
							|  |  |  |               title: Text(username == null | 
					
						
							|  |  |  |                   ? translate("Login") | 
					
						
							|  |  |  |                   : translate("Logout") + ' ($username)'), | 
					
						
							|  |  |  |               leading: Icon(Icons.person), | 
					
						
							|  |  |  |               onPressed: (context) { | 
					
						
							|  |  |  |                 if (username == null) { | 
					
						
							|  |  |  |                   showLogin(); | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                   logout(); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               }, | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ], | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         SettingsSection( | 
					
						
							|  |  |  |           title: Text(translate("Settings")), | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  |           tiles: [ | 
					
						
							|  |  |  |             SettingsTile.navigation( | 
					
						
							| 
									
										
										
										
											2022-03-24 21:31:47 +08:00
										 |  |  |               title: Text(translate('ID/Relay Server')), | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  |               leading: Icon(Icons.cloud), | 
					
						
							|  |  |  |               onPressed: (context) { | 
					
						
							| 
									
										
										
										
											2022-04-15 17:50:15 +08:00
										 |  |  |                 showServerSettings(); | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  |               }, | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ], | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         SettingsSection( | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  |           title: Text(translate("About")), | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  |           tiles: [ | 
					
						
							|  |  |  |             SettingsTile.navigation( | 
					
						
							| 
									
										
										
										
											2022-04-15 17:50:15 +08:00
										 |  |  |                 onPressed: (context) async { | 
					
						
							|  |  |  |                   if (await canLaunch(url)) { | 
					
						
							|  |  |  |                     await launch(url); | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  |                 title: Text(translate("Version: ") + version), | 
					
						
							| 
									
										
										
										
											2022-04-15 17:50:15 +08:00
										 |  |  |                 value: Padding( | 
					
						
							|  |  |  |                   padding: EdgeInsets.symmetric(vertical: 8), | 
					
						
							|  |  |  |                   child: Text('rustdesk.com', | 
					
						
							|  |  |  |                       style: TextStyle( | 
					
						
							|  |  |  |                         decoration: TextDecoration.underline, | 
					
						
							|  |  |  |                       )), | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  |                 ), | 
					
						
							|  |  |  |                 leading: Icon(Icons.info)), | 
					
						
							|  |  |  |           ], | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ], | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-15 17:50:15 +08:00
										 |  |  | void showServerSettings() { | 
					
						
							|  |  |  |   final id = FFI.getByName('option', 'custom-rendezvous-server'); | 
					
						
							|  |  |  |   final relay = FFI.getByName('option', 'relay-server'); | 
					
						
							|  |  |  |   final api = FFI.getByName('option', 'api-server'); | 
					
						
							|  |  |  |   final key = FFI.getByName('option', 'key'); | 
					
						
							|  |  |  |   showServerSettingsWithValue(id, relay, key, api); | 
					
						
							| 
									
										
										
										
											2022-02-28 21:26:44 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-03-24 20:57:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | void showAbout() { | 
					
						
							|  |  |  |   DialogManager.show((setState, close) { | 
					
						
							|  |  |  |     return CustomAlertDialog( | 
					
						
							|  |  |  |       title: Text(translate('About') + ' RustDesk'), | 
					
						
							|  |  |  |       content: Wrap(direction: Axis.vertical, spacing: 12, children: [ | 
					
						
							|  |  |  |         Text('Version: $version'), | 
					
						
							|  |  |  |         InkWell( | 
					
						
							|  |  |  |             onTap: () async { | 
					
						
							|  |  |  |               const url = 'https://rustdesk.com/'; | 
					
						
							|  |  |  |               if (await canLaunch(url)) { | 
					
						
							|  |  |  |                 await launch(url); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             child: Padding( | 
					
						
							|  |  |  |               padding: EdgeInsets.symmetric(vertical: 8), | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  |               child: Text('rustdesk.com', | 
					
						
							| 
									
										
										
										
											2022-03-24 20:57:30 +08:00
										 |  |  |                   style: TextStyle( | 
					
						
							|  |  |  |                     decoration: TextDecoration.underline, | 
					
						
							|  |  |  |                   )), | 
					
						
							|  |  |  |             )), | 
					
						
							|  |  |  |       ]), | 
					
						
							| 
									
										
										
										
											2022-03-24 21:23:22 +08:00
										 |  |  |       actions: [], | 
					
						
							| 
									
										
										
										
											2022-03-24 20:57:30 +08:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-04-21 10:02:47 +08:00
										 |  |  |   }, clickMaskDismiss: true, backDismiss: true); | 
					
						
							| 
									
										
										
										
											2022-03-24 20:57:30 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | Future<String> login(String name, String pass) async { | 
					
						
							|  |  |  | /* js test CORS | 
					
						
							|  |  |  | const data = { username: 'example', password: 'xx' }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | fetch('http://localhost:21114/api/login', { | 
					
						
							|  |  |  |   method: 'POST', // or 'PUT'
 | 
					
						
							|  |  |  |   headers: { | 
					
						
							|  |  |  |     'Content-Type': 'application/json', | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   body: JSON.stringify(data), | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | .then(response => response.json()) | 
					
						
							|  |  |  | .then(data => { | 
					
						
							|  |  |  |   console.log('Success:', data); | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | .catch((error) => { | 
					
						
							|  |  |  |   console.error('Error:', error); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  |   final url = getUrl(); | 
					
						
							|  |  |  |   final body = { | 
					
						
							|  |  |  |     'username': name, | 
					
						
							|  |  |  |     'password': pass, | 
					
						
							|  |  |  |     'id': FFI.getByName('server_id'), | 
					
						
							|  |  |  |     'uuid': FFI.getByName('uuid') | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     final response = await http.post(Uri.parse('${url}/api/login'), | 
					
						
							|  |  |  |         headers: {"Content-Type": "application/json"}, body: json.encode(body)); | 
					
						
							|  |  |  |     return parseResp(response.body); | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     print(e); | 
					
						
							|  |  |  |     return 'Failed to access $url'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | String parseResp(String body) { | 
					
						
							|  |  |  |   final data = json.decode(body); | 
					
						
							|  |  |  |   final error = data['error']; | 
					
						
							|  |  |  |   if (error != null) { | 
					
						
							|  |  |  |     return error!; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   final token = data['access_token']; | 
					
						
							|  |  |  |   if (token != null) { | 
					
						
							|  |  |  |     FFI.setByName('option', '{"name": "access_token", "value": "$token"}'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   final info = data['user']; | 
					
						
							|  |  |  |   if (info != null) { | 
					
						
							|  |  |  |     final value = json.encode(info); | 
					
						
							|  |  |  |     FFI.setByName('option', json.encode({"name": "user_info", "value": value})); | 
					
						
							|  |  |  |     FFI.ffiModel.updateUser(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return ''; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void refreshCurrentUser() async { | 
					
						
							|  |  |  |   final token = FFI.getByName("option", "access_token"); | 
					
						
							|  |  |  |   if (token == '') return; | 
					
						
							|  |  |  |   final url = getUrl(); | 
					
						
							|  |  |  |   final body = { | 
					
						
							|  |  |  |     'id': FFI.getByName('server_id'), | 
					
						
							|  |  |  |     'uuid': FFI.getByName('uuid') | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     final response = await http.post(Uri.parse('${url}/api/currentUser'), | 
					
						
							|  |  |  |         headers: { | 
					
						
							|  |  |  |           "Content-Type": "application/json", | 
					
						
							|  |  |  |           "Authorization": "Bearer $token" | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         body: json.encode(body)); | 
					
						
							|  |  |  |     final status = response.statusCode; | 
					
						
							|  |  |  |     if (status == 401 || status == 400) { | 
					
						
							|  |  |  |       resetToken(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     parseResp(response.body); | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     print('$e'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void logout() async { | 
					
						
							|  |  |  |   final token = FFI.getByName("option", "access_token"); | 
					
						
							|  |  |  |   if (token == '') return; | 
					
						
							|  |  |  |   final url = getUrl(); | 
					
						
							|  |  |  |   final body = { | 
					
						
							|  |  |  |     'id': FFI.getByName('server_id'), | 
					
						
							|  |  |  |     'uuid': FFI.getByName('uuid') | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     await http.post(Uri.parse('${url}/api/logout'), | 
					
						
							|  |  |  |         headers: { | 
					
						
							|  |  |  |           "Content-Type": "application/json", | 
					
						
							|  |  |  |           "Authorization": "Bearer $token" | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         body: json.encode(body)); | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							| 
									
										
										
										
											2022-04-19 13:07:45 +08:00
										 |  |  |     showToast('Failed to access $url'); | 
					
						
							| 
									
										
										
										
											2022-03-28 15:44:45 +08:00
										 |  |  |   } | 
					
						
							|  |  |  |   resetToken(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void resetToken() { | 
					
						
							|  |  |  |   FFI.setByName('option', '{"name": "access_token", "value": ""}'); | 
					
						
							|  |  |  |   FFI.setByName('option', '{"name": "user_info", "value": ""}'); | 
					
						
							|  |  |  |   FFI.ffiModel.updateUser(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | String getUrl() { | 
					
						
							|  |  |  |   var url = FFI.getByName('option', 'api-server'); | 
					
						
							|  |  |  |   if (url == '') { | 
					
						
							|  |  |  |     url = FFI.getByName('option', 'custom-rendezvous-server'); | 
					
						
							|  |  |  |     if (url != '') { | 
					
						
							|  |  |  |       if (url.contains(':')) { | 
					
						
							|  |  |  |         final tmp = url.split(':'); | 
					
						
							|  |  |  |         if (tmp.length == 2) { | 
					
						
							|  |  |  |           var port = int.parse(tmp[1]) - 2; | 
					
						
							|  |  |  |           url = 'http://${tmp[0]}:$port'; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         url = 'http://${url}:21114'; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (url == '') { | 
					
						
							|  |  |  |     url = 'https://admin.rustdesk.com'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return url; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void showLogin() { | 
					
						
							|  |  |  |   final passwordController = TextEditingController(); | 
					
						
							|  |  |  |   final nameController = TextEditingController(); | 
					
						
							|  |  |  |   var loading = false; | 
					
						
							|  |  |  |   var error = ''; | 
					
						
							|  |  |  |   DialogManager.show((setState, close) { | 
					
						
							|  |  |  |     return CustomAlertDialog( | 
					
						
							|  |  |  |       title: Text(translate('Login')), | 
					
						
							|  |  |  |       content: Column(mainAxisSize: MainAxisSize.min, children: [ | 
					
						
							|  |  |  |         TextField( | 
					
						
							|  |  |  |           autofocus: true, | 
					
						
							|  |  |  |           autocorrect: false, | 
					
						
							|  |  |  |           enableSuggestions: false, | 
					
						
							|  |  |  |           keyboardType: TextInputType.visiblePassword, | 
					
						
							|  |  |  |           decoration: InputDecoration( | 
					
						
							|  |  |  |             labelText: translate('Username'), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |           controller: nameController, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         PasswordWidget(controller: passwordController), | 
					
						
							|  |  |  |       ]), | 
					
						
							|  |  |  |       actions: (loading | 
					
						
							|  |  |  |               ? <Widget>[CircularProgressIndicator()] | 
					
						
							|  |  |  |               : (error != "" | 
					
						
							|  |  |  |                   ? <Widget>[ | 
					
						
							|  |  |  |                       Text(translate(error), | 
					
						
							|  |  |  |                           style: TextStyle(color: Colors.red)) | 
					
						
							|  |  |  |                     ] | 
					
						
							|  |  |  |                   : <Widget>[])) + | 
					
						
							|  |  |  |           <Widget>[ | 
					
						
							|  |  |  |             TextButton( | 
					
						
							|  |  |  |               style: flatButtonStyle, | 
					
						
							|  |  |  |               onPressed: loading | 
					
						
							|  |  |  |                   ? null | 
					
						
							|  |  |  |                   : () { | 
					
						
							|  |  |  |                       close(); | 
					
						
							|  |  |  |                       setState(() { | 
					
						
							|  |  |  |                         loading = false; | 
					
						
							|  |  |  |                       }); | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |               child: Text(translate('Cancel')), | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             TextButton( | 
					
						
							|  |  |  |               style: flatButtonStyle, | 
					
						
							|  |  |  |               onPressed: loading | 
					
						
							|  |  |  |                   ? null | 
					
						
							|  |  |  |                   : () async { | 
					
						
							|  |  |  |                       final name = nameController.text.trim(); | 
					
						
							|  |  |  |                       final pass = passwordController.text.trim(); | 
					
						
							|  |  |  |                       if (name != "" && pass != "") { | 
					
						
							|  |  |  |                         setState(() { | 
					
						
							|  |  |  |                           loading = true; | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                         final e = await login(name, pass); | 
					
						
							|  |  |  |                         setState(() { | 
					
						
							|  |  |  |                           loading = false; | 
					
						
							|  |  |  |                           error = e; | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                         if (e == "") { | 
					
						
							|  |  |  |                           close(); | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                       } | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |               child: Text(translate('OK')), | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ], | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | String? getUsername() { | 
					
						
							|  |  |  |   final token = FFI.getByName("option", "access_token"); | 
					
						
							|  |  |  |   String? username; | 
					
						
							|  |  |  |   if (token != "") { | 
					
						
							|  |  |  |     final info = FFI.getByName("option", "user_info"); | 
					
						
							|  |  |  |     if (info != "") { | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         Map<String, dynamic> tmp = json.decode(info); | 
					
						
							|  |  |  |         username = tmp["name"]; | 
					
						
							|  |  |  |       } catch (e) { | 
					
						
							|  |  |  |         print('$e'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return username; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-04-15 17:50:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class ScanButton extends StatelessWidget { | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     return IconButton( | 
					
						
							|  |  |  |       icon: Icon(Icons.qr_code_scanner), | 
					
						
							|  |  |  |       onPressed: () { | 
					
						
							|  |  |  |         Navigator.push( | 
					
						
							|  |  |  |           context, | 
					
						
							|  |  |  |           MaterialPageRoute( | 
					
						
							|  |  |  |             builder: (BuildContext context) => ScanPage(), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |