diff --git a/lib/widgets/overlay.dart b/lib/widgets/overlay.dart new file mode 100644 index 000000000..8861321eb --- /dev/null +++ b/lib/widgets/overlay.dart @@ -0,0 +1,378 @@ +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 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; + debugPrint("created"); +} + +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; + debugPrint("chatEntry created"); +} + +hideChatWindowOverlay() { + if (chatWindowOverlayEntry != null) { + chatWindowOverlayEntry!.remove(); + chatWindowOverlayEntry = null; + return; + } +} + +toggleChatOverlay() { + if (chatIconOverlayEntry == null || chatWindowOverlayEntry == null) { + FFI.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)), + ], + ), + )); + }); + } +} + +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 - 60; + debugPrint("left top : $left,$top"); + + final overlay = OverlayEntry(builder: (context) { + return DraggableMobileActions( + position: Offset(left, top), + width: overlayW, + height: overlayH, + onBackPressed: () => FFI.tap(MouseButtons.right), + onHomePressed: () => FFI.tap(MouseButtons.wheel), + onRecentPressed: () async { + FFI.sendMouse('down', MouseButtons.wheel); + await Future.delayed(Duration(milliseconds: 500)); + FFI.sendMouse('up', MouseButtons.wheel); + }, + ); + }); + globalOverlayState.insert(overlay); + mobileActionsOverlayEntry = overlay; + debugPrint("mobileActionsOverlay created"); +} + +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 createState() => _DraggableState(); +} + +class _DraggableState extends State { + 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)); + } +}