import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:settings_ui/settings_ui.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:provider/provider.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; import '../common.dart'; import '../widgets/dialog.dart'; import '../models/model.dart'; import 'home_page.dart'; class SettingsPage extends StatefulWidget implements PageShape { @override final title = translate("Settings"); @override final icon = Icon(Icons.settings); @override final appBarActions = []; @override _SettingsState createState() => _SettingsState(); } class _SettingsState extends State { static const url = 'https://rustdesk.com/'; @override Widget build(BuildContext context) { Provider.of(context); final username = getUsername(); return SettingsList( sections: [ SettingsSection( title: Text(translate("Account")), 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")), tiles: [ SettingsTile.navigation( title: Text(translate('ID/Relay Server')), leading: Icon(Icons.cloud), onPressed: (context) { showServer(); }, ), ], ), SettingsSection( title: Text(translate("About")), tiles: [ SettingsTile.navigation( title: Text(translate("Version: ") + version), value: InkWell( onTap: () async { if (await canLaunch(url)) { await launch(url); } }, child: Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Text('rustdesk.com', style: TextStyle( decoration: TextDecoration.underline, )), ), ), leading: Icon(Icons.info)), ], ), ], ); } } void showServer() { final formKey = GlobalKey(); final id0 = FFI.getByName('option', 'custom-rendezvous-server'); final relay0 = FFI.getByName('option', 'relay-server'); final api0 = FFI.getByName('option', 'api-server'); final key0 = FFI.getByName('option', 'key'); var id = ''; var relay = ''; var key = ''; var api = ''; DialogManager.show((setState, close) { return CustomAlertDialog( title: Text(translate('ID/Relay Server')), content: Form( key: formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( initialValue: id0, decoration: InputDecoration( labelText: translate('ID Server'), ), validator: validate, onSaved: (String? value) { if (value != null) id = value.trim(); }, ) ] + (isAndroid ? [ TextFormField( initialValue: relay0, decoration: InputDecoration( labelText: translate('Relay Server'), ), validator: validate, onSaved: (String? value) { if (value != null) relay = value.trim(); }, ) ] : []) + [ TextFormField( initialValue: api0, decoration: InputDecoration( labelText: translate('API Server'), ), validator: validate, onSaved: (String? value) { if (value != null) api = value.trim(); }, ), TextFormField( initialValue: key0, decoration: InputDecoration( labelText: 'Key', ), validator: null, onSaved: (String? value) { if (value != null) key = value.trim(); }, ), ])), actions: [ TextButton( style: flatButtonStyle, onPressed: () { close(); }, child: Text(translate('Cancel')), ), TextButton( style: flatButtonStyle, onPressed: () { if (formKey.currentState != null && formKey.currentState!.validate()) { formKey.currentState!.save(); if (id != id0) FFI.setByName('option', '{"name": "custom-rendezvous-server", "value": "$id"}'); if (relay != relay0) FFI.setByName( 'option', '{"name": "relay-server", "value": "$relay"}'); if (key != key0) FFI.setByName('option', '{"name": "key", "value": "$key"}'); if (api != api0) FFI.setByName( 'option', '{"name": "api-server", "value": "$api"}'); close(); } }, child: Text(translate('OK')), ), ], onWillPop: () async { return true; }, ); }, barrierDismissible: true); } String? validate(value) { value = value.trim(); if (value.isEmpty) { return null; } final res = FFI.getByName('test_if_valid_server', value); return res.isEmpty ? null : res; } 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), child: Text('rustdesk.com', style: TextStyle( decoration: TextDecoration.underline, )), )), ]), actions: [], onWillPop: () async { return true; }, ); }, barrierDismissible: true); } Future 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) { EasyLoading.showToast('Failed to access $url', maskType: EasyLoadingMaskType.black); } 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 ? [CircularProgressIndicator()] : (error != "" ? [ Text(translate(error), style: TextStyle(color: Colors.red)) ] : [])) + [ 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 tmp = json.decode(info); username = tmp["name"]; } catch (e) { print('$e'); } } } return username; }