add PenetrableOverlayState, opt chat page over remote_page
This commit is contained in:
parent
c306ec3ba7
commit
893f18cdec
flutter/lib
common/widgets
desktop
models
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user