fix: mobile actions, position (#8446)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
parent
a9e0ea8520
commit
40cb59336f
@ -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),
|
||||
|
@ -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(
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user