From 1daaa3a4cdf4354f65f8218b73417509822de60d Mon Sep 17 00:00:00 2001 From: csf Date: Tue, 22 Mar 2022 21:47:42 +0800 Subject: [PATCH] update android chat,server page --- .../com/carriez/flutter_hbb/InputService.kt | 5 +- .../com/carriez/flutter_hbb/MainActivity.kt | 15 +- lib/models/model.dart | 4 +- lib/models/server_model.dart | 159 ++++++++++++++---- lib/pages/chat_page.dart | 89 ++++++---- lib/pages/server_page.dart | 91 ++++------ 6 files changed, 237 insertions(+), 126 deletions(-) diff --git a/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt b/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt index 5c94b7c34..33a2a637e 100644 --- a/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt +++ b/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt @@ -16,9 +16,8 @@ class InputService : AccessibilityService() { companion object{ var ctx:InputService? = null - fun isOpen():Boolean{ - return ctx!=null - } + val isOpen: Boolean + get() = ctx!=null } private val logTag = "input service" private var leftIsDown = false diff --git a/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt b/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt index f4b2ec82c..28ca43880 100644 --- a/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt +++ b/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt @@ -12,7 +12,6 @@ import android.provider.Settings import android.util.DisplayMetrics import android.util.Log import androidx.annotation.RequiresApi -import androidx.core.app.NotificationManagerCompat import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel @@ -77,7 +76,7 @@ class MainActivity : FlutterActivity() { "check_service" -> { flutterMethodChannel.invokeMethod( "on_permission_changed", - mapOf("name" to "input", "value" to InputService.isOpen().toString()) + mapOf("name" to "input", "value" to InputService.isOpen.toString()) ) flutterMethodChannel.invokeMethod( "on_permission_changed", @@ -88,6 +87,16 @@ class MainActivity : FlutterActivity() { initInput() result.success(true) } + "stop_input" -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + InputService.ctx?.disableSelf() + } + InputService.ctx = null + flutterMethodChannel.invokeMethod( + "on_permission_changed", + mapOf("name" to "input", "value" to InputService.isOpen.toString()) + ) + } else -> {} } } @@ -133,7 +142,7 @@ class MainActivity : FlutterActivity() { override fun onResume() { super.onResume() - val inputPer = InputService.isOpen() + val inputPer = InputService.isOpen Log.d(logTag, "onResume inputPer:$inputPer") activity.runOnUiThread { flutterMethodChannel.invokeMethod( diff --git a/lib/models/model.dart b/lib/models/model.dart index 42e20392f..1df5b820a 100644 --- a/lib/models/model.dart +++ b/lib/models/model.dart @@ -143,8 +143,8 @@ class FfiModel with ChangeNotifier { FFI.fileModel.jobError(evt); } else if (name == 'try_start_without_auth') { FFI.serverModel.loginRequest(evt); - } else if (name == 'on_client_logon') { - + } else if (name == 'on_client_authorized') { + FFI.serverModel.onClientAuthorized(evt); } else if (name == 'on_client_remove') { FFI.serverModel.onClientRemove(evt); } diff --git a/lib/models/server_model.dart b/lib/models/server_model.dart index a0822708a..cb46ab3d6 100644 --- a/lib/models/server_model.dart +++ b/lib/models/server_model.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_hbb/models/native_model.dart'; import '../common.dart'; import '../pages/server_page.dart'; import 'model.dart'; @@ -12,6 +13,7 @@ class ServerModel with ChangeNotifier { bool _isStart = false; bool _mediaOk = false; bool _inputOk = false; + late bool _audioOk; late bool _fileOk; final _serverId = TextEditingController(text: _emptyIdShow); final _serverPasswd = TextEditingController(text: ""); @@ -24,6 +26,8 @@ class ServerModel with ChangeNotifier { bool get inputOk => _inputOk; + bool get audioOk => _audioOk; + bool get fileOk => _fileOk; TextEditingController get serverId => _serverId; @@ -35,35 +39,72 @@ class ServerModel with ChangeNotifier { ServerModel() { ()async{ await Future.delayed(Duration(seconds: 2)); - final file = FFI.getByName('option', 'enable-file-transfer'); - debugPrint("got file in option:$file"); - if (file.isEmpty) { - _fileOk = false; - Map res = Map() - ..["name"] = "enable-file-transfer" - ..["value"] = "N"; - FFI.setByName('option', jsonEncode(res)); // 重新设置默认值 - } else { - if (file == "Y") { - _fileOk = true; - } else { - _fileOk = false; - } - } - }(); + final audioOption = FFI.getByName('option', 'enable-audio'); + _audioOk = audioOption.isEmpty; // audio true by default + final fileOption = FFI.getByName('option', 'enable-file-transfer'); + _fileOk = fileOption.isEmpty; + Map res = Map() + ..["name"] = "enable-keyboard" + ..["value"] = 'N'; + FFI.setByName('option', jsonEncode(res)); // input false by default + }(); + } + + toggleAudio(){ + _audioOk = !_audioOk; + Map res = Map() + ..["name"] = "enable-audio" + ..["value"] = _audioOk ? '' : 'N'; + FFI.setByName('option', jsonEncode(res)); + notifyListeners(); } toggleFile() { _fileOk = !_fileOk; Map res = Map() ..["name"] = "enable-file-transfer" - ..["value"] = _fileOk ? 'Y' : 'N'; - debugPrint("save option:$res"); + ..["value"] = _fileOk ? '' : 'N'; FFI.setByName('option', jsonEncode(res)); notifyListeners(); } + toggleInput(){ + if(_inputOk){ + PlatformFFI.invokeMethod("stop_input"); + }else{ + showInputWarnAlert(); + } + } + + toggleService() async { + if(_isStart){ + final res = await DialogManager.show((setState, close) => CustomAlertDialog( + title: Text("是否关闭"), + content: Text("关闭录屏服务将自动关闭所有已连接的控制"), + actions: [ + TextButton(onPressed: ()=>close(), child: Text("Cancel")), + ElevatedButton(onPressed: ()=>close(true), child: Text("Ok")), + ], + )); + if(res == true){ + stopService(); + } + }else{ + final res = await DialogManager.show((setState, close) => CustomAlertDialog( + title: Text("是否开启录屏服务"), + content: Text("将自动开启监听服务"), + actions: [ + TextButton(onPressed: ()=>close(), child: Text("Cancel")), + ElevatedButton(onPressed: ()=>close(true), child: Text("Ok")), + ], + )); + if(res == true){ + startService(); + } + } + } + Future startService() async { _isStart = true; notifyListeners(); @@ -78,7 +119,8 @@ class ServerModel with ChangeNotifier { Future stopService() async { _isStart = false; - release(); + _interval?.cancel(); + _interval = null; FFI.serverModel.closeAll(); await FFI.invokeMethod("stop_service"); FFI.setByName("stop_service"); @@ -121,10 +163,6 @@ class ServerModel with ChangeNotifier { notifyListeners(); } - release() { - _interval?.cancel(); - _interval = null; - } changeStatue(String name, bool value) { debugPrint("changeStatue value $value"); @@ -137,8 +175,13 @@ class ServerModel with ChangeNotifier { } break; case "input": + if(_inputOk!= value){ + Map res = Map() + ..["name"] = "enable-keyboard" + ..["value"] = value ? '' : 'N'; + FFI.setByName('option', jsonEncode(res)); + } _inputOk = value; - //TODO change option break; default: return; @@ -165,7 +208,7 @@ class ServerModel with ChangeNotifier { final Map response = Map(); response["id"] = client.id; DialogManager.show((setState, close) => CustomAlertDialog( - title: Text("Control Request"), + title: Text(client.isFileTransfer?"File":"Screen" + "Control Request"), content: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, @@ -174,6 +217,7 @@ class ServerModel with ChangeNotifier { Text(translate("Do you accept?")), SizedBox(height: 20), clientInfo(client), + Text(translate("It will be control your device!")), ], ), actions: [ @@ -198,13 +242,20 @@ class ServerModel with ChangeNotifier { notifyListeners(); close(); }), - ])); + ],onWillPop: ()async=>true,),barrierDismissible: true); } catch (e) { debugPrint("loginRequest failed,error:$e"); } } - void onClientLogin(Map evt) {} + void onClientAuthorized(Map evt) { + try{ + _clients.add(Client.fromJson(jsonDecode(evt['client']))); + notifyListeners(); + }catch(e){ + + } + } void onClientRemove(Map evt) { try { @@ -213,6 +264,8 @@ class ServerModel with ChangeNotifier { _clients.remove(client); notifyListeners(); } catch (e) { + // singleWhere fail ,reset the login dialog + DialogManager.reset(); debugPrint("onClientRemove failed,error:$e"); } } @@ -226,13 +279,16 @@ class ServerModel with ChangeNotifier { } class Client { - int id = 0; // for client connections inner count id + int id = 0; // client connections inner count id bool authorized = false; bool isFileTransfer = false; String name = ""; - String peerId = ""; // for peer user's id,show at app + String peerId = ""; // peer user's id,show at app + bool keyboard = false; + bool clipboard = false; + bool audio = false; - Client(this.authorized, this.isFileTransfer, this.name, this.peerId); + Client(this.authorized, this.isFileTransfer, this.name, this.peerId,this.keyboard,this.clipboard,this.audio); Client.fromJson(Map json) { id = json['id']; @@ -240,6 +296,9 @@ class Client { isFileTransfer = json['is_file_transfer']; name = json['name']; peerId = json['peer_id']; + keyboard= json['keyboard']; + clipboard= json['clipboard']; + audio= json['audio']; } Map toJson() { @@ -249,6 +308,46 @@ class Client { data['is_file_transfer'] = this.isFileTransfer; data['name'] = this.name; data['peer_id'] = this.peerId; + data['keyboard'] = this.keyboard; + data['clipboard'] = this.clipboard; + data['audio'] = this.audio; return data; } } + +showInputWarnAlert() async { + if (globalKey.currentContext == null) return; + DialogManager.reset(); + await showDialog( + context: globalKey.currentContext!, + builder: (alertContext) { + // TODO t + DialogManager.register(alertContext); + return AlertDialog( + title: Text("获取输入权限引导"), + content: Text.rich(TextSpan(style: TextStyle(), children: [ + TextSpan(text: "请在接下来的系统设置页\n进入"), + TextSpan(text: " [服务] ", style: TextStyle(color: MyTheme.accent)), + TextSpan(text: "配置页面\n将"), + TextSpan( + text: " [RustDesk Input] ", + style: TextStyle(color: MyTheme.accent)), + TextSpan(text: "服务开启") + ])), + actions: [ + TextButton( + child: Text(translate("Do nothing")), + onPressed: () { + DialogManager.reset(); + }), + ElevatedButton( + child: Text(translate("Go System Setting")), + onPressed: () { + FFI.serverModel.initInput(); + DialogManager.reset(); + }), + ], + ); + }); + DialogManager.drop(); +} diff --git a/lib/pages/chat_page.dart b/lib/pages/chat_page.dart index 46f239167..f06cdc315 100644 --- a/lib/pages/chat_page.dart +++ b/lib/pages/chat_page.dart @@ -4,9 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:provider/provider.dart'; -import '../main.dart'; -import '../models/model.dart'; -import '../models/native_model.dart'; import 'home_page.dart'; OverlayEntry? iconOverlayEntry; @@ -15,7 +12,6 @@ OverlayEntry? windowOverlayEntry; ChatPage chatPage = ChatPage(); class ChatPage extends StatelessWidget implements PageShape { - @override final title = "Chat"; @@ -28,17 +24,18 @@ class ChatPage extends StatelessWidget implements PageShape { @override Widget build(BuildContext context) { return Container( - color: MyTheme.grayBg, - child: Consumer(builder: (context, chatModel, child) { - return DashChat( - sendOnEnter: false, // if true,reload keyboard everytime,need fix - onSend: (chatMsg) { - chatModel.send(chatMsg); - }, - user: chatModel.me, - messages: chatModel.messages[chatModel.currentID], - ); - })); + color: MyTheme.grayBg, + child: Consumer(builder: (context, chatModel, child) { + return DashChat( + inputContainerStyle: BoxDecoration(color: Colors.white70), + sendOnEnter: false, // if true,reload keyboard everytime,need fix + onSend: (chatMsg) { + chatModel.send(chatMsg); + }, + user: chatModel.me, + messages: chatModel.messages[chatModel.currentID], + ); + })); } } @@ -116,14 +113,14 @@ toggleChatOverlay() { class ChatWindowOverlay extends StatefulWidget { final double windowWidth = 250; - final double windowHeight = 300; + final double windowHeight = 350; @override State createState() => _ChatWindowOverlayState(); } class _ChatWindowOverlayState extends State { - Offset _o = Offset(20, 20); + Offset _o = Offset(20, 80); bool _keyboardVisible = false; double _saveHeight = 0; double _lastBottomHeight = 0; @@ -154,48 +151,47 @@ class _ChatWindowOverlayState extends State { }); } - checkScreenSize(){ + checkScreenSize() { // TODO 横屏处理 } - checkKeyBoard(){ + checkKeyboard() { final bottomHeight = MediaQuery.of(context).viewInsets.bottom; final currentVisible = bottomHeight != 0; debugPrint(bottomHeight.toString() + currentVisible.toString()); // save - if (!_keyboardVisible && currentVisible){ + if (!_keyboardVisible && currentVisible) { _saveHeight = _o.dy; debugPrint("on save $_saveHeight"); } // reset - if (_lastBottomHeight>0 && bottomHeight == 0){ + if (_lastBottomHeight > 0 && bottomHeight == 0) { debugPrint("on reset"); - _o = Offset(_o.dx,_saveHeight); + _o = Offset(_o.dx, _saveHeight); } // onKeyboardVisible - if (_keyboardVisible && currentVisible){ - - + if (_keyboardVisible && currentVisible) { final sumHeight = bottomHeight + widget.windowHeight; final contextHeight = MediaQuery.of(context).size.height; - debugPrint("prepare update sumHeight:$sumHeight,contextHeight:$contextHeight"); - if(sumHeight + _o.dy > contextHeight){ + debugPrint( + "prepare update sumHeight:$sumHeight,contextHeight:$contextHeight"); + if (sumHeight + _o.dy > contextHeight) { final y = contextHeight - sumHeight; debugPrint("on update"); - _o = Offset(_o.dx,y); + _o = Offset(_o.dx, y); } } _keyboardVisible = currentVisible; _lastBottomHeight = bottomHeight; - } + @override Widget build(BuildContext context) { - checkKeyBoard(); + checkKeyboard(); checkScreenSize(); return Positioned( top: _o.dy, @@ -206,8 +202,35 @@ class _ChatWindowOverlayState extends State { resizeToAvoidBottomInset: false, appBar: CustomAppBar( onPanUpdate: (d) => changeOffset(d.delta), - appBar: AppBar( - title: Text("Chat")), + appBar: Container( + color: MyTheme.accent50, + height: 50, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding(padding: EdgeInsets.symmetric(horizontal: 15),child: Text( + "Chat", + style: TextStyle( + color: Colors.white, + fontFamily: 'WorkSans', + fontWeight: FontWeight.bold, + fontSize: 20), + )), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton(onPressed: () { + hideChatWindowOverlay(); + }, icon: Icon(Icons.keyboard_arrow_down)), + IconButton(onPressed: () { + hideChatWindowOverlay(); + hideChatIconOverlay(); + }, icon: Icon(Icons.close)) + ], + ) + ], + ), + ), ), body: chatPage, )); @@ -216,7 +239,7 @@ class _ChatWindowOverlayState extends State { class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { final GestureDragUpdateCallback onPanUpdate; - final AppBar appBar; + final Widget appBar; const CustomAppBar( {Key? key, required this.onPanUpdate, required this.appBar}) diff --git a/lib/pages/server_page.dart b/lib/pages/server_page.dart index 162f58bbd..ad5589ece 100644 --- a/lib/pages/server_page.dart +++ b/lib/pages/server_page.dart @@ -158,20 +158,20 @@ class _PermissionCheckerState extends State { Widget build(BuildContext context) { final serverModel = Provider.of(context); final androidVersion = PlatformFFI.androidVersion ?? 0; - final hasAudioPermission = androidVersion>=33; + final hasAudioPermission = androidVersion>=30; return PaddingCard( title: translate("Configuration Permissions"), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ PermissionRow(translate("Screen Capture"), serverModel.mediaOk, - serverModel.startService), + serverModel.toggleService), PermissionRow(translate("Mouse Control"), serverModel.inputOk, - showInputWarnAlert), + serverModel.toggleInput), PermissionRow(translate("File Transfer"), serverModel.fileOk, serverModel.toggleFile), hasAudioPermission?PermissionRow(translate("Audio Capture"), serverModel.inputOk, - showInputWarnAlert):Text("* 当前安卓版本不支持音频捕获",style: TextStyle(color: MyTheme.darkGray),), + serverModel.toggleAudio):Text("* 当前安卓版本不支持音频捕获",style: TextStyle(color: MyTheme.darkGray),), SizedBox(height: 8), serverModel.mediaOk ? ElevatedButton.icon( @@ -235,7 +235,8 @@ class ConnectionManager extends StatelessWidget { return Column( children: serverModel.clients .map((client) => PaddingCard( - title: translate("Connection"), + title: translate(client.isFileTransfer?"File Connection":"Screen Connection"), + titleIcon: client.isFileTransfer?Icons.folder_outlined:Icons.mobile_screen_share, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -259,9 +260,10 @@ class ConnectionManager extends StatelessWidget { } class PaddingCard extends StatelessWidget { - PaddingCard({required this.child, this.title}); + PaddingCard({required this.child, this.title,this.titleIcon}); final String? title; + final IconData? titleIcon; final Widget child; @override @@ -272,15 +274,20 @@ class PaddingCard extends StatelessWidget { 0, Padding( padding: EdgeInsets.symmetric(vertical: 5.0), - child: Text( - title!, - style: TextStyle( - fontFamily: 'WorkSans', - fontWeight: FontWeight.bold, - fontSize: 22, - color: MyTheme.accent80, - ), - ))); + child: Row( + children: [ + titleIcon !=null?Padding(padding: EdgeInsets.only(right: 10),child:Icon(titleIcon,color: MyTheme.accent80,size: 30)):SizedBox.shrink(), + Text( + title!, + style: TextStyle( + fontFamily: 'WorkSans', + fontWeight: FontWeight.bold, + fontSize: 22, + color: MyTheme.accent80, + ), + ) + ], + ) )); } return Container( width: double.maxFinite, @@ -306,51 +313,25 @@ Widget clientInfo(Client client) { CircleAvatar( child: Text(client.name[0]), backgroundColor: MyTheme.border), SizedBox(width: 12), - Text(client.name, style: TextStyle(color: MyTheme.idColor)), - SizedBox(width: 8), - Text(client.peerId, style: TextStyle(color: MyTheme.idColor)) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center + ,children: [ + Text(client.name, style: TextStyle(color: MyTheme.idColor,fontSize: 20)), + SizedBox(width: 8), + Text(client.peerId, style: TextStyle(color: MyTheme.idColor,fontSize: 10)) + ]) ], ), - Text("类型:${client.isFileTransfer?"管理文件":"屏幕控制"}" ,style: TextStyle(color: MyTheme.darkGray)) + // !client.isFileTransfer?Row( + // children: [ + // client.audio?Icon(Icons.volume_up):SizedBox.shrink(), + // client.keyboard?Icon(Icons.mouse):SizedBox.shrink(), + // ], + // ):SizedBox.shrink() ]); } -showInputWarnAlert() async { - if (globalKey.currentContext == null) return; - DialogManager.reset(); - await showDialog( - context: globalKey.currentContext!, - builder: (alertContext) { - // TODO t - DialogManager.register(alertContext); - return AlertDialog( - title: Text("获取输入权限引导"), - content: Text.rich(TextSpan(style: TextStyle(), children: [ - TextSpan(text: "请在接下来的系统设置页\n进入"), - TextSpan(text: " [服务] ", style: TextStyle(color: MyTheme.accent)), - TextSpan(text: "配置页面\n将"), - TextSpan( - text: " [RustDesk Input] ", - style: TextStyle(color: MyTheme.accent)), - TextSpan(text: "服务开启") - ])), - actions: [ - TextButton( - child: Text(translate("Do nothing")), - onPressed: () { - DialogManager.reset(); - }), - ElevatedButton( - child: Text(translate("Go System Setting")), - onPressed: () { - FFI.serverModel.initInput(); - DialogManager.reset(); - }), - ], - ); - }); - DialogManager.drop(); -} void toAndroidChannelInit() { FFI.setMethodCallHandler((method, arguments) {