mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-28 15:49:04 +08:00
add PenetrableOverlayState, opt chat page over remote_page
This commit is contained in:
parent
c306ec3ba7
commit
893f18cdec
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user