add PenetrableOverlayState, opt chat page over remote_page

This commit is contained in:
csf 2023-02-07 00:11:48 +09:00
parent c306ec3ba7
commit 893f18cdec
4 changed files with 177 additions and 106 deletions
flutter/lib

@ -1,7 +1,7 @@
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../consts.dart'; import '../../consts.dart';
@ -92,20 +92,19 @@ class DraggableChatWindow extends StatelessWidget {
bottom: BorderSide( bottom: BorderSide(
color: Theme.of(context).hintColor.withOpacity(0.4)))), color: Theme.of(context).hintColor.withOpacity(0.4)))),
height: 38, height: 38,
child: Obx(() => Opacity(
opacity: chatModel.isWindowFocus.value ? 1.0 : 0.4,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Padding( Padding(
padding: padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
const EdgeInsets.symmetric(horizontal: 15, vertical: 8), child: Obx(() => Opacity(
opacity: chatModel.isWindowFocus.value ? 1.0 : 0.4,
child: Row(children: [ child: Row(children: [
Icon(Icons.chat_bubble_outline, Icon(Icons.chat_bubble_outline,
size: 20, color: Theme.of(context).colorScheme.primary), size: 20, color: Theme.of(context).colorScheme.primary),
SizedBox(width: 6), SizedBox(width: 6),
Text(translate("Chat")) Text(translate("Chat"))
])), ])))),
Padding( Padding(
padding: EdgeInsets.all(2), padding: EdgeInsets.all(2),
child: ActionIcon( child: ActionIcon(
@ -116,7 +115,7 @@ class DraggableChatWindow extends StatelessWidget {
boxSize: 32, boxSize: 32,
)) ))
], ],
))), ),
); );
} }
} }
@ -372,3 +371,68 @@ class QualityMonitor extends StatelessWidget {
) )
: const SizedBox.shrink())); : const SizedBox.shrink()));
} }
class PenetrableOverlayState {
final _middleBlocked = false.obs;
final _overlayKey = GlobalKey<OverlayState>();
VoidCallback? onMiddleBlockedClick; // to-do use listener
RxBool get middleBlocked => _middleBlocked;
GlobalKey get overlayKey => _overlayKey;
OverlayState? get overlayState => _overlayKey.currentState;
OverlayState? getOverlayStateOrGlobal() {
if (overlayState == null) {
if (globalKey.currentState == null ||
globalKey.currentState!.overlay == null) return null;
return globalKey.currentState!.overlay;
} else {
return overlayState;
}
}
void addMiddleBlockedListener(void Function(bool) cb) {
_middleBlocked.listen(cb);
}
void setMiddleBlocked(bool blocked) {
if (blocked != _middleBlocked.value) {
_middleBlocked.value = blocked;
}
}
}
class PenetrableOverlay extends StatelessWidget {
final Widget underlying;
final List<OverlayEntry>? upperLayer;
final PenetrableOverlayState state;
PenetrableOverlay(
{required this.underlying, required this.state, this.upperLayer});
@override
Widget build(BuildContext context) {
final initialEntries = [
OverlayEntry(builder: (_) => underlying),
/// middle layer
OverlayEntry(
builder: (context) => Obx(() => Listener(
onPointerDown: (_) {
state.onMiddleBlockedClick?.call();
},
child: Container(
color: state.middleBlocked.value
? Colors.red.withOpacity(0.3)
: null)))),
];
if (upperLayer != null) {
initialEntries.addAll(upperLayer!);
}
return Overlay(key: state.overlayKey, initialEntries: initialEntries);
}
}

@ -62,6 +62,8 @@ class _RemotePageState extends State<RemotePage>
late RxBool _remoteCursorMoved; late RxBool _remoteCursorMoved;
late RxBool _keyboardEnabled; late RxBool _keyboardEnabled;
final overlayState = PenetrableOverlayState();
final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode"); final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode");
Function(bool)? _onEnterOrLeaveImage4Menubar; Function(bool)? _onEnterOrLeaveImage4Menubar;
@ -133,6 +135,12 @@ class _RemotePageState extends State<RemotePage>
// }); // });
// _isCustomCursorInited = true; // _isCustomCursorInited = true;
// } // }
_ffi.chatModel.setPenetrableOverlayState(overlayState);
// make remote page penetrable automatically, effective for chat over remote
overlayState.onMiddleBlockedClick = () {
overlayState.setMiddleBlocked(false);
};
} }
@override @override
@ -193,12 +201,9 @@ class _RemotePageState extends State<RemotePage>
Widget buildBody(BuildContext context) { Widget buildBody(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).backgroundColor, backgroundColor: Theme.of(context).backgroundColor,
body: Overlay( body: PenetrableOverlay(
initialEntries: [ state: overlayState,
OverlayEntry(builder: (context) { underlying: Container(
_ffi.chatModel.setOverlayState(Overlay.of(context));
_ffi.dialogManager.setOverlayState(Overlay.of(context));
return Container(
color: Colors.black, color: Colors.black,
child: RawKeyFocusScope( child: RawKeyFocusScope(
focusNode: _rawKeyFocusNode, focusNode: _rawKeyFocusNode,
@ -221,10 +226,21 @@ class _RemotePageState extends State<RemotePage>
} }
}, },
inputModel: _ffi.inputModel, inputModel: _ffi.inputModel,
child: getBodyForDesktop(context))); child: getBodyForDesktop(context))),
}) upperLayer: [
OverlayEntry(
builder: (context) => RemoteMenubar(
id: widget.id,
ffi: _ffi,
state: widget.menubarState,
onEnterOrLeaveImageSetter: (func) =>
_onEnterOrLeaveImage4Menubar = func,
onEnterOrLeaveImageCleaner: () =>
_onEnterOrLeaveImage4Menubar = null,
))
], ],
)); ),
);
} }
@override @override
@ -345,13 +361,6 @@ class _RemotePageState extends State<RemotePage>
QualityMonitor(_ffi.qualityMonitorModel), null, null), QualityMonitor(_ffi.qualityMonitorModel), null, null),
), ),
); );
paints.add(RemoteMenubar(
id: widget.id,
ffi: _ffi,
state: widget.menubarState,
onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Menubar = func,
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Menubar = null,
));
return Stack( return Stack(
children: paints, children: paints,
); );

@ -297,12 +297,23 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
); );
} }
final _chatButtonKey = GlobalKey();
Widget _buildChat(BuildContext context) { Widget _buildChat(BuildContext context) {
return IconButton( return IconButton(
key: _chatButtonKey,
tooltip: translate('Chat'), tooltip: translate('Chat'),
onPressed: () { onPressed: () {
RenderBox? renderBox =
_chatButtonKey.currentContext?.findRenderObject() as RenderBox?;
Offset? initPos;
if (renderBox != null) {
final pos = renderBox.localToGlobal(Offset.zero);
initPos = Offset(pos.dx, pos.dy + _MenubarTheme.dividerHeight);
}
widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID); widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID);
widget.ffi.chatModel.toggleChatOverlay(); widget.ffi.chatModel.toggleChatOverlay(chatInitPos: initPos);
}, },
icon: const Icon( icon: const Icon(
Icons.message, Icons.message,

@ -5,7 +5,6 @@ import 'package:draggable_float_widget/draggable_float_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import '../consts.dart'; import '../consts.dart';
@ -30,16 +29,12 @@ class MessageBody {
class ChatModel with ChangeNotifier { class ChatModel with ChangeNotifier {
static final clientModeID = -1; static final clientModeID = -1;
/// _overlayState:
/// Desktop: store session overlay by using [setOverlayState].
/// Mobile: always null, use global overlay.
/// see [_getOverlayState] in [showChatIconOverlay] or [showChatWindowOverlay]
OverlayState? _overlayState;
OverlayEntry? chatIconOverlayEntry; OverlayEntry? chatIconOverlayEntry;
OverlayEntry? chatWindowOverlayEntry; OverlayEntry? chatWindowOverlayEntry;
bool isConnManager = false; bool isConnManager = false;
RxBool isWindowFocus = true.obs; RxBool isWindowFocus = true.obs;
PenetrableOverlayState? pOverlayState;
final ChatUser me = ChatUser( final ChatUser me = ChatUser(
id: "", id: "",
@ -58,6 +53,19 @@ class ChatModel with ChangeNotifier {
bool get isShowCMChatPage => _isShowCMChatPage; bool get isShowCMChatPage => _isShowCMChatPage;
void setPenetrableOverlayState(PenetrableOverlayState state) {
pOverlayState = state;
pOverlayState!.addMiddleBlockedListener((v) {
if (!v) {
isWindowFocus.value = false;
if (isWindowFocus.value) {
isWindowFocus.toggle();
}
}
});
}
final WeakReference<FFI> parent; final WeakReference<FFI> parent;
ChatModel(this.parent); ChatModel(this.parent);
@ -74,20 +82,6 @@ class ChatModel with ChangeNotifier {
} }
} }
setOverlayState(OverlayState? os) {
_overlayState = os;
}
OverlayState? _getOverlayState() {
if (_overlayState == null) {
if (globalKey.currentState == null ||
globalKey.currentState!.overlay == null) return null;
return globalKey.currentState!.overlay;
} else {
return _overlayState;
}
}
showChatIconOverlay({Offset offset = const Offset(200, 50)}) { showChatIconOverlay({Offset offset = const Offset(200, 50)}) {
if (chatIconOverlayEntry != null) { if (chatIconOverlayEntry != null) {
chatIconOverlayEntry!.remove(); chatIconOverlayEntry!.remove();
@ -100,7 +94,7 @@ class ChatModel with ChangeNotifier {
} }
} }
final overlayState = _getOverlayState(); final overlayState = pOverlayState?.getOverlayStateOrGlobal();
if (overlayState == null) return; if (overlayState == null) return;
final overlay = OverlayEntry(builder: (context) { final overlay = OverlayEntry(builder: (context) {
@ -132,33 +126,26 @@ class ChatModel with ChangeNotifier {
} }
} }
showChatWindowOverlay() { showChatWindowOverlay({Offset? chatInitPos}) {
if (chatWindowOverlayEntry != null) return; if (chatWindowOverlayEntry != null) return;
final overlayState = _getOverlayState(); isWindowFocus.value = true;
pOverlayState?.setMiddleBlocked(true);
final overlayState = pOverlayState?.getOverlayStateOrGlobal();
if (overlayState == null) return; if (overlayState == null) return;
final overlay = OverlayEntry(builder: (context) { final overlay = OverlayEntry(builder: (context) {
bool innerClicked = false;
return Listener( return Listener(
onPointerDown: (_) { onPointerDown: (_) {
if (!innerClicked) {
isWindowFocus.value = false;
}
innerClicked = false;
},
child: Obx(() => Container(
color: isWindowFocus.value ? Colors.red.withOpacity(0.3) : null,
child: Listener(
onPointerDown: (_) {
innerClicked = true;
if (!isWindowFocus.value) { if (!isWindowFocus.value) {
isWindowFocus.value = true; isWindowFocus.value = true;
pOverlayState?.setMiddleBlocked(true);
} }
}, },
child: DraggableChatWindow( child: DraggableChatWindow(
position: const Offset(20, 80), position: chatInitPos ?? Offset(20, 80),
width: 250, width: 250,
height: 350, height: 350,
chatModel: this))))); chatModel: this));
}); });
overlayState.insert(overlay); overlayState.insert(overlay);
chatWindowOverlayEntry = overlay; chatWindowOverlayEntry = overlay;
@ -167,6 +154,7 @@ class ChatModel with ChangeNotifier {
hideChatWindowOverlay() { hideChatWindowOverlay() {
if (chatWindowOverlayEntry != null) { if (chatWindowOverlayEntry != null) {
pOverlayState?.setMiddleBlocked(false);
chatWindowOverlayEntry!.remove(); chatWindowOverlayEntry!.remove();
chatWindowOverlayEntry = null; chatWindowOverlayEntry = null;
return; return;
@ -176,13 +164,13 @@ class ChatModel with ChangeNotifier {
_isChatOverlayHide() => ((!isDesktop && chatIconOverlayEntry == null) || _isChatOverlayHide() => ((!isDesktop && chatIconOverlayEntry == null) ||
chatWindowOverlayEntry == null); chatWindowOverlayEntry == null);
toggleChatOverlay() { toggleChatOverlay({Offset? chatInitPos}) {
if (_isChatOverlayHide()) { if (_isChatOverlayHide()) {
gFFI.invokeMethod("enable_soft_keyboard", true); gFFI.invokeMethod("enable_soft_keyboard", true);
if (!isDesktop) { if (!isDesktop) {
showChatIconOverlay(); showChatIconOverlay();
} }
showChatWindowOverlay(); showChatWindowOverlay(chatInitPos: chatInitPos);
} else { } else {
hideChatIconOverlay(); hideChatIconOverlay();
hideChatWindowOverlay(); hideChatWindowOverlay();
@ -310,7 +298,6 @@ class ChatModel with ChangeNotifier {
close() { close() {
hideChatIconOverlay(); hideChatIconOverlay();
hideChatWindowOverlay(); hideChatWindowOverlay();
_overlayState = null;
notifyListeners(); notifyListeners();
} }