fix: mobile actions, position (#8446)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou 2024-06-23 11:06:47 +08:00 committed by GitHub
parent a9e0ea8520
commit 40cb59336f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 164 additions and 48 deletions

View File

@ -718,7 +718,21 @@ class OverlayDialogManager {
int _tagCount = 0;
OverlayEntry? _mobileActionsOverlayEntry;
RxBool mobileActionsOverlayVisible = false.obs;
RxBool mobileActionsOverlayVisible = true.obs;
setMobileActionsOverlayVisible(bool v, {store = true}) {
if (store) {
bind.setLocalFlutterOption(k: kOptionShowMobileAction, v: v ? 'Y' : 'N');
}
// No need to read the value from local storage after setting it.
// It better to toggle the value directly.
mobileActionsOverlayVisible.value = v;
}
loadMobileActionsOverlayVisible() {
mobileActionsOverlayVisible.value =
bind.getLocalFlutterOption(k: kOptionShowMobileAction) != 'N';
}
void setOverlayState(OverlayKeyState overlayKeyState) {
_overlayKeyState = overlayKeyState;
@ -865,14 +879,14 @@ class OverlayDialogManager {
);
overlayState.insert(overlay);
_mobileActionsOverlayEntry = overlay;
mobileActionsOverlayVisible.value = true;
setMobileActionsOverlayVisible(true);
}
void hideMobileActionsOverlay() {
void hideMobileActionsOverlay({store = true}) {
if (_mobileActionsOverlayEntry != null) {
_mobileActionsOverlayEntry!.remove();
_mobileActionsOverlayEntry = null;
mobileActionsOverlayVisible.value = false;
setMobileActionsOverlayVisible(false, store: store);
return;
}
}
@ -891,21 +905,27 @@ class OverlayDialogManager {
}
makeMobileActionsOverlayEntry(VoidCallback? onHide, {FFI? ffi}) {
final position = SimpleWrapper(Offset(0, 0));
makeMobileActions(BuildContext context, double s) {
final scale = s < 0.85 ? 0.85 : s;
final session = ffi ?? gFFI;
// compute overlay position
final screenW = MediaQuery.of(context).size.width;
final screenH = MediaQuery.of(context).size.height;
const double overlayW = 200;
const double overlayH = 45;
final left = (screenW - overlayW * scale) / 2;
final top = screenH - (overlayH + 80) * scale;
position.value = Offset(left, top);
computeOverlayPosition() {
final screenW = MediaQuery.of(context).size.width;
final screenH = MediaQuery.of(context).size.height;
final left = (screenW - overlayW * scale) / 2;
final top = screenH - (overlayH + 80) * scale;
return Offset(left, top);
}
if (draggablePositions.mobileActions.isInvalid()) {
draggablePositions.mobileActions.update(computeOverlayPosition());
} else {
draggablePositions.mobileActions.tryAdjust(overlayW, overlayH, scale);
}
return DraggableMobileActions(
scale: scale,
position: position,
position: draggablePositions.mobileActions,
width: overlayW,
height: overlayH,
onBackPressed: () => session.inputModel.tap(MouseButtons.right),

View File

@ -1,6 +1,8 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:debounce_throttle/debounce_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
@ -26,9 +28,12 @@ class DraggableChatWindow extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (draggablePositions.chatWindow.isInvalid()) {
draggablePositions.chatWindow.update(position);
}
return isIOS
? IOSDraggable(
position: position,
position: draggablePositions.chatWindow,
chatModel: chatModel,
width: width,
height: height,
@ -45,7 +50,7 @@ class DraggableChatWindow extends StatelessWidget {
)
: Draggable(
checkKeyboard: true,
position: SimpleWrapper(position),
position: draggablePositions.chatWindow,
width: width,
height: height,
chatModel: chatModel,
@ -176,7 +181,7 @@ class DraggableMobileActions extends StatelessWidget {
required this.scale});
final double scale;
final SimpleWrapper<Offset> position;
final DraggableKeyPosition position;
final double width;
final double height;
final VoidCallback? onBackPressed;
@ -241,6 +246,92 @@ class DraggableMobileActions extends StatelessWidget {
}
}
class DraggableKeyPosition {
final String key;
Offset _pos;
late Debouncer<int> _debouncerStore;
DraggableKeyPosition(this.key)
: _pos = DraggablePositions.kInvalidDraggablePosition;
get pos => _pos;
_loadPosition(String k) {
final value = bind.getLocalFlutterOption(k: k);
if (value.isNotEmpty) {
final parts = value.split(',');
if (parts.length == 2) {
return Offset(double.parse(parts[0]), double.parse(parts[1]));
}
}
return DraggablePositions.kInvalidDraggablePosition;
}
load() {
_pos = _loadPosition(key);
_debouncerStore = Debouncer<int>(const Duration(milliseconds: 500),
onChanged: (v) => _store(), initialValue: 0);
}
update(Offset pos) {
_pos = pos;
_triggerStore();
}
// Adjust position to keep it in the screen
// Only used for desktop and web desktop
tryAdjust(double w, double h, double scale) {
final size = MediaQuery.of(Get.context!).size;
w = w * scale;
h = h * scale;
double x = _pos.dx;
double y = _pos.dy;
if (x + w > size.width) {
x = size.width - w;
}
final tabBarHeight = isDesktop ? kDesktopRemoteTabBarHeight : 0;
if (y + h > (size.height - tabBarHeight)) {
y = size.height - tabBarHeight - h;
}
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
if (x != _pos.dx || y != _pos.dy) {
update(Offset(x, y));
}
}
isInvalid() {
return _pos == DraggablePositions.kInvalidDraggablePosition;
}
_triggerStore() => _debouncerStore.value = _debouncerStore.value + 1;
_store() {
bind.setLocalFlutterOption(k: key, v: '${_pos.dx},${_pos.dy}');
}
}
class DraggablePositions {
static const kChatWindow = 'draggablePositionChat';
static const kMobileActions = 'draggablePositionMobile';
static const kIOSDraggable = 'draggablePositionIOS';
static const kInvalidDraggablePosition = Offset(-999999, -999999);
final chatWindow = DraggableKeyPosition(kChatWindow);
final mobileActions = DraggableKeyPosition(kMobileActions);
final iOSDraggable = DraggableKeyPosition(kIOSDraggable);
load() {
chatWindow.load();
mobileActions.load();
iOSDraggable.load();
}
}
DraggablePositions draggablePositions = DraggablePositions();
class Draggable extends StatefulWidget {
Draggable(
{Key? key,
@ -255,7 +346,7 @@ class Draggable extends StatefulWidget {
final bool checkKeyboard;
final bool checkScreenSize;
final SimpleWrapper<Offset> position;
final DraggableKeyPosition position;
final double width;
final double height;
final ChatModel? chatModel;
@ -277,7 +368,7 @@ class _DraggableState extends State<Draggable> {
_chatModel = widget.chatModel;
}
get position => widget.position.value;
get position => widget.position.pos;
void onPanUpdate(DragUpdateDetails d) {
final offset = d.delta;
@ -301,7 +392,7 @@ class _DraggableState extends State<Draggable> {
y = position.dy + offset.dy;
}
setState(() {
widget.position.value = Offset(x, y);
widget.position.update(Offset(x, y));
});
_chatModel?.setChatWindowPosition(position);
}
@ -320,7 +411,7 @@ class _DraggableState extends State<Draggable> {
// reset
if (_lastBottomHeight > 0 && bottomHeight == 0) {
setState(() {
widget.position.value = Offset(position.dx, _saveHeight);
widget.position.update(Offset(position.dx, _saveHeight));
});
}
@ -331,7 +422,7 @@ class _DraggableState extends State<Draggable> {
if (sumHeight + position.dy > contextHeight) {
final y = contextHeight - sumHeight;
setState(() {
widget.position.value = Offset(position.dx, y);
widget.position.update(Offset(position.dx, y));
});
}
}
@ -362,14 +453,14 @@ class _DraggableState extends State<Draggable> {
class IOSDraggable extends StatefulWidget {
const IOSDraggable(
{Key? key,
this.position = Offset.zero,
this.chatModel,
required this.position,
required this.width,
required this.height,
required this.builder})
: super(key: key);
final Offset position;
final DraggableKeyPosition position;
final ChatModel? chatModel;
final double width;
final double height;
@ -380,7 +471,6 @@ class IOSDraggable extends StatefulWidget {
}
class IOSDraggableState extends State<IOSDraggable> {
late Offset _position;
late ChatModel? _chatModel;
late double _width;
late double _height;
@ -391,25 +481,26 @@ class IOSDraggableState extends State<IOSDraggable> {
@override
void initState() {
super.initState();
_position = widget.position;
_chatModel = widget.chatModel;
_width = widget.width;
_height = widget.height;
}
get position => widget.position;
checkKeyboard() {
final bottomHeight = MediaQuery.of(context).viewInsets.bottom;
final currentVisible = bottomHeight != 0;
// save
if (!_keyboardVisible && currentVisible) {
_saveHeight = _position.dy;
_saveHeight = position.value.dy;
}
// reset
if (_lastBottomHeight > 0 && bottomHeight == 0) {
setState(() {
_position = Offset(_position.dx, _saveHeight);
position.value = Offset(position.value.dx, _saveHeight);
});
}
@ -417,10 +508,10 @@ class IOSDraggableState extends State<IOSDraggable> {
if (_keyboardVisible && currentVisible) {
final sumHeight = bottomHeight + _height;
final contextHeight = MediaQuery.of(context).size.height;
if (sumHeight + _position.dy > contextHeight) {
if (sumHeight + position.value.dy > contextHeight) {
final y = contextHeight - sumHeight;
setState(() {
_position = Offset(_position.dx, y);
position.value = Offset(position.value.dx, y);
});
}
}
@ -435,14 +526,14 @@ class IOSDraggableState extends State<IOSDraggable> {
return Stack(
children: [
Positioned(
left: _position.dx,
top: _position.dy,
left: position.value.dx,
top: position.value.dy,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
_position += details.delta;
position.value += details.delta;
});
_chatModel?.setChatWindowPosition(_position);
_chatModel?.setChatWindowPosition(position.value);
},
child: Material(
child: Container(

View File

@ -142,6 +142,8 @@ const String kOptionDisableFloatingWindow = "disable-floating-window";
const String kOptionKeepScreenOn = "keep-screen-on";
const String kOptionShowMobileAction = "showMobileActions";
const String kUrlActionClose = "close";
const String kTabLabelHomePage = "Home";

View File

@ -134,6 +134,7 @@ class _RemotePageState extends State<RemotePage>
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
_ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId);
_ffi.dialogManager.loadMobileActionsOverlayVisible();
// Session option should be set after models.dart/FFI.start
_showRemoteCursor.value = bind.sessionGetToggleOptionSync(
sessionId: sessionId, arg: 'show-remote-cursor');
@ -322,13 +323,6 @@ class _RemotePageState extends State<RemotePage>
if (!_ffi.ffiModel.isPeerAndroid) {
return Offstage();
} else {
if (_ffi.connType == ConnType.defaultConn &&
_ffi.ffiModel.permissions['keyboard'] != false) {
Timer(
Duration(milliseconds: 10),
() => _ffi.dialogManager
.mobileActionsOverlayVisible.value = true);
}
return Obx(() => Offstage(
offstage: _ffi.dialogManager
.mobileActionsOverlayVisible.isFalse,

View File

@ -579,8 +579,8 @@ class _MobileActionMenu extends StatelessWidget {
return Obx(() => _IconMenuButton(
assetName: 'assets/actions_mobile.svg',
tooltip: 'Mobile Actions',
onPressed: () =>
ffi.dialogManager.mobileActionsOverlayVisible.toggle(),
onPressed: () => ffi.dialogManager.setMobileActionsOverlayVisible(
!ffi.dialogManager.mobileActionsOverlayVisible.value),
color: ffi.dialogManager.mobileActionsOverlayVisible.isTrue
? _ToolbarTheme.blueColor
: _ToolbarTheme.inactiveColor,

View File

@ -6,6 +6,7 @@ import 'package:bot_toast/bot_toast.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common/widgets/overlay.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/desktop/pages/install_page.dart';
import 'package:flutter_hbb/desktop/pages/server_page.dart';
@ -156,6 +157,7 @@ void runMobileApp() async {
await initEnv(kAppTypeMain);
if (isAndroid) androidChannelInit();
if (isAndroid) platformFFI.syncAndroidServiceAppDirConfigPath();
draggablePositions.load();
await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]);
gFFI.userModel.refreshCurrentUser();
runApp(App());
@ -176,6 +178,7 @@ void runMultiWindow(
late Widget widget;
switch (appType) {
case kAppTypeDesktopRemote:
draggablePositions.load();
widget = DesktopRemoteScreen(
params: argument,
);

View File

@ -82,13 +82,14 @@ class _RemotePageState extends State<RemotePage> {
.changeCurrentKey(MessageKey(widget.id, ChatModel.clientModeID));
gFFI.chatModel.voiceCallStatus.value = VoiceCallStatus.notStarted;
_blockableOverlayState.applyFfi(gFFI);
gFFI.dialogManager.loadMobileActionsOverlayVisible();
}
@override
Future<void> dispose() async {
// https://github.com/flutter/flutter/issues/64935
super.dispose();
gFFI.dialogManager.hideMobileActionsOverlay();
gFFI.dialogManager.hideMobileActionsOverlay(store: false);
gFFI.inputModel.listenToMouse(false);
gFFI.imageModel.disposeImage();
gFFI.cursorModel.disposeImages();

View File

@ -917,10 +917,12 @@ class FfiModel with ChangeNotifier {
if (parent.target?.connType == ConnType.defaultConn &&
parent.target != null &&
parent.target!.ffiModel.permissions['keyboard'] != false) {
Timer(
Duration(milliseconds: delayMSecs),
() => parent.target!.dialogManager
.showMobileActionsOverlay(ffi: parent.target!));
Timer(Duration(milliseconds: delayMSecs), () {
if (parent.target!.dialogManager.mobileActionsOverlayVisible.isTrue) {
parent.target!.dialogManager
.showMobileActionsOverlay(ffi: parent.target!);
}
});
}
}
}
@ -1587,10 +1589,13 @@ class CanvasModel with ChangeNotifier {
// (focalPoint.dx - _x_1) / s1 + displayOriginX = (focalPoint.dx - _x_2) / s2 + displayOriginX
// _x_2 = focalPoint.dx - (focalPoint.dx - _x_1) / s1 * s2
_x = focalPoint.dx - (focalPoint.dx - _x) / s * _scale;
final adjustForKeyboard = parent.target?.cursorModel.adjustForKeyboard() ?? 0.0;
final adjustForKeyboard =
parent.target?.cursorModel.adjustForKeyboard() ?? 0.0;
// (focalPoint.dy - _y_1 + adjust) / s1 + displayOriginY = (focalPoint.dy - _y_2 + adjust) / s2 + displayOriginY
// _y_2 = focalPoint.dy + adjust - (focalPoint.dy - _y_1 + adjust) / s1 * s2
_y = focalPoint.dy + adjustForKeyboard - (focalPoint.dy - _y + adjustForKeyboard) / s * _scale;
_y = focalPoint.dy +
adjustForKeyboard -
(focalPoint.dy - _y + adjustForKeyboard) / s * _scale;
notifyListeners();
}