refact: mobile actions (#8236)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou 2024-06-01 20:23:58 +08:00 committed by GitHub
parent df36580451
commit ed5487a1fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 109 additions and 59 deletions

View File

@ -18,6 +18,7 @@ import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_hbb/utils/platform_channel.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:uni_links/uni_links.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart';
@ -858,30 +859,10 @@ class OverlayDialogManager {
final overlayState = _overlayKeyState.state;
if (overlayState == null) return;
// compute overlay position
final screenW = MediaQuery.of(globalKey.currentContext!).size.width;
final screenH = MediaQuery.of(globalKey.currentContext!).size.height;
const double overlayW = 200;
const double overlayH = 45;
final left = (screenW - overlayW) / 2;
final top = screenH - overlayH - 80;
final overlay = OverlayEntry(builder: (context) {
final session = ffi ?? gFFI;
return DraggableMobileActions(
position: Offset(left, top),
width: overlayW,
height: overlayH,
onBackPressed: () => session.inputModel.tap(MouseButtons.right),
onHomePressed: () => session.inputModel.tap(MouseButtons.wheel),
onRecentPressed: () async {
session.inputModel.sendMouse('down', MouseButtons.wheel);
await Future.delayed(const Duration(milliseconds: 500));
session.inputModel.sendMouse('up', MouseButtons.wheel);
},
onHidePressed: () => hideMobileActionsOverlay(),
);
});
final overlay = makeMobileActionsOverlayEntry(
() => hideMobileActionsOverlay(),
ffi: ffi,
);
overlayState.insert(overlay);
_mobileActionsOverlayEntry = overlay;
mobileActionsOverlayVisible.value = true;
@ -909,6 +890,45 @@ 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);
return DraggableMobileActions(
scale: scale,
position: position,
width: overlayW,
height: overlayH,
onBackPressed: () => session.inputModel.tap(MouseButtons.right),
onHomePressed: () => session.inputModel.tap(MouseButtons.wheel),
onRecentPressed: () async {
session.inputModel.sendMouse('down', MouseButtons.wheel);
await Future.delayed(const Duration(milliseconds: 500));
session.inputModel.sendMouse('up', MouseButtons.wheel);
},
onHidePressed: onHide,
);
}
return OverlayEntry(builder: (context) {
if (isDesktop) {
final c = Provider.of<CanvasModel>(context);
return makeMobileActions(context, c.scale * 2.0);
} else {
return makeMobileActions(globalKey.currentContext!, 1.0);
}
});
}
void showToast(String text, {Duration timeout = const Duration(seconds: 3)}) {
final overlayState = globalKey.currentState?.overlay;
if (overlayState == null) return;
@ -3255,7 +3275,8 @@ Widget buildPresetPasswordWarning() {
translate("Security Alert"),
style: TextStyle(
color: Colors.red,
fontSize: 18, // https://github.com/rustdesk/rustdesk-server-pro/issues/261
fontSize:
18, // https://github.com/rustdesk/rustdesk-server-pro/issues/261
fontWeight: FontWeight.bold,
),
)).paddingOnly(bottom: 8),

View File

@ -45,7 +45,7 @@ class DraggableChatWindow extends StatelessWidget {
)
: Draggable(
checkKeyboard: true,
position: position,
position: SimpleWrapper(position),
width: width,
height: height,
chatModel: chatModel,
@ -166,15 +166,17 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
/// floating buttons of back/home/recent actions for android
class DraggableMobileActions extends StatelessWidget {
DraggableMobileActions(
{this.position = Offset.zero,
this.onBackPressed,
{this.onBackPressed,
this.onRecentPressed,
this.onHomePressed,
this.onHidePressed,
required this.position,
required this.width,
required this.height});
required this.height,
required this.scale});
final Offset position;
final double scale;
final SimpleWrapper<Offset> position;
final double width;
final double height;
final VoidCallback? onBackPressed;
@ -186,8 +188,8 @@ class DraggableMobileActions extends StatelessWidget {
Widget build(BuildContext context) {
return Draggable(
position: position,
width: width,
height: height,
width: scale * width,
height: scale * height,
builder: (_, onPanUpdate) {
return GestureDetector(
onPanUpdate: onPanUpdate,
@ -197,7 +199,8 @@ class DraggableMobileActions extends StatelessWidget {
child: Container(
decoration: BoxDecoration(
color: MyTheme.accent.withOpacity(0.4),
borderRadius: BorderRadius.all(Radius.circular(15))),
borderRadius:
BorderRadius.all(Radius.circular(15 * scale))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
@ -205,17 +208,20 @@ class DraggableMobileActions extends StatelessWidget {
color: Colors.white,
onPressed: onBackPressed,
splashRadius: kDesktopIconButtonSplashRadius,
icon: const Icon(Icons.arrow_back)),
icon: const Icon(Icons.arrow_back),
iconSize: 24 * scale),
IconButton(
color: Colors.white,
onPressed: onHomePressed,
splashRadius: kDesktopIconButtonSplashRadius,
icon: const Icon(Icons.home)),
icon: const Icon(Icons.home),
iconSize: 24 * scale),
IconButton(
color: Colors.white,
onPressed: onRecentPressed,
splashRadius: kDesktopIconButtonSplashRadius,
icon: const Icon(Icons.more_horiz)),
icon: const Icon(Icons.more_horiz),
iconSize: 24 * scale),
const VerticalDivider(
width: 0,
thickness: 2,
@ -226,7 +232,8 @@ class DraggableMobileActions extends StatelessWidget {
color: Colors.white,
onPressed: onHidePressed,
splashRadius: kDesktopIconButtonSplashRadius,
icon: const Icon(Icons.keyboard_arrow_down)),
icon: const Icon(Icons.keyboard_arrow_down),
iconSize: 24 * scale),
],
),
)));
@ -235,11 +242,11 @@ class DraggableMobileActions extends StatelessWidget {
}
class Draggable extends StatefulWidget {
const Draggable(
Draggable(
{Key? key,
this.checkKeyboard = false,
this.checkScreenSize = false,
this.position = Offset.zero,
required this.position,
required this.width,
required this.height,
this.chatModel,
@ -248,7 +255,7 @@ class Draggable extends StatefulWidget {
final bool checkKeyboard;
final bool checkScreenSize;
final Offset position;
final SimpleWrapper<Offset> position;
final double width;
final double height;
final ChatModel? chatModel;
@ -259,7 +266,6 @@ class Draggable extends StatefulWidget {
}
class _DraggableState extends State<Draggable> {
late Offset _position;
late ChatModel? _chatModel;
bool _keyboardVisible = false;
double _saveHeight = 0;
@ -268,35 +274,36 @@ class _DraggableState extends State<Draggable> {
@override
void initState() {
super.initState();
_position = widget.position;
_chatModel = widget.chatModel;
}
get position => widget.position.value;
void onPanUpdate(DragUpdateDetails d) {
final offset = d.delta;
final size = MediaQuery.of(context).size;
double x = 0;
double y = 0;
if (_position.dx + offset.dx + widget.width > size.width) {
if (position.dx + offset.dx + widget.width > size.width) {
x = size.width - widget.width;
} else if (_position.dx + offset.dx < 0) {
} else if (position.dx + offset.dx < 0) {
x = 0;
} else {
x = _position.dx + offset.dx;
x = position.dx + offset.dx;
}
if (_position.dy + offset.dy + widget.height > size.height) {
if (position.dy + offset.dy + widget.height > size.height) {
y = size.height - widget.height;
} else if (_position.dy + offset.dy < 0) {
} else if (position.dy + offset.dy < 0) {
y = 0;
} else {
y = _position.dy + offset.dy;
y = position.dy + offset.dy;
}
setState(() {
_position = Offset(x, y);
widget.position.value = Offset(x, y);
});
_chatModel?.setChatWindowPosition(_position);
_chatModel?.setChatWindowPosition(position);
}
checkScreenSize() {}
@ -307,13 +314,13 @@ class _DraggableState extends State<Draggable> {
// save
if (!_keyboardVisible && currentVisible) {
_saveHeight = _position.dy;
_saveHeight = position.dy;
}
// reset
if (_lastBottomHeight > 0 && bottomHeight == 0) {
setState(() {
_position = Offset(_position.dx, _saveHeight);
widget.position.value = Offset(position.dx, _saveHeight);
});
}
@ -321,10 +328,10 @@ class _DraggableState extends State<Draggable> {
if (_keyboardVisible && currentVisible) {
final sumHeight = bottomHeight + widget.height;
final contextHeight = MediaQuery.of(context).size.height;
if (sumHeight + _position.dy > contextHeight) {
if (sumHeight + position.dy > contextHeight) {
final y = contextHeight - sumHeight;
setState(() {
_position = Offset(_position.dx, y);
widget.position.value = Offset(position.dx, y);
});
}
}
@ -343,8 +350,8 @@ class _DraggableState extends State<Draggable> {
}
return Stack(children: [
Positioned(
top: _position.dy,
left: _position.dx,
top: position.dy,
left: position.dx,
width: widget.width,
height: widget.height,
child: widget.builder(context, onPanUpdate))

View File

@ -307,8 +307,30 @@ class _RemotePageState extends State<RemotePage>
_ffi.ffiModel.waitForFirstImage.isTrue
? emptyOverlay()
: () {
_ffi.ffiModel.tryShowAndroidActionsOverlay();
return Offstage();
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,
child: Overlay(initialEntries: [
makeMobileActionsOverlayEntry(
() => _ffi
.dialogManager
.mobileActionsOverlayVisible
.value = false,
ffi: _ffi,
)
]),
));
}
}(),
// Use Overlay to enable rebuild every time on menu button click.
_ffi.ffiModel.pi.isSet.isTrue

View File

@ -588,7 +588,7 @@ class _MobileActionMenu extends StatelessWidget {
assetName: 'assets/actions_mobile.svg',
tooltip: 'Mobile Actions',
onPressed: () =>
ffi.dialogManager.toggleMobileActionsOverlay(ffi: ffi),
ffi.dialogManager.mobileActionsOverlayVisible.toggle(),
color: ffi.dialogManager.mobileActionsOverlayVisible.isTrue
? _ToolbarTheme.blueColor
: _ToolbarTheme.inactiveColor,