mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-29 16:49:10 +08:00
4a89469b84
# Conflicts: # Cargo.lock # Cargo.toml # build.rs # flutter/.gitignore # flutter/lib/common.dart # flutter/lib/mobile/pages/remote_page.dart # flutter/lib/models/model.dart # flutter/lib/models/native_model.dart # flutter/lib/models/server_model.dart # flutter/pubspec.lock # flutter/pubspec.yaml # src/client.rs # src/client/file_trait.rs # src/flutter.rs # src/mobile_ffi.rs # src/ui.rs
387 lines
11 KiB
Dart
387 lines
11 KiB
Dart
import 'package:draggable_float_widget/draggable_float_widget.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hbb/common.dart';
|
|
|
|
import '../../models/model.dart';
|
|
import '../pages/chat_page.dart';
|
|
|
|
OverlayEntry? chatIconOverlayEntry;
|
|
OverlayEntry? chatWindowOverlayEntry;
|
|
|
|
OverlayEntry? mobileActionsOverlayEntry;
|
|
|
|
class DraggableChatWindow extends StatelessWidget {
|
|
DraggableChatWindow(
|
|
{this.position = Offset.zero, required this.width, required this.height});
|
|
|
|
final Offset position;
|
|
final double width;
|
|
final double height;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Draggable(
|
|
checkKeyboard: true,
|
|
position: position,
|
|
width: width,
|
|
height: height,
|
|
builder: (_, onPanUpdate) {
|
|
return isIOS
|
|
? chatPage
|
|
: Scaffold(
|
|
resizeToAvoidBottomInset: false,
|
|
appBar: CustomAppBar(
|
|
onPanUpdate: onPanUpdate,
|
|
appBar: Container(
|
|
color: MyTheme.accent50,
|
|
height: 50,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 15),
|
|
child: Text(
|
|
translate("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,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|
final GestureDragUpdateCallback onPanUpdate;
|
|
final Widget appBar;
|
|
|
|
const CustomAppBar(
|
|
{Key? key, required this.onPanUpdate, required this.appBar})
|
|
: super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(onPanUpdate: onPanUpdate, child: appBar);
|
|
}
|
|
|
|
@override
|
|
Size get preferredSize => new Size.fromHeight(kToolbarHeight);
|
|
}
|
|
|
|
showChatIconOverlay({Offset offset = const Offset(200, 50)}) {
|
|
if (chatIconOverlayEntry != null) {
|
|
chatIconOverlayEntry!.remove();
|
|
}
|
|
if (globalKey.currentState == null || globalKey.currentState!.overlay == null)
|
|
return;
|
|
final bar = navigationBarKey.currentWidget;
|
|
if (bar != null) {
|
|
if ((bar as BottomNavigationBar).currentIndex == 1) {
|
|
return;
|
|
}
|
|
}
|
|
final globalOverlayState = globalKey.currentState!.overlay!;
|
|
|
|
final overlay = OverlayEntry(builder: (context) {
|
|
return DraggableFloatWidget(
|
|
config: DraggableFloatWidgetBaseConfig(
|
|
initPositionYInTop: false,
|
|
initPositionYMarginBorder: 100,
|
|
borderTopContainTopBar: true,
|
|
),
|
|
child: FloatingActionButton(
|
|
onPressed: () {
|
|
if (chatWindowOverlayEntry == null) {
|
|
showChatWindowOverlay();
|
|
} else {
|
|
hideChatWindowOverlay();
|
|
}
|
|
},
|
|
child: Icon(Icons.message)));
|
|
});
|
|
globalOverlayState.insert(overlay);
|
|
chatIconOverlayEntry = overlay;
|
|
}
|
|
|
|
hideChatIconOverlay() {
|
|
if (chatIconOverlayEntry != null) {
|
|
chatIconOverlayEntry!.remove();
|
|
chatIconOverlayEntry = null;
|
|
}
|
|
}
|
|
|
|
showChatWindowOverlay() {
|
|
if (chatWindowOverlayEntry != null) return;
|
|
if (globalKey.currentState == null || globalKey.currentState!.overlay == null)
|
|
return;
|
|
final globalOverlayState = globalKey.currentState!.overlay!;
|
|
|
|
final overlay = OverlayEntry(builder: (context) {
|
|
return DraggableChatWindow(
|
|
position: Offset(20, 80), width: 250, height: 350);
|
|
});
|
|
globalOverlayState.insert(overlay);
|
|
chatWindowOverlayEntry = overlay;
|
|
}
|
|
|
|
hideChatWindowOverlay() {
|
|
if (chatWindowOverlayEntry != null) {
|
|
chatWindowOverlayEntry!.remove();
|
|
chatWindowOverlayEntry = null;
|
|
return;
|
|
}
|
|
}
|
|
|
|
toggleChatOverlay() {
|
|
if (chatIconOverlayEntry == null || chatWindowOverlayEntry == null) {
|
|
gFFI.invokeMethod("enable_soft_keyboard", true);
|
|
showChatIconOverlay();
|
|
showChatWindowOverlay();
|
|
} else {
|
|
hideChatIconOverlay();
|
|
hideChatWindowOverlay();
|
|
}
|
|
}
|
|
|
|
/// floating buttons of back/home/recent actions for android
|
|
class DraggableMobileActions extends StatelessWidget {
|
|
DraggableMobileActions(
|
|
{this.position = Offset.zero,
|
|
this.onBackPressed,
|
|
this.onRecentPressed,
|
|
this.onHomePressed,
|
|
required this.width,
|
|
required this.height});
|
|
|
|
final Offset position;
|
|
final double width;
|
|
final double height;
|
|
final VoidCallback? onBackPressed;
|
|
final VoidCallback? onHomePressed;
|
|
final VoidCallback? onRecentPressed;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Draggable(
|
|
position: position,
|
|
width: width,
|
|
height: height,
|
|
builder: (_, onPanUpdate) {
|
|
return GestureDetector(
|
|
onPanUpdate: onPanUpdate,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: MyTheme.accent.withOpacity(0.4),
|
|
borderRadius: BorderRadius.all(Radius.circular(15))),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
IconButton(
|
|
color: MyTheme.white,
|
|
onPressed: onBackPressed,
|
|
icon: Icon(Icons.arrow_back)),
|
|
IconButton(
|
|
color: MyTheme.white,
|
|
onPressed: onHomePressed,
|
|
icon: Icon(Icons.home)),
|
|
IconButton(
|
|
color: MyTheme.white,
|
|
onPressed: onRecentPressed,
|
|
icon: Icon(Icons.more_horiz)),
|
|
VerticalDivider(
|
|
width: 0,
|
|
thickness: 2,
|
|
indent: 10,
|
|
endIndent: 10,
|
|
),
|
|
IconButton(
|
|
color: MyTheme.white,
|
|
onPressed: hideMobileActionsOverlay,
|
|
icon: Icon(Icons.keyboard_arrow_down)),
|
|
],
|
|
),
|
|
));
|
|
});
|
|
}
|
|
}
|
|
|
|
resetMobileActionsOverlay() {
|
|
if (mobileActionsOverlayEntry == null) return;
|
|
hideMobileActionsOverlay();
|
|
showMobileActionsOverlay();
|
|
}
|
|
|
|
showMobileActionsOverlay() {
|
|
if (mobileActionsOverlayEntry != null) return;
|
|
if (globalKey.currentContext == null ||
|
|
globalKey.currentState == null ||
|
|
globalKey.currentState!.overlay == null) return;
|
|
final globalOverlayState = globalKey.currentState!.overlay!;
|
|
|
|
// compute overlay position
|
|
final screenW = MediaQuery.of(globalKey.currentContext!).size.width;
|
|
final screenH = MediaQuery.of(globalKey.currentContext!).size.height;
|
|
final double overlayW = 200;
|
|
final double overlayH = 45;
|
|
final left = (screenW - overlayW) / 2;
|
|
final top = screenH - overlayH - 80;
|
|
|
|
final overlay = OverlayEntry(builder: (context) {
|
|
return DraggableMobileActions(
|
|
position: Offset(left, top),
|
|
width: overlayW,
|
|
height: overlayH,
|
|
onBackPressed: () => gFFI.tap(MouseButtons.right),
|
|
onHomePressed: () => gFFI.tap(MouseButtons.wheel),
|
|
onRecentPressed: () async {
|
|
gFFI.sendMouse('down', MouseButtons.wheel);
|
|
await Future.delayed(Duration(milliseconds: 500));
|
|
gFFI.sendMouse('up', MouseButtons.wheel);
|
|
},
|
|
);
|
|
});
|
|
globalOverlayState.insert(overlay);
|
|
mobileActionsOverlayEntry = overlay;
|
|
}
|
|
|
|
hideMobileActionsOverlay() {
|
|
if (mobileActionsOverlayEntry != null) {
|
|
mobileActionsOverlayEntry!.remove();
|
|
mobileActionsOverlayEntry = null;
|
|
return;
|
|
}
|
|
}
|
|
|
|
class Draggable extends StatefulWidget {
|
|
Draggable(
|
|
{this.checkKeyboard = false,
|
|
this.checkScreenSize = false,
|
|
this.position = Offset.zero,
|
|
required this.width,
|
|
required this.height,
|
|
required this.builder});
|
|
|
|
final bool checkKeyboard;
|
|
final bool checkScreenSize;
|
|
final Offset position;
|
|
final double width;
|
|
final double height;
|
|
final Widget Function(BuildContext, GestureDragUpdateCallback) builder;
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _DraggableState();
|
|
}
|
|
|
|
class _DraggableState extends State<Draggable> {
|
|
late Offset _position;
|
|
bool _keyboardVisible = false;
|
|
double _saveHeight = 0;
|
|
double _lastBottomHeight = 0;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_position = widget.position;
|
|
}
|
|
|
|
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) {
|
|
x = size.width - widget.width;
|
|
} else if (_position.dx + offset.dx < 0) {
|
|
x = 0;
|
|
} else {
|
|
x = _position.dx + offset.dx;
|
|
}
|
|
|
|
if (_position.dy + offset.dy + widget.height > size.height) {
|
|
y = size.height - widget.height;
|
|
} else if (_position.dy + offset.dy < 0) {
|
|
y = 0;
|
|
} else {
|
|
y = _position.dy + offset.dy;
|
|
}
|
|
setState(() {
|
|
_position = Offset(x, y);
|
|
});
|
|
}
|
|
|
|
checkScreenSize() {}
|
|
|
|
checkKeyboard() {
|
|
final bottomHeight = MediaQuery.of(context).viewInsets.bottom;
|
|
final currentVisible = bottomHeight != 0;
|
|
|
|
debugPrint(bottomHeight.toString() + currentVisible.toString());
|
|
// save
|
|
if (!_keyboardVisible && currentVisible) {
|
|
_saveHeight = _position.dy;
|
|
}
|
|
|
|
// reset
|
|
if (_lastBottomHeight > 0 && bottomHeight == 0) {
|
|
setState(() {
|
|
_position = Offset(_position.dx, _saveHeight);
|
|
});
|
|
}
|
|
|
|
// onKeyboardVisible
|
|
if (_keyboardVisible && currentVisible) {
|
|
final sumHeight = bottomHeight + widget.height;
|
|
final contextHeight = MediaQuery.of(context).size.height;
|
|
if (sumHeight + _position.dy > contextHeight) {
|
|
final y = contextHeight - sumHeight;
|
|
setState(() {
|
|
_position = Offset(_position.dx, y);
|
|
});
|
|
}
|
|
}
|
|
|
|
_keyboardVisible = currentVisible;
|
|
_lastBottomHeight = bottomHeight;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (widget.checkKeyboard) {
|
|
checkKeyboard();
|
|
}
|
|
if (widget.checkKeyboard) {
|
|
checkScreenSize();
|
|
}
|
|
return Positioned(
|
|
top: _position.dy,
|
|
left: _position.dx,
|
|
width: widget.width,
|
|
height: widget.height,
|
|
child: widget.builder(context, onPanUpdate));
|
|
}
|
|
}
|