update android chat,server page

This commit is contained in:
csf
2022-03-22 21:47:42 +08:00
parent 6ce7018f07
commit 1daaa3a4cd
6 changed files with 237 additions and 126 deletions

View File

@@ -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);
}

View File

@@ -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<String, String> 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<String, String> res = Map()
..["name"] = "enable-keyboard"
..["value"] = 'N';
FFI.setByName('option', jsonEncode(res)); // input false by default
}();
}
toggleAudio(){
_audioOk = !_audioOk;
Map<String, String> res = Map()
..["name"] = "enable-audio"
..["value"] = _audioOk ? '' : 'N';
FFI.setByName('option', jsonEncode(res));
notifyListeners();
}
toggleFile() {
_fileOk = !_fileOk;
Map<String, String> 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<bool>((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<bool>((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<Null> startService() async {
_isStart = true;
notifyListeners();
@@ -78,7 +119,8 @@ class ServerModel with ChangeNotifier {
Future<Null> 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<String, String> 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<String, dynamic> 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<String, dynamic> evt) {}
void onClientAuthorized(Map<String, dynamic> evt) {
try{
_clients.add(Client.fromJson(jsonDecode(evt['client'])));
notifyListeners();
}catch(e){
}
}
void onClientRemove(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<bool>(
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();
}

View File

@@ -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<ChatModel>(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<ChatModel>(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<StatefulWidget> createState() => _ChatWindowOverlayState();
}
class _ChatWindowOverlayState extends State<ChatWindowOverlay> {
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<ChatWindowOverlay> {
});
}
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<ChatWindowOverlay> {
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<ChatWindowOverlay> {
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})

View File

@@ -158,20 +158,20 @@ class _PermissionCheckerState extends State<PermissionChecker> {
Widget build(BuildContext context) {
final serverModel = Provider.of<ServerModel>(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<bool>(
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) {