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

View File

@ -1,7 +1,7 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.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 '../../consts.dart';
@ -92,31 +92,30 @@ class DraggableChatWindow extends StatelessWidget {
bottom: BorderSide(
color: Theme.of(context).hintColor.withOpacity(0.4)))),
height: 38,
child: Obx(() => Opacity(
opacity: chatModel.isWindowFocus.value ? 1.0 : 0.4,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
child: Obx(() => Opacity(
opacity: chatModel.isWindowFocus.value ? 1.0 : 0.4,
child: Row(children: [
Icon(Icons.chat_bubble_outline,
size: 20, color: Theme.of(context).colorScheme.primary),
SizedBox(width: 6),
Text(translate("Chat"))
])),
Padding(
padding: EdgeInsets.all(2),
child: ActionIcon(
message: 'Close',
icon: IconFont.close,
onTap: chatModel.hideChatWindowOverlay,
isClose: true,
boxSize: 32,
))
],
))),
])))),
Padding(
padding: EdgeInsets.all(2),
child: ActionIcon(
message: 'Close',
icon: IconFont.close,
onTap: chatModel.hideChatWindowOverlay,
isClose: true,
boxSize: 32,
))
],
),
);
}
}
@ -372,3 +371,68 @@ class QualityMonitor extends StatelessWidget {
)
: 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);
}
}

View File

@ -62,6 +62,8 @@ class _RemotePageState extends State<RemotePage>
late RxBool _remoteCursorMoved;
late RxBool _keyboardEnabled;
final overlayState = PenetrableOverlayState();
final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode");
Function(bool)? _onEnterOrLeaveImage4Menubar;
@ -133,6 +135,12 @@ class _RemotePageState extends State<RemotePage>
// });
// _isCustomCursorInited = true;
// }
_ffi.chatModel.setPenetrableOverlayState(overlayState);
// make remote page penetrable automatically, effective for chat over remote
overlayState.onMiddleBlockedClick = () {
overlayState.setMiddleBlocked(false);
};
}
@override
@ -192,39 +200,47 @@ class _RemotePageState extends State<RemotePage>
Widget buildBody(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: Overlay(
initialEntries: [
OverlayEntry(builder: (context) {
_ffi.chatModel.setOverlayState(Overlay.of(context));
_ffi.dialogManager.setOverlayState(Overlay.of(context));
return Container(
color: Colors.black,
child: RawKeyFocusScope(
focusNode: _rawKeyFocusNode,
onFocusChange: (bool imageFocused) {
debugPrint(
"onFocusChange(window active:${!_isWindowBlur}) $imageFocused");
// See [onWindowBlur].
if (Platform.isWindows) {
if (_isWindowBlur) {
imageFocused = false;
Future.delayed(Duration.zero, () {
_rawKeyFocusNode.unfocus();
});
}
if (imageFocused) {
_ffi.inputModel.enterOrLeave(true);
} else {
_ffi.inputModel.enterOrLeave(false);
}
}
},
inputModel: _ffi.inputModel,
child: getBodyForDesktop(context)));
})
],
));
backgroundColor: Theme.of(context).backgroundColor,
body: PenetrableOverlay(
state: overlayState,
underlying: Container(
color: Colors.black,
child: RawKeyFocusScope(
focusNode: _rawKeyFocusNode,
onFocusChange: (bool imageFocused) {
debugPrint(
"onFocusChange(window active:${!_isWindowBlur}) $imageFocused");
// See [onWindowBlur].
if (Platform.isWindows) {
if (_isWindowBlur) {
imageFocused = false;
Future.delayed(Duration.zero, () {
_rawKeyFocusNode.unfocus();
});
}
if (imageFocused) {
_ffi.inputModel.enterOrLeave(true);
} else {
_ffi.inputModel.enterOrLeave(false);
}
}
},
inputModel: _ffi.inputModel,
child: getBodyForDesktop(context))),
upperLayer: [
OverlayEntry(
builder: (context) => RemoteMenubar(
id: widget.id,
ffi: _ffi,
state: widget.menubarState,
onEnterOrLeaveImageSetter: (func) =>
_onEnterOrLeaveImage4Menubar = func,
onEnterOrLeaveImageCleaner: () =>
_onEnterOrLeaveImage4Menubar = null,
))
],
),
);
}
@override
@ -345,13 +361,6 @@ class _RemotePageState extends State<RemotePage>
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(
children: paints,
);

View File

@ -297,12 +297,23 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
);
}
final _chatButtonKey = GlobalKey();
Widget _buildChat(BuildContext context) {
return IconButton(
key: _chatButtonKey,
tooltip: translate('Chat'),
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.toggleChatOverlay();
widget.ffi.chatModel.toggleChatOverlay(chatInitPos: initPos);
},
icon: const Icon(
Icons.message,

View File

@ -5,7 +5,6 @@ import 'package:draggable_float_widget/draggable_float_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/platform_model.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 '../consts.dart';
@ -30,16 +29,12 @@ class MessageBody {
class ChatModel with ChangeNotifier {
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? chatWindowOverlayEntry;
bool isConnManager = false;
RxBool isWindowFocus = true.obs;
PenetrableOverlayState? pOverlayState;
final ChatUser me = ChatUser(
id: "",
@ -58,6 +53,19 @@ class ChatModel with ChangeNotifier {
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;
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)}) {
if (chatIconOverlayEntry != null) {
chatIconOverlayEntry!.remove();
@ -100,7 +94,7 @@ class ChatModel with ChangeNotifier {
}
}
final overlayState = _getOverlayState();
final overlayState = pOverlayState?.getOverlayStateOrGlobal();
if (overlayState == null) return;
final overlay = OverlayEntry(builder: (context) {
@ -132,33 +126,26 @@ class ChatModel with ChangeNotifier {
}
}
showChatWindowOverlay() {
showChatWindowOverlay({Offset? chatInitPos}) {
if (chatWindowOverlayEntry != null) return;
final overlayState = _getOverlayState();
isWindowFocus.value = true;
pOverlayState?.setMiddleBlocked(true);
final overlayState = pOverlayState?.getOverlayStateOrGlobal();
if (overlayState == null) return;
final overlay = OverlayEntry(builder: (context) {
bool innerClicked = false;
return Listener(
onPointerDown: (_) {
if (!innerClicked) {
isWindowFocus.value = false;
if (!isWindowFocus.value) {
isWindowFocus.value = true;
pOverlayState?.setMiddleBlocked(true);
}
innerClicked = false;
},
child: Obx(() => Container(
color: isWindowFocus.value ? Colors.red.withOpacity(0.3) : null,
child: Listener(
onPointerDown: (_) {
innerClicked = true;
if (!isWindowFocus.value) {
isWindowFocus.value = true;
}
},
child: DraggableChatWindow(
position: const Offset(20, 80),
width: 250,
height: 350,
chatModel: this)))));
child: DraggableChatWindow(
position: chatInitPos ?? Offset(20, 80),
width: 250,
height: 350,
chatModel: this));
});
overlayState.insert(overlay);
chatWindowOverlayEntry = overlay;
@ -167,6 +154,7 @@ class ChatModel with ChangeNotifier {
hideChatWindowOverlay() {
if (chatWindowOverlayEntry != null) {
pOverlayState?.setMiddleBlocked(false);
chatWindowOverlayEntry!.remove();
chatWindowOverlayEntry = null;
return;
@ -176,13 +164,13 @@ class ChatModel with ChangeNotifier {
_isChatOverlayHide() => ((!isDesktop && chatIconOverlayEntry == null) ||
chatWindowOverlayEntry == null);
toggleChatOverlay() {
toggleChatOverlay({Offset? chatInitPos}) {
if (_isChatOverlayHide()) {
gFFI.invokeMethod("enable_soft_keyboard", true);
if (!isDesktop) {
showChatIconOverlay();
}
showChatWindowOverlay();
showChatWindowOverlay(chatInitPos: chatInitPos);
} else {
hideChatIconOverlay();
hideChatWindowOverlay();
@ -310,7 +298,6 @@ class ChatModel with ChangeNotifier {
close() {
hideChatIconOverlay();
hideChatWindowOverlay();
_overlayState = null;
notifyListeners();
}