diff --git a/lib/common.dart b/lib/common.dart index 6461b8319..398e85076 100644 --- a/lib/common.dart +++ b/lib/common.dart @@ -114,7 +114,7 @@ void msgBox(String type, String title, String text, Expanded(child: Container()), wrap(Translator.call('OK'), () { EasyLoading.dismiss(); - Navigator.pop(globalKey.currentContext!); // TODO + Navigator.pop(globalKey.currentContext!); }) ]; if (hasCancel == null) { @@ -202,3 +202,4 @@ bool isAndroid = false; bool isIOS = false; bool isWeb = false; bool isDesktop = false; +var version = ""; diff --git a/lib/models/chat_model.dart b/lib/models/chat_model.dart new file mode 100644 index 000000000..46091b5cd --- /dev/null +++ b/lib/models/chat_model.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +class ChatModel with ChangeNotifier { + +} \ No newline at end of file diff --git a/lib/models/model.dart b/lib/models/model.dart index e9bc7c061..8e659facd 100644 --- a/lib/models/model.dart +++ b/lib/models/model.dart @@ -692,11 +692,12 @@ extension ToString on MouseButtons{ class FFI { - static String id = ""; + static var id = ""; static var shift = false; static var ctrl = false; static var alt = false; static var command = false; + static var version = ""; static final imageModel = ImageModel(); static final ffiModel = FfiModel(); static final cursorModel = CursorModel(); @@ -823,10 +824,6 @@ class FFI { PlatformFFI.setByName(name, value); } - static Future getVersion() async { - return await PlatformFFI.getVersion(); - } - static handleMouse(Map evt) { var type = ''; var isMove = false; diff --git a/lib/models/native_model.dart b/lib/models/native_model.dart index 730a0aef7..aca1b1869 100644 --- a/lib/models/native_model.dart +++ b/lib/models/native_model.dart @@ -107,6 +107,7 @@ class PlatformFFI { } catch (e) { print(e); } + version = await getVersion(); } static void startDesktopWebListener( diff --git a/lib/pages/chat_page.dart b/lib/pages/chat_page.dart new file mode 100644 index 000000000..f3262c6c5 --- /dev/null +++ b/lib/pages/chat_page.dart @@ -0,0 +1,49 @@ +import 'package:dash_chat/dash_chat.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common.dart'; +import 'home_page.dart'; + + +class ChatPage extends StatelessWidget implements PageShape { + + final FocusNode _focusNode = FocusNode(); + + @override + final title = "Chat"; + + @override + final icon = Icon(Icons.chat); + + @override + final appBarActions = []; + + @override + Widget build(BuildContext context) { + return Container( + color: MyTheme.darkGray, + child: DashChat( + focusNode: _focusNode, + onSend: (ChatMessage) {}, + user: ChatUser(uid: "111", name: "Bob"), + messages: [ + ChatMessage( + text: "hello", user: ChatUser(uid: "111", name: "Bob")), + ChatMessage( + text: "hi", user: ChatUser(uid: "222", name: "Alice")), + ChatMessage( + text: "hello", user: ChatUser(uid: "111", name: "Bob")), + ChatMessage( + text: "hi", user: ChatUser(uid: "222", name: "Alice")), + ChatMessage( + text: "hello", user: ChatUser(uid: "111", name: "Bob")), + ChatMessage( + text: "hi", user: ChatUser(uid: "222", name: "Alice")), + ChatMessage( + text: "hello", user: ChatUser(uid: "111", name: "Bob")), + ChatMessage( + text: "hi", user: ChatUser(uid: "222", name: "Alice")), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/connection_page.dart b/lib/pages/connection_page.dart new file mode 100644 index 000000000..083b762fa --- /dev/null +++ b/lib/pages/connection_page.dart @@ -0,0 +1,268 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'dart:async'; +import '../common.dart'; +import '../models/model.dart'; +import 'home_page.dart'; +import 'remote_page.dart'; + +class ConnectionPage extends StatefulWidget implements PageShape { + ConnectionPage({Key? key}) : super(key: key); + + @override + final icon = Icon(Icons.connected_tv); + + @override + final title = translate("Connection"); + + @override + final appBarActions = []; + + @override + _ConnectionPageState createState() => _ConnectionPageState(); +} + +class _ConnectionPageState extends State { + final _idController = TextEditingController(); + var _updateUrl = ''; + var _menuPos; + + @override + void initState() { + super.initState(); + if (isAndroid) { + Timer(Duration(seconds: 5), () { + _updateUrl = FFI.getByName('software_update_url'); + if (_updateUrl.isNotEmpty) setState(() {}); + }); + } + } + + @override + Widget build(BuildContext context) { + Provider.of(context); + if (_idController.text.isEmpty) _idController.text = FFI.getId(); + return SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + getUpdateUI(), + getSearchBarUI(), + Container(height: 12), + getPeers(), + ]), + ); + } + + void onConnect() { + var id = _idController.text.trim(); + connect(id); + } + + void connect(String id) { + if (id == '') return; + id = id.replaceAll(' ', ''); + () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => RemotePage(id: id), + ), + ); + setState(() {}); + }(); + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.unfocus(); + } + } + + Widget getUpdateUI() { + return _updateUrl.isEmpty + ? SizedBox(height: 0) + : InkWell( + onTap: () async { + final url = _updateUrl + '.apk'; + if (await canLaunch(url)) { + await launch(url); + } + }, + child: Container( + alignment: AlignmentDirectional.center, + width: double.infinity, + color: Colors.pinkAccent, + padding: EdgeInsets.symmetric(vertical: 12), + child: Text(translate('Download new version'), + style: TextStyle( + color: Colors.white, fontWeight: FontWeight.bold)))); + } + + Widget getSearchBarUI() { + if (!FFI.ffiModel.initialized) { + return Container(); + } + var w = Padding( + padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0.0), + child: Container( + height: 84, + child: Padding( + padding: const EdgeInsets.only(top: 8, bottom: 8), + child: Ink( + decoration: BoxDecoration( + color: MyTheme.white, + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(13.0), + bottomLeft: Radius.circular(13.0), + topLeft: Radius.circular(13.0), + topRight: Radius.circular(13.0), + ), + ), + child: Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.only(left: 16, right: 16), + child: TextField( + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.visiblePassword, + // keyboardType: TextInputType.number, + style: TextStyle( + fontFamily: 'WorkSans', + fontWeight: FontWeight.bold, + fontSize: 30, + color: MyTheme.idColor, + ), + decoration: InputDecoration( + labelText: translate('Remote ID'), + // hintText: 'Enter your remote ID', + border: InputBorder.none, + helperStyle: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: MyTheme.darkGray, + ), + labelStyle: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + letterSpacing: 0.2, + color: MyTheme.darkGray, + ), + ), + controller: _idController, + ), + ), + ), + SizedBox( + width: 60, + height: 60, + child: IconButton( + icon: Icon(Icons.arrow_forward, + color: MyTheme.darkGray, size: 45), + onPressed: onConnect, + autofocus: _idController.text.isNotEmpty, + ), + ) + ], + ), + ), + ), + ), + ); + return Center( + child: Container(constraints: BoxConstraints(maxWidth: 600), child: w)); + } + + @override + void dispose() { + _idController.dispose(); + super.dispose(); + } + + Widget getPlatformImage(String platform) { + platform = platform.toLowerCase(); + if (platform == 'mac os') + platform = 'mac'; + else if (platform != 'linux') platform = 'win'; + return Image.asset('assets/$platform.png', width: 24, height: 24); + } + + Widget getPeers() { + if (!FFI.ffiModel.initialized) { + return Container(); + } + final size = MediaQuery.of(context).size; + final space = 8.0; + var width = size.width - 2 * space; + final minWidth = 320.0; + if (size.width > minWidth + 2 * space) { + final n = (size.width / (minWidth + 2 * space)).floor(); + width = size.width / n - 2 * space; + } + final cards = []; + var peers = FFI.peers(); + peers.forEach((p) { + cards.add(Container( + width: width, + child: Card( + child: GestureDetector( + onTap: () => { + if (!isDesktop) {connect('${p.id}')} + }, + onDoubleTap: () => { + if (isDesktop) {connect('${p.id}')} + }, + onLongPressStart: (details) { + var x = details.globalPosition.dx; + var y = details.globalPosition.dy; + this._menuPos = RelativeRect.fromLTRB(x, y, x, y); + this.showPeerMenu(context, p.id); + }, + child: ListTile( + contentPadding: const EdgeInsets.only(left: 12), + subtitle: Text('${p.username}@${p.hostname}'), + title: Text('${p.id}'), + leading: Container( + padding: const EdgeInsets.all(6), + child: getPlatformImage('${p.platform}'), + color: str2color('${p.id}${p.platform}', 0x7f)), + trailing: InkWell( + child: Padding( + padding: const EdgeInsets.all(12), + child: Icon(Icons.more_vert)), + onTapDown: (e) { + var x = e.globalPosition.dx; + var y = e.globalPosition.dy; + this._menuPos = RelativeRect.fromLTRB(x, y, x, y); + }, + onDoubleTap: () {}, + onTap: () { + showPeerMenu(context, p.id); + }), + ))))); + }); + return Wrap(children: cards, spacing: space, runSpacing: space); + } + + void showPeerMenu(BuildContext context, String id) async { + var value = await showMenu( + context: context, + position: this._menuPos, + items: [ + PopupMenuItem( + child: Text(translate('Remove')), value: 'remove'), + ], + elevation: 8, + ); + if (value == 'remove') { + setState(() => FFI.setByName('remove', '$id')); + () async { + removePreference(id); + }(); + } + } +} \ No newline at end of file diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index d2dd9b718..2900da268 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,12 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hbb/pages/chat_page.dart'; import 'package:flutter_hbb/pages/server_page.dart'; -import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'dart:async'; +import 'package:flutter_hbb/pages/settings_page.dart'; import '../common.dart'; -import '../models/model.dart'; -import 'remote_page.dart'; +import 'connection_page.dart'; abstract class PageShape extends Widget { final String title = ""; @@ -23,7 +20,12 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { var _selectedIndex = 0; - final List _pages = [ConnectionPage(), ServerPage()]; + final List _pages = [ + ConnectionPage(), + ChatPage(), + ServerPage(), + SettingsPage() + ]; @override Widget build(BuildContext context) { @@ -51,391 +53,3 @@ class _HomePageState extends State { ); } } - -class ConnectionPage extends StatefulWidget implements PageShape { - ConnectionPage({Key? key}) : super(key: key); - - @override - final icon = Icon(Icons.connected_tv); - - @override - final title = translate("Connection"); - - @override - final appBarActions = [ - PopupMenuButton( - itemBuilder: (context) => [ - PopupMenuItem( - child: Text(translate('ID Server')), value: 'id_server'), - PopupMenuItem( - child: Text(translate('About') + ' RustDesk'), value: 'about') - ], - onSelected: (value) { - if (value == 'id_server') { - showServer(); - } else if (value == 'about') { - showAbout(); - } - }) - ]; - - @override - _ConnectionPageState createState() => _ConnectionPageState(); -} - -class _ConnectionPageState extends State { - final _idController = TextEditingController(); - var _updateUrl = ''; - var _menuPos; - - @override - void initState() { - super.initState(); - if (isAndroid) { - Timer(Duration(seconds: 5), () { - _updateUrl = FFI.getByName('software_update_url'); - if (_updateUrl.isNotEmpty) setState(() {}); - }); - } - } - - @override - Widget build(BuildContext context) { - Provider.of(context); - if (_idController.text.isEmpty) _idController.text = FFI.getId(); - return SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - getUpdateUI(), - getSearchBarUI(), - Container(height: 12), - getPeers(), - ]), - ); - } - - void onConnect() { - var id = _idController.text.trim(); - connect(id); - } - - void connect(String id) { - if (id == '') return; - id = id.replaceAll(' ', ''); - () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => RemotePage(id: id), - ), - ); - setState(() {}); - }(); - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.unfocus(); - } - } - - Widget getUpdateUI() { - return _updateUrl.isEmpty - ? SizedBox(height: 0) - : InkWell( - onTap: () async { - final url = _updateUrl + '.apk'; - if (await canLaunch(url)) { - await launch(url); - } - }, - child: Container( - alignment: AlignmentDirectional.center, - width: double.infinity, - color: Colors.pinkAccent, - padding: EdgeInsets.symmetric(vertical: 12), - child: Text(translate('Download new version'), - style: TextStyle( - color: Colors.white, fontWeight: FontWeight.bold)))); - } - - Widget getSearchBarUI() { - if (!FFI.ffiModel.initialized) { - return Container(); - } - var w = Padding( - padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0.0), - child: Container( - height: 84, - child: Padding( - padding: const EdgeInsets.only(top: 8, bottom: 8), - child: Ink( - decoration: BoxDecoration( - color: MyTheme.white, - borderRadius: const BorderRadius.only( - bottomRight: Radius.circular(13.0), - bottomLeft: Radius.circular(13.0), - topLeft: Radius.circular(13.0), - topRight: Radius.circular(13.0), - ), - ), - child: Row( - children: [ - Expanded( - child: Container( - padding: const EdgeInsets.only(left: 16, right: 16), - child: TextField( - autocorrect: false, - enableSuggestions: false, - keyboardType: TextInputType.visiblePassword, - // keyboardType: TextInputType.number, - style: TextStyle( - fontFamily: 'WorkSans', - fontWeight: FontWeight.bold, - fontSize: 30, - color: MyTheme.idColor, - ), - decoration: InputDecoration( - labelText: translate('Remote ID'), - // hintText: 'Enter your remote ID', - border: InputBorder.none, - helperStyle: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: MyTheme.darkGray, - ), - labelStyle: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - letterSpacing: 0.2, - color: MyTheme.darkGray, - ), - ), - autofocus: _idController.text.isEmpty, - controller: _idController, - ), - ), - ), - SizedBox( - width: 60, - height: 60, - child: IconButton( - icon: Icon(Icons.arrow_forward, - color: MyTheme.darkGray, size: 45), - onPressed: onConnect, - autofocus: _idController.text.isNotEmpty, - ), - ) - ], - ), - ), - ), - ), - ); - return Center( - child: Container(constraints: BoxConstraints(maxWidth: 600), child: w)); - } - - @override - void dispose() { - _idController.dispose(); - super.dispose(); - } - - Widget getPlatformImage(String platform) { - platform = platform.toLowerCase(); - if (platform == 'mac os') - platform = 'mac'; - else if (platform != 'linux') platform = 'win'; - return Image.asset('assets/$platform.png', width: 24, height: 24); - } - - Widget getPeers() { - if (!FFI.ffiModel.initialized) { - return Container(); - } - final size = MediaQuery.of(context).size; - final space = 8.0; - var width = size.width - 2 * space; - final minWidth = 320.0; - if (size.width > minWidth + 2 * space) { - final n = (size.width / (minWidth + 2 * space)).floor(); - width = size.width / n - 2 * space; - } - final cards = []; - var peers = FFI.peers(); - peers.forEach((p) { - cards.add(Container( - width: width, - child: Card( - child: GestureDetector( - onTap: () => { - if (!isDesktop) {connect('${p.id}')} - }, - onDoubleTap: () => { - if (isDesktop) {connect('${p.id}')} - }, - onLongPressStart: (details) { - var x = details.globalPosition.dx; - var y = details.globalPosition.dy; - this._menuPos = RelativeRect.fromLTRB(x, y, x, y); - this.showPeerMenu(context, p.id); - }, - child: ListTile( - contentPadding: const EdgeInsets.only(left: 12), - subtitle: Text('${p.username}@${p.hostname}'), - title: Text('${p.id}'), - leading: Container( - padding: const EdgeInsets.all(6), - child: getPlatformImage('${p.platform}'), - color: str2color('${p.id}${p.platform}', 0x7f)), - trailing: InkWell( - child: Padding( - padding: const EdgeInsets.all(12), - child: Icon(Icons.more_vert)), - onTapDown: (e) { - var x = e.globalPosition.dx; - var y = e.globalPosition.dy; - this._menuPos = RelativeRect.fromLTRB(x, y, x, y); - }, - onDoubleTap: () {}, - onTap: () { - showPeerMenu(context, p.id); - }), - ))))); - }); - return Wrap(children: cards, spacing: space, runSpacing: space); - } - - void showPeerMenu(BuildContext context, String id) async { - var value = await showMenu( - context: context, - position: this._menuPos, - items: [ - PopupMenuItem( - child: Text(translate('Remove')), value: 'remove'), - ], - elevation: 8, - ); - if (value == 'remove') { - setState(() => FFI.setByName('remove', '$id')); - () async { - removePreference(id); - }(); - } - } -} - -void showServer() { - final formKey = GlobalKey(); - final id0 = FFI.getByName('option', 'custom-rendezvous-server'); - final relay0 = FFI.getByName('option', 'relay-server'); - final key0 = FFI.getByName('option', 'key'); - var id = ''; - var relay = ''; - var key = ''; - showAlertDialog((setState) => Tuple3( - Text(translate('ID Server')), - 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(); - }, - ), - /* - TextFormField( - initialValue: relay0, - decoration: InputDecoration( - labelText: translate('Relay Server'), - ), - validator: validate, - onSaved: (String value) { - relay = value.trim(); - }, - ), - */ - TextFormField( - initialValue: key0, - decoration: InputDecoration( - labelText: 'Key', - ), - validator: null, - onSaved: (String? value) { - if (value != null) key = value.trim(); - }, - ), - ])), - [ - TextButton( - style: flatButtonStyle, - onPressed: () { - DialogManager.reset(); - }, - 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"}'); - DialogManager.reset(); - } - }, - child: Text(translate('OK')), - ), - ], - )); -} - -Future showAbout() async { - var version = await FFI.getVersion(); - showAlertDialog( - (setState) => Tuple3( - SizedBox.shrink(), // TODO test old:null - 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('Support', - style: TextStyle( - decoration: TextDecoration.underline, - )), - )), - ]), - []), - () async => true, - 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; -} diff --git a/lib/pages/server_page.dart b/lib/pages/server_page.dart index 269536d4e..b2a80be08 100644 --- a/lib/pages/server_page.dart +++ b/lib/pages/server_page.dart @@ -260,8 +260,8 @@ class PermissionRow extends StatelessWidget { Row( children: [ SizedBox( - width: 60, - child: Text(name + ":", + width: 70, + child: Text(name, style: TextStyle(fontSize: 16.0, color: MyTheme.accent50))), SizedBox( width: 50, diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart new file mode 100644 index 000000000..2fb38b604 --- /dev/null +++ b/lib/pages/settings_page.dart @@ -0,0 +1,149 @@ +import 'package:settings_ui/settings_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:tuple/tuple.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../common.dart'; +import '../models/model.dart'; +import 'home_page.dart'; + +class SettingsPage extends StatelessWidget implements PageShape { + @override + final title = "Settings"; + + @override + final icon = Icon(Icons.settings); + + @override + final appBarActions = []; + + static const url = 'https://rustdesk.com/'; + + @override + Widget build(BuildContext context) { + return SettingsList( + sections: [ + SettingsSection( + title: Text("Common"), + tiles: [ + SettingsTile.navigation( + title: Text(translate('ID Server')), + leading: Icon(Icons.cloud), + onPressed: (context) { + showServer(); + }, + ), + ], + ), + SettingsSection( + title: Text("About"), + tiles: [ + SettingsTile.navigation( + title: Text("Version: "+version), + value: InkWell( + onTap: () async { + const url = 'https://rustdesk.com/'; + if (await canLaunch(url)) { + await launch(url); + } + }, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text('Support', + 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 key0 = FFI.getByName('option', 'key'); + var id = ''; + var relay = ''; + var key = ''; + showAlertDialog((setState) => Tuple3( + Text(translate('ID Server')), + 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(); + }, + ), + /* + TextFormField( + initialValue: relay0, + decoration: InputDecoration( + labelText: translate('Relay Server'), + ), + validator: validate, + onSaved: (String value) { + relay = value.trim(); + }, + ), + */ + TextFormField( + initialValue: key0, + decoration: InputDecoration( + labelText: 'Key', + ), + validator: null, + onSaved: (String? value) { + if (value != null) key = value.trim(); + }, + ), + ])), + [ + TextButton( + style: flatButtonStyle, + onPressed: () { + DialogManager.reset(); + }, + 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"}'); + DialogManager.reset(); + } + }, + child: Text(translate('OK')), + ), + ], + )); +} + +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; +} diff --git a/pubspec.yaml b/pubspec.yaml index 7d8a9ef70..b37dd33f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,8 @@ dependencies: url_launcher: ^6.0.9 shared_preferences: ^2.0.6 toggle_switch: ^1.4.0 + dash_chat: ^1.1.16 + settings_ui: ^2.0.2 dev_dependencies: flutter_launcher_icons: ^0.9.1