Merge pull request #3183 from Heap-Hop/opt_keytools

Opt keytools [mobile]
This commit is contained in:
RustDesk 2023-02-13 09:25:59 +08:00 committed by GitHub
commit 9f4437b9bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 171 additions and 110 deletions

View File

@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/mobile/widgets/gesture_help.dart'; import 'package:flutter_hbb/mobile/widgets/gesture_help.dart';
import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
@ -33,17 +34,16 @@ class RemotePage extends StatefulWidget {
} }
class _RemotePageState extends State<RemotePage> { class _RemotePageState extends State<RemotePage> {
Timer? _interval;
Timer? _timer; Timer? _timer;
bool _showBar = !isWebDesktop; bool _showBar = !isWebDesktop;
double _bottom = 0; bool _showGestureHelp = false;
String _value = ''; String _value = '';
double _scale = 1; double _scale = 1;
double _mouseScrollIntegral = 0; // mouse scroll speed controller double _mouseScrollIntegral = 0; // mouse scroll speed controller
Orientation? _currentOrientation; Orientation? _currentOrientation;
var _more = true; final keyboardVisibilityController = KeyboardVisibilityController();
var _fn = false; late final StreamSubscription<bool> keyboardSubscription;
final FocusNode _mobileFocusNode = FocusNode(); final FocusNode _mobileFocusNode = FocusNode();
final FocusNode _physicalFocusNode = FocusNode(); final FocusNode _physicalFocusNode = FocusNode();
var _showEdit = false; // use soft keyboard var _showEdit = false; // use soft keyboard
@ -58,14 +58,14 @@ class _RemotePageState extends State<RemotePage> {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
gFFI.dialogManager gFFI.dialogManager
.showLoading(translate('Connecting...'), onCancel: closeConnection); .showLoading(translate('Connecting...'), onCancel: closeConnection);
_interval =
Timer.periodic(Duration(milliseconds: 30), (timer) => interval());
}); });
Wakelock.enable(); Wakelock.enable();
_physicalFocusNode.requestFocus(); _physicalFocusNode.requestFocus();
gFFI.ffiModel.updateEventListener(widget.id); gFFI.ffiModel.updateEventListener(widget.id);
gFFI.inputModel.listenToMouse(true); gFFI.inputModel.listenToMouse(true);
gFFI.qualityMonitorModel.checkShowQualityMonitor(widget.id); gFFI.qualityMonitorModel.checkShowQualityMonitor(widget.id);
keyboardSubscription =
keyboardVisibilityController.onChange.listen(onSoftKeyboardChanged);
} }
@override @override
@ -76,47 +76,26 @@ class _RemotePageState extends State<RemotePage> {
_mobileFocusNode.dispose(); _mobileFocusNode.dispose();
_physicalFocusNode.dispose(); _physicalFocusNode.dispose();
gFFI.close(); gFFI.close();
_interval?.cancel();
_timer?.cancel(); _timer?.cancel();
gFFI.dialogManager.dismissAll(); gFFI.dialogManager.dismissAll();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: SystemUiOverlay.values); overlays: SystemUiOverlay.values);
Wakelock.disable(); Wakelock.disable();
keyboardSubscription.cancel();
super.dispose(); super.dispose();
} }
void resetTool() { void onSoftKeyboardChanged(bool visible) {
inputModel.resetModifiers(); if (!visible) {
} SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
// [pi.version.isNotEmpty] -> check ready or not, avoid login without soft-keyboard
bool isKeyboardShown() { if (gFFI.chatModel.chatWindowOverlayEntry == null &&
return _bottom >= 100; gFFI.ffiModel.pi.version.isNotEmpty) {
} gFFI.invokeMethod("enable_soft_keyboard", false);
}
// crash on web before widget initiated.
void intervalUnsafe() {
var v = MediaQuery.of(context).viewInsets.bottom;
if (v != _bottom) {
resetTool();
setState(() {
_bottom = v;
if (v < 100) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: []);
// [pi.version.isNotEmpty] -> check ready or not, avoid login without soft-keyboard
if (gFFI.chatModel.chatWindowOverlayEntry == null &&
gFFI.ffiModel.pi.version.isNotEmpty) {
gFFI.invokeMethod("enable_soft_keyboard", false);
}
}
});
} }
} // update for Scaffold
setState(() {});
void interval() {
try {
intervalUnsafe();
} catch (e) {}
} }
// handle mobile virtual keyboard // handle mobile virtual keyboard
@ -219,8 +198,9 @@ class _RemotePageState extends State<RemotePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final pi = Provider.of<FfiModel>(context).pi; final pi = Provider.of<FfiModel>(context).pi;
final hideKeyboard = isKeyboardShown() && _showEdit; final keyboardIsVisible =
final showActionButton = !_showBar || hideKeyboard; keyboardVisibilityController.isVisible && _showEdit;
final showActionButton = !_showBar || keyboardIsVisible || _showGestureHelp;
final keyboard = gFFI.ffiModel.permissions['keyboard'] != false; final keyboard = gFFI.ffiModel.permissions['keyboard'] != false;
return WillPopScope( return WillPopScope(
@ -230,33 +210,39 @@ class _RemotePageState extends State<RemotePage> {
}, },
child: getRawPointerAndKeyBody(Scaffold( child: getRawPointerAndKeyBody(Scaffold(
// workaround for https://github.com/rustdesk/rustdesk/issues/3131 // workaround for https://github.com/rustdesk/rustdesk/issues/3131
floatingActionButtonLocation: hideKeyboard floatingActionButtonLocation: keyboardIsVisible
? FABLocation(FloatingActionButtonLocation.endFloat, 0, -35) ? FABLocation(FloatingActionButtonLocation.endFloat, 0, -35)
: null, : null,
floatingActionButton: !showActionButton floatingActionButton: !showActionButton
? null ? null
: FloatingActionButton( : FloatingActionButton(
mini: !hideKeyboard, mini: !keyboardIsVisible,
child: Icon( child: Icon(
hideKeyboard ? Icons.expand_more : Icons.expand_less, (keyboardIsVisible || _showGestureHelp)
? Icons.expand_more
: Icons.expand_less,
color: Colors.white, color: Colors.white,
), ),
backgroundColor: MyTheme.accent, backgroundColor: MyTheme.accent,
onPressed: () { onPressed: () {
setState(() { setState(() {
if (hideKeyboard) { if (keyboardIsVisible) {
_showEdit = false; _showEdit = false;
gFFI.invokeMethod("enable_soft_keyboard", false); gFFI.invokeMethod("enable_soft_keyboard", false);
_mobileFocusNode.unfocus(); _mobileFocusNode.unfocus();
_physicalFocusNode.requestFocus(); _physicalFocusNode.requestFocus();
} else if (_showGestureHelp) {
_showGestureHelp = false;
} else { } else {
_showBar = !_showBar; _showBar = !_showBar;
} }
}); });
}), }),
bottomNavigationBar: _showBar && pi.displays.isNotEmpty bottomNavigationBar: _showGestureHelp
? getBottomAppBar(keyboard) ? getGestureHelp()
: null, : (_showBar && pi.displays.isNotEmpty
? getBottomAppBar(keyboard)
: null),
body: Overlay( body: Overlay(
initialEntries: [ initialEntries: [
OverlayEntry(builder: (context) { OverlayEntry(builder: (context) {
@ -346,7 +332,8 @@ class _RemotePageState extends State<RemotePage> {
icon: Icon(gFFI.ffiModel.touchMode icon: Icon(gFFI.ffiModel.touchMode
? Icons.touch_app ? Icons.touch_app
: Icons.mouse), : Icons.mouse),
onPressed: changeTouchMode, onPressed: () => setState(
() => _showGestureHelp = !_showGestureHelp),
), ),
]) + ]) +
(isWeb (isWeb
@ -498,6 +485,7 @@ class _RemotePageState extends State<RemotePage> {
} }
Widget getBodyForMobile() { Widget getBodyForMobile() {
final keyboardIsVisible = keyboardVisibilityController.isVisible;
return Container( return Container(
color: MyTheme.canvasColor, color: MyTheme.canvasColor,
child: Stack(children: () { child: Stack(children: () {
@ -508,7 +496,7 @@ class _RemotePageState extends State<RemotePage> {
right: 10, right: 10,
child: QualityMonitor(gFFI.qualityMonitorModel), child: QualityMonitor(gFFI.qualityMonitorModel),
), ),
getHelpTools(), KeyHelpTools(requestShow: (keyboardIsVisible || _showGestureHelp)),
SizedBox( SizedBox(
width: 0, width: 0,
height: 0, height: 0,
@ -678,29 +666,20 @@ class _RemotePageState extends State<RemotePage> {
}(); }();
} }
void changeTouchMode() { /// aka changeTouchMode
setState(() => _showEdit = false); BottomAppBar getGestureHelp() {
showModalBottomSheet( return BottomAppBar(
// backgroundColor: MyTheme.grayBg, child: SingleChildScrollView(
isScrollControlled: true, controller: ScrollController(),
context: context, padding: EdgeInsets.symmetric(vertical: 10),
shape: const RoundedRectangleBorder( child: GestureHelp(
borderRadius: BorderRadius.vertical(top: Radius.circular(5))), touchMode: gFFI.ffiModel.touchMode,
builder: (context) => DraggableScrollableSheet( onTouchModeChange: (t) {
expand: false, gFFI.ffiModel.toggleTouchMode();
builder: (context, scrollController) { final v = gFFI.ffiModel.touchMode ? 'Y' : '';
return SingleChildScrollView( bind.sessionPeerOption(
controller: ScrollController(), id: widget.id, name: "touch", value: v);
padding: EdgeInsets.symmetric(vertical: 10), })));
child: GestureHelp(
touchMode: gFFI.ffiModel.touchMode,
onTouchModeChange: (t) {
gFFI.ffiModel.toggleTouchMode();
final v = gFFI.ffiModel.touchMode ? 'Y' : '';
bind.sessionPeerOption(
id: widget.id, name: "touch", value: v);
}));
}));
} }
// * Currently mobile does not enable map mode // * Currently mobile does not enable map mode
@ -723,49 +702,74 @@ class _RemotePageState extends State<RemotePage> {
// ])); // ]));
// }, clickMaskDismiss: true); // }, clickMaskDismiss: true);
// } // }
}
Widget getHelpTools() { class KeyHelpTools extends StatefulWidget {
final keyboard = isKeyboardShown(); /// need to show by external request, etc [keyboardIsVisible] or [changeTouchMode]
if (!keyboard) { final bool requestShow;
return SizedBox();
KeyHelpTools({required this.requestShow});
@override
State<KeyHelpTools> createState() => _KeyHelpToolsState();
}
class _KeyHelpToolsState extends State<KeyHelpTools> {
var _more = true;
var _fn = false;
var _pin = false;
final _keyboardVisibilityController = KeyboardVisibilityController();
InputModel get inputModel => gFFI.inputModel;
Widget wrap(String text, void Function() onPressed,
{bool? active, IconData? icon}) {
return TextButton(
style: TextButton.styleFrom(
minimumSize: Size(0, 0),
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 9.75),
//adds padding inside the button
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
//limits the touch area to the button area
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
),
backgroundColor: active == true ? MyTheme.accent80 : null,
),
child: icon != null
? Icon(icon, size: 14, color: Colors.white)
: Text(translate(text),
style: TextStyle(color: Colors.white, fontSize: 11)),
onPressed: onPressed);
}
@override
Widget build(BuildContext context) {
final hasModifierOn = inputModel.ctrl ||
inputModel.alt ||
inputModel.shift ||
inputModel.command;
if (!_pin && !hasModifierOn && !widget.requestShow) {
return Offstage();
} }
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
wrap(String text, void Function() onPressed,
[bool? active, IconData? icon]) {
return TextButton(
style: TextButton.styleFrom(
minimumSize: Size(0, 0),
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 9.75),
//adds padding inside the button
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
//limits the touch area to the button area
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
),
backgroundColor: active == true ? MyTheme.accent80 : null,
),
child: icon != null
? Icon(icon, size: 17, color: Colors.white)
: Text(translate(text),
style: TextStyle(color: Colors.white, fontSize: 11)),
onPressed: onPressed);
}
final pi = gFFI.ffiModel.pi; final pi = gFFI.ffiModel.pi;
final isMac = pi.platform == kPeerPlatformMacOS; final isMac = pi.platform == kPeerPlatformMacOS;
final modifiers = <Widget>[ final modifiers = <Widget>[
wrap('Ctrl ', () { wrap('Ctrl ', () {
setState(() => inputModel.ctrl = !inputModel.ctrl); setState(() => inputModel.ctrl = !inputModel.ctrl);
}, inputModel.ctrl), }, active: inputModel.ctrl),
wrap(' Alt ', () { wrap(' Alt ', () {
setState(() => inputModel.alt = !inputModel.alt); setState(() => inputModel.alt = !inputModel.alt);
}, inputModel.alt), }, active: inputModel.alt),
wrap('Shift', () { wrap('Shift', () {
setState(() => inputModel.shift = !inputModel.shift); setState(() => inputModel.shift = !inputModel.shift);
}, inputModel.shift), }, active: inputModel.shift),
wrap(isMac ? ' Cmd ' : ' Win ', () { wrap(isMac ? ' Cmd ' : ' Win ', () {
setState(() => inputModel.command = !inputModel.command); setState(() => inputModel.command = !inputModel.command);
}, inputModel.command), }, active: inputModel.command),
]; ];
final keys = <Widget>[ final keys = <Widget>[
wrap( wrap(
@ -778,7 +782,14 @@ class _RemotePageState extends State<RemotePage> {
} }
}, },
), ),
_fn), active: _fn),
wrap(
'',
() => setState(
() => _pin = !_pin,
),
active: _pin,
icon: Icons.push_pin),
wrap( wrap(
' ... ', ' ... ',
() => setState( () => setState(
@ -789,7 +800,7 @@ class _RemotePageState extends State<RemotePage> {
} }
}, },
), ),
_more), active: _more),
]; ];
final fn = <Widget>[ final fn = <Widget>[
SizedBox(width: 9999), SizedBox(width: 9999),
@ -829,16 +840,16 @@ class _RemotePageState extends State<RemotePage> {
SizedBox(width: 9999), SizedBox(width: 9999),
wrap('', () { wrap('', () {
inputModel.inputKey('VK_LEFT'); inputModel.inputKey('VK_LEFT');
}, false, Icons.keyboard_arrow_left), }, icon: Icons.keyboard_arrow_left),
wrap('', () { wrap('', () {
inputModel.inputKey('VK_UP'); inputModel.inputKey('VK_UP');
}, false, Icons.keyboard_arrow_up), }, icon: Icons.keyboard_arrow_up),
wrap('', () { wrap('', () {
inputModel.inputKey('VK_DOWN'); inputModel.inputKey('VK_DOWN');
}, false, Icons.keyboard_arrow_down), }, icon: Icons.keyboard_arrow_down),
wrap('', () { wrap('', () {
inputModel.inputKey('VK_RIGHT'); inputModel.inputKey('VK_RIGHT');
}, false, Icons.keyboard_arrow_right), }, icon: Icons.keyboard_arrow_right),
wrap(isMac ? 'Cmd+C' : 'Ctrl+C', () { wrap(isMac ? 'Cmd+C' : 'Ctrl+C', () {
sendPrompt(isMac, 'VK_C'); sendPrompt(isMac, 'VK_C');
}), }),
@ -853,14 +864,15 @@ class _RemotePageState extends State<RemotePage> {
return Container( return Container(
color: Color(0xAA000000), color: Color(0xAA000000),
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: keyboard ? 24 : 4, left: 0, right: 0, bottom: 8), top: _keyboardVisibilityController.isVisible ? 24 : 4, bottom: 8),
child: Wrap( child: Wrap(
spacing: space, spacing: space,
runSpacing: space, runSpacing: space,
children: <Widget>[SizedBox(width: 9999)] + children: <Widget>[SizedBox(width: 9999)] +
(keyboard modifiers +
? modifiers + keys + (_fn ? fn : []) + (_more ? more : []) keys +
: modifiers), (_fn ? fn : []) +
(_more ? more : []),
)); ));
} }
} }

View File

@ -488,6 +488,54 @@ packages:
url: "https://github.com/Kingtous/flutter_improved_scrolling" url: "https://github.com/Kingtous/flutter_improved_scrolling"
source: git source: git
version: "0.0.3" version: "0.0.3"
flutter_keyboard_visibility:
dependency: "direct main"
description:
name: flutter_keyboard_visibility
sha256: "86b71bbaffa38e885f5c21b1182408b9be6951fd125432cf6652c636254cef2d"
url: "https://pub.dev"
source: hosted
version: "5.4.0"
flutter_keyboard_visibility_linux:
dependency: transitive
description:
name: flutter_keyboard_visibility_linux
sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_keyboard_visibility_macos:
dependency: transitive
description:
name: flutter_keyboard_visibility_macos
sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_keyboard_visibility_platform_interface:
dependency: transitive
description:
name: flutter_keyboard_visibility_platform_interface
sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4
url: "https://pub.dev"
source: hosted
version: "2.0.0"
flutter_keyboard_visibility_web:
dependency: transitive
description:
name: flutter_keyboard_visibility_web
sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1
url: "https://pub.dev"
source: hosted
version: "2.0.0"
flutter_keyboard_visibility_windows:
dependency: transitive
description:
name: flutter_keyboard_visibility_windows
sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -91,6 +91,7 @@ dependencies:
win32: any win32: any
password_strength: ^0.2.0 password_strength: ^0.2.0
flutter_launcher_icons: ^0.11.0 flutter_launcher_icons: ^0.11.0
flutter_keyboard_visibility: ^5.4.0
dev_dependencies: dev_dependencies: