Merge pull request #3123 from Heap-Hop/opt_chat_overlay_and_fix_pageview_2

Fix chat text selectable, refactor overlay and fix pageview
This commit is contained in:
RustDesk 2023-02-08 21:43:26 +08:00 committed by GitHub
commit 5aee66e89c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 281 additions and 147 deletions

View File

@ -367,20 +367,25 @@ class Dialog<T> {
} }
} }
class OverlayKeyState {
final _overlayKey = GlobalKey<OverlayState>();
/// use global overlay by default
OverlayState? get state =>
_overlayKey.currentState ?? globalKey.currentState?.overlay;
GlobalKey<OverlayState>? get key => _overlayKey;
}
class OverlayDialogManager { class OverlayDialogManager {
OverlayState? _overlayState;
final Map<String, Dialog> _dialogs = {}; final Map<String, Dialog> _dialogs = {};
var _overlayKeyState = OverlayKeyState();
int _tagCount = 0; int _tagCount = 0;
OverlayEntry? _mobileActionsOverlayEntry; OverlayEntry? _mobileActionsOverlayEntry;
/// By default OverlayDialogManager use global overlay void setOverlayState(OverlayKeyState overlayKeyState) {
OverlayDialogManager() { _overlayKeyState = overlayKeyState;
_overlayState = globalKey.currentState?.overlay;
}
void setOverlayState(OverlayState? overlayState) {
_overlayState = overlayState;
} }
void dismissAll() { void dismissAll() {
@ -404,7 +409,7 @@ class OverlayDialogManager {
bool useAnimation = true, bool useAnimation = true,
bool forceGlobal = false}) { bool forceGlobal = false}) {
final overlayState = final overlayState =
forceGlobal ? globalKey.currentState?.overlay : _overlayState; forceGlobal ? globalKey.currentState?.overlay : _overlayKeyState.state;
if (overlayState == null) { if (overlayState == null) {
return Future.error( return Future.error(
@ -508,7 +513,8 @@ class OverlayDialogManager {
void showMobileActionsOverlay({FFI? ffi}) { void showMobileActionsOverlay({FFI? ffi}) {
if (_mobileActionsOverlayEntry != null) return; if (_mobileActionsOverlayEntry != null) return;
if (_overlayState == null) return; final overlayState = _overlayKeyState.state;
if (overlayState == null) return;
// compute overlay position // compute overlay position
final screenW = MediaQuery.of(globalKey.currentContext!).size.width; final screenW = MediaQuery.of(globalKey.currentContext!).size.width;
@ -534,7 +540,7 @@ class OverlayDialogManager {
onHidePressed: () => hideMobileActionsOverlay(), onHidePressed: () => hideMobileActionsOverlay(),
); );
}); });
_overlayState!.insert(overlay); overlayState.insert(overlay);
_mobileActionsOverlayEntry = overlay; _mobileActionsOverlayEntry = overlay;
} }

View File

@ -95,10 +95,31 @@ class ChatPage extends StatelessWidget implements PageShape {
color: Theme.of(context).colorScheme.primary)), color: Theme.of(context).colorScheme.primary)),
messageOptions: MessageOptions( messageOptions: MessageOptions(
showOtherUsersAvatar: false, showOtherUsersAvatar: false,
showTime: true,
currentUserTextColor: Colors.white,
textColor: Colors.white, textColor: Colors.white,
maxWidth: constraints.maxWidth * 0.7, maxWidth: constraints.maxWidth * 0.7,
messageTextBuilder: (message, _, __) {
final isOwnMessage =
message.user.id == currentUser.id;
return Column(
crossAxisAlignment: isOwnMessage
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: <Widget>[
Text(message.text,
style: TextStyle(color: Colors.white)),
Padding(
padding: const EdgeInsets.only(top: 5),
child: Text(
"${message.createdAt.hour}:${message.createdAt.minute}",
style: TextStyle(
color: Colors.white,
fontSize: 10,
),
),
),
],
);
},
messageDecorationBuilder: (_, __, ___) => messageDecorationBuilder: (_, __, ___) =>
defaultMessageDecoration( defaultMessageDecoration(
color: MyTheme.accent80, color: MyTheme.accent80,

View File

@ -1,6 +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.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../consts.dart'; import '../../consts.dart';
@ -96,12 +97,14 @@ class DraggableChatWindow extends StatelessWidget {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
child: Row(children: [ child: Obx(() => Opacity(
Icon(Icons.chat_bubble_outline, opacity: chatModel.isWindowFocus.value ? 1.0 : 0.4,
size: 20, color: Theme.of(context).colorScheme.primary), child: Row(children: [
SizedBox(width: 6), Icon(Icons.chat_bubble_outline,
Text(translate("Chat")) size: 20, color: Theme.of(context).colorScheme.primary),
])), SizedBox(width: 6),
Text(translate("Chat"))
])))),
Padding( Padding(
padding: EdgeInsets.all(2), padding: EdgeInsets.all(2),
child: ActionIcon( child: ActionIcon(
@ -304,15 +307,17 @@ class _DraggableState extends State<Draggable> {
if (widget.checkKeyboard) { if (widget.checkKeyboard) {
checkKeyboard(); checkKeyboard();
} }
if (widget.checkKeyboard) { if (widget.checkScreenSize) {
checkScreenSize(); checkScreenSize();
} }
return Positioned( return Stack(children: [
top: _position.dy, Positioned(
left: _position.dx, top: _position.dy,
width: widget.width, left: _position.dx,
height: widget.height, width: widget.width,
child: widget.builder(context, onPanUpdate)); height: widget.height,
child: widget.builder(context, onPanUpdate))
]);
} }
} }
@ -366,3 +371,55 @@ class QualityMonitor extends StatelessWidget {
) )
: const SizedBox.shrink())); : const SizedBox.shrink()));
} }
class BlockableOverlayState extends OverlayKeyState {
final _middleBlocked = false.obs;
VoidCallback? onMiddleBlockedClick; // to-do use listener
RxBool get middleBlocked => _middleBlocked;
void addMiddleBlockedListener(void Function(bool) cb) {
_middleBlocked.listen(cb);
}
void setMiddleBlocked(bool blocked) {
if (blocked != _middleBlocked.value) {
_middleBlocked.value = blocked;
}
}
}
class BlockableOverlay extends StatelessWidget {
final Widget underlying;
final List<OverlayEntry>? upperLayer;
final BlockableOverlayState state;
BlockableOverlay(
{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.transparent : null)))),
];
if (upperLayer != null) {
initialEntries.addAll(upperLayer!);
}
/// set key
return Overlay(key: state.key, initialEntries: initialEntries);
}
}

View File

@ -64,23 +64,17 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final tabWidget = Container( final tabWidget = Container(
child: Overlay(initialEntries: [ child: Scaffold(
OverlayEntry(builder: (context) { backgroundColor: Theme.of(context).backgroundColor,
gFFI.dialogManager.setOverlayState(Overlay.of(context)); body: DesktopTab(
return Scaffold( controller: tabController,
backgroundColor: Theme.of(context).backgroundColor, tail: ActionIcon(
body: DesktopTab( message: 'Settings',
controller: tabController, icon: IconFont.menu,
tail: ActionIcon( onTap: DesktopTabPage.onAddSetting,
message: 'Settings', isClose: false,
icon: IconFont.menu, ),
onTap: DesktopTabPage.onAddSetting, )));
isClose: false,
),
));
})
]),
);
return Platform.isMacOS return Platform.isMacOS
? tabWidget ? tabWidget
: Obx( : Obx(

View File

@ -80,6 +80,7 @@ class _FileManagerPageState extends State<FileManagerPage>
Entry? _lastClickEntry; Entry? _lastClickEntry;
final _dropMaskVisible = false.obs; // TODO impl drop mask final _dropMaskVisible = false.obs; // TODO impl drop mask
final _overlayKeyState = OverlayKeyState();
ScrollController getBreadCrumbScrollController(bool isLocal) { ScrollController getBreadCrumbScrollController(bool isLocal) {
return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote; return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote;
@ -115,6 +116,7 @@ class _FileManagerPageState extends State<FileManagerPage>
// register location listener // register location listener
_locationNodeLocal.addListener(onLocalLocationFocusChanged); _locationNodeLocal.addListener(onLocalLocationFocusChanged);
_locationNodeRemote.addListener(onRemoteLocationFocusChanged); _locationNodeRemote.addListener(onRemoteLocationFocusChanged);
_ffi.dialogManager.setOverlayState(_overlayKeyState);
} }
@override @override
@ -137,9 +139,8 @@ class _FileManagerPageState extends State<FileManagerPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Overlay(initialEntries: [ return Overlay(key: _overlayKeyState.key, initialEntries: [
OverlayEntry(builder: (context) { OverlayEntry(builder: (_) {
_ffi.dialogManager.setOverlayState(Overlay.of(context));
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: _ffi.fileModel, value: _ffi.fileModel,
child: Consumer<FileModel>(builder: (context, model, child) { child: Consumer<FileModel>(builder: (context, model, child) {

View File

@ -61,6 +61,8 @@ class _RemotePageState extends State<RemotePage>
late RxBool _remoteCursorMoved; late RxBool _remoteCursorMoved;
late RxBool _keyboardEnabled; late RxBool _keyboardEnabled;
final _blockableOverlayState = BlockableOverlayState();
final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode"); final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode");
Function(bool)? _onEnterOrLeaveImage4Menubar; Function(bool)? _onEnterOrLeaveImage4Menubar;
@ -132,6 +134,13 @@ class _RemotePageState extends State<RemotePage>
// }); // });
// _isCustomCursorInited = true; // _isCustomCursorInited = true;
// } // }
_ffi.dialogManager.setOverlayState(_blockableOverlayState);
_ffi.chatModel.setOverlayState(_blockableOverlayState);
// make remote page penetrable automatically, effective for chat over remote
_blockableOverlayState.onMiddleBlockedClick = () {
_blockableOverlayState.setMiddleBlocked(false);
};
} }
@override @override
@ -191,39 +200,50 @@ 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(
initialEntries: [ /// the Overlay key will be set with _blockableOverlayState in BlockableOverlay
OverlayEntry(builder: (context) { /// see override build() in [BlockableOverlay]
_ffi.chatModel.setOverlayState(Overlay.of(context)); body: BlockableOverlay(
_ffi.dialogManager.setOverlayState(Overlay.of(context)); state: _blockableOverlayState,
return Container( underlying: Container(
color: Colors.black, color: Colors.black,
child: RawKeyFocusScope( child: RawKeyFocusScope(
focusNode: _rawKeyFocusNode, focusNode: _rawKeyFocusNode,
onFocusChange: (bool imageFocused) { onFocusChange: (bool imageFocused) {
debugPrint( debugPrint(
"onFocusChange(window active:${!_isWindowBlur}) $imageFocused"); "onFocusChange(window active:${!_isWindowBlur}) $imageFocused");
// See [onWindowBlur]. // See [onWindowBlur].
if (Platform.isWindows) { if (Platform.isWindows) {
if (_isWindowBlur) { if (_isWindowBlur) {
imageFocused = false; imageFocused = false;
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
_rawKeyFocusNode.unfocus(); _rawKeyFocusNode.unfocus();
}); });
} }
if (imageFocused) { if (imageFocused) {
_ffi.inputModel.enterOrLeave(true); _ffi.inputModel.enterOrLeave(true);
} else { } else {
_ffi.inputModel.enterOrLeave(false); _ffi.inputModel.enterOrLeave(false);
} }
} }
}, },
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
@ -344,13 +364,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,
); );

View File

@ -68,26 +68,19 @@ class _DesktopServerPageState extends State<DesktopServerPage>
], ],
child: Consumer<ServerModel>( child: Consumer<ServerModel>(
builder: (context, serverModel, child) => Container( builder: (context, serverModel, child) => Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: border: Border.all(color: MyTheme.color(context).border!)),
Border.all(color: MyTheme.color(context).border!)), child: Scaffold(
child: Overlay(initialEntries: [ backgroundColor: Theme.of(context).backgroundColor,
OverlayEntry(builder: (context) { body: Center(
gFFI.dialogManager.setOverlayState(Overlay.of(context)); child: Column(
return Scaffold( mainAxisAlignment: MainAxisAlignment.start,
backgroundColor: Theme.of(context).backgroundColor, children: [
body: Center( Expanded(child: ConnectionManager()),
child: Column( ],
mainAxisAlignment: MainAxisAlignment.start, ),
children: [ ),
Expanded(child: ConnectionManager()), ))));
],
),
),
);
})
]),
)));
} }
@override @override

View File

@ -688,9 +688,11 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
); );
} }
final _chatButtonKey = GlobalKey();
Widget _buildChat(BuildContext context) { Widget _buildChat(BuildContext context) {
FfiModel ffiModel = Provider.of<FfiModel>(context); FfiModel ffiModel = Provider.of<FfiModel>(context);
return mod_menu.PopupMenuButton( return mod_menu.PopupMenuButton(
key: _chatButtonKey,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
icon: SvgPicture.asset( icon: SvgPicture.asset(
"assets/chat.svg", "assets/chat.svg",
@ -779,8 +781,17 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
style: style, style: style,
), ),
proc: () { proc: () {
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);
}, },
padding: padding, padding: padding,
dismissOnClicked: true, dismissOnClicked: true,

View File

@ -327,14 +327,32 @@ class DesktopTab extends StatelessWidget {
)); ));
} }
List<Widget> _tabWidgets = [];
Widget _buildPageView() { Widget _buildPageView() {
return _buildBlock( return _buildBlock(
child: Obx(() => PageView( child: Obx(() => PageView(
controller: state.value.pageController, controller: state.value.pageController,
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
children: state.value.tabs children: () {
.map((tab) => tab.page) /// to-do refactor, separate connection state and UI state for remote session.
.toList(growable: false)))); /// [workaround] PageView children need an immutable list, after it has been passed into PageView
final tabLen = state.value.tabs.length;
if (tabLen == _tabWidgets.length) {
return _tabWidgets;
} else if (_tabWidgets.isNotEmpty &&
tabLen == _tabWidgets.length + 1) {
/// On add. Use the previous list(pointer) to prevent item's state init twice.
/// *[_tabWidgets.isNotEmpty] means TabsWindow(remote_tab_page or file_manager_tab_page) opened before, but was hidden. In this case, we have to reload, otherwise the child can't be built.
_tabWidgets.add(state.value.tabs.last.page);
return _tabWidgets;
} else {
/// On remove or change. Use new list(pointer) to reload list children so that items loading order is normal.
/// the Widgets in list must enable [AutomaticKeepAliveClientMixin]
final newList = state.value.tabs.map((v) => v.page).toList();
_tabWidgets = newList;
return newList;
}
}())));
} }
/// Check whether to show ListView /// Check whether to show ListView
@ -767,7 +785,8 @@ class _ListView extends StatelessWidget {
tabBuilder: tabBuilder, tabBuilder: tabBuilder,
tabMenuBuilder: tabMenuBuilder, tabMenuBuilder: tabMenuBuilder,
maxLabelWidth: maxLabelWidth, maxLabelWidth: maxLabelWidth,
selectedTabBackgroundColor: selectedTabBackgroundColor ?? MyTheme.tabbar(context).selectedTabBackgroundColor, selectedTabBackgroundColor: selectedTabBackgroundColor ??
MyTheme.tabbar(context).selectedTabBackgroundColor,
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor, unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
); );
}).toList())); }).toList()));
@ -1121,7 +1140,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
dividerColor: dividerColor ?? this.dividerColor, dividerColor: dividerColor ?? this.dividerColor,
hoverColor: hoverColor ?? this.hoverColor, hoverColor: hoverColor ?? this.hoverColor,
closeHoverColor: closeHoverColor ?? this.closeHoverColor, closeHoverColor: closeHoverColor ?? this.closeHoverColor,
selectedTabBackgroundColor: selectedTabBackgroundColor ?? this.selectedTabBackgroundColor, selectedTabBackgroundColor:
selectedTabBackgroundColor ?? this.selectedTabBackgroundColor,
); );
} }
@ -1147,7 +1167,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
dividerColor: Color.lerp(dividerColor, other.dividerColor, t), dividerColor: Color.lerp(dividerColor, other.dividerColor, t),
hoverColor: Color.lerp(hoverColor, other.hoverColor, t), hoverColor: Color.lerp(hoverColor, other.hoverColor, t),
closeHoverColor: Color.lerp(closeHoverColor, other.closeHoverColor, t), closeHoverColor: Color.lerp(closeHoverColor, other.closeHoverColor, t),
selectedTabBackgroundColor: Color.lerp(selectedTabBackgroundColor, other.selectedTabBackgroundColor, t), selectedTabBackgroundColor: Color.lerp(
selectedTabBackgroundColor, other.selectedTabBackgroundColor, t),
); );
} }

View File

@ -1,7 +1,10 @@
import 'dart:async';
import 'package:dash_chat_2/dash_chat_2.dart'; import 'package:dash_chat_2/dash_chat_2.dart';
import 'package:draggable_float_widget/draggable_float_widget.dart'; 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.dart'; import 'package:get/get.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@ -27,16 +30,13 @@ 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;
BlockableOverlayState? _blockableOverlayState;
final Rx<VoiceCallStatus> _voiceCallStatus = Rx(VoiceCallStatus.notStarted); final Rx<VoiceCallStatus> _voiceCallStatus = Rx(VoiceCallStatus.notStarted);
Rx<VoiceCallStatus> get voiceCallStatus => _voiceCallStatus; Rx<VoiceCallStatus> get voiceCallStatus => _voiceCallStatus;
@ -58,6 +58,19 @@ class ChatModel with ChangeNotifier {
bool get isShowCMChatPage => _isShowCMChatPage; bool get isShowCMChatPage => _isShowCMChatPage;
void setOverlayState(BlockableOverlayState blockableOverlayState) {
_blockableOverlayState = blockableOverlayState;
_blockableOverlayState!.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 +87,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 +99,7 @@ class ChatModel with ChangeNotifier {
} }
} }
final overlayState = _getOverlayState(); final overlayState = _blockableOverlayState?.state;
if (overlayState == null) return; if (overlayState == null) return;
final overlay = OverlayEntry(builder: (context) { final overlay = OverlayEntry(builder: (context) {
@ -132,23 +131,35 @@ class ChatModel with ChangeNotifier {
} }
} }
showChatWindowOverlay() { showChatWindowOverlay({Offset? chatInitPos}) {
if (chatWindowOverlayEntry != null) return; if (chatWindowOverlayEntry != null) return;
final overlayState = _getOverlayState(); isWindowFocus.value = true;
_blockableOverlayState?.setMiddleBlocked(true);
final overlayState = _blockableOverlayState?.state;
if (overlayState == null) return; if (overlayState == null) return;
final overlay = OverlayEntry(builder: (context) { final overlay = OverlayEntry(builder: (context) {
return DraggableChatWindow( return Listener(
position: const Offset(20, 80), onPointerDown: (_) {
width: 250, if (!isWindowFocus.value) {
height: 350, isWindowFocus.value = true;
chatModel: this); _blockableOverlayState?.setMiddleBlocked(true);
}
},
child: DraggableChatWindow(
position: chatInitPos ?? Offset(20, 80),
width: 250,
height: 350,
chatModel: this));
}); });
overlayState.insert(overlay); overlayState.insert(overlay);
chatWindowOverlayEntry = overlay; chatWindowOverlayEntry = overlay;
requestChatInputFocus();
} }
hideChatWindowOverlay() { hideChatWindowOverlay() {
if (chatWindowOverlayEntry != null) { if (chatWindowOverlayEntry != null) {
_blockableOverlayState?.setMiddleBlocked(false);
chatWindowOverlayEntry!.remove(); chatWindowOverlayEntry!.remove();
chatWindowOverlayEntry = null; chatWindowOverlayEntry = null;
return; return;
@ -158,13 +169,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();
@ -194,6 +205,7 @@ class ChatModel with ChangeNotifier {
await windowManager.setSizeAlignment( await windowManager.setSizeAlignment(
kConnectionManagerWindowSize, Alignment.topRight); kConnectionManagerWindowSize, Alignment.topRight);
} else { } else {
requestChatInputFocus();
await windowManager.show(); await windowManager.show();
await windowManager.setSizeAlignment(Size(600, 400), Alignment.topRight); await windowManager.setSizeAlignment(Size(600, 400), Alignment.topRight);
_isShowCMChatPage = !_isShowCMChatPage; _isShowCMChatPage = !_isShowCMChatPage;
@ -291,7 +303,6 @@ class ChatModel with ChangeNotifier {
close() { close() {
hideChatIconOverlay(); hideChatIconOverlay();
hideChatWindowOverlay(); hideChatWindowOverlay();
_overlayState = null;
notifyListeners(); notifyListeners();
} }
@ -299,6 +310,14 @@ class ChatModel with ChangeNotifier {
_messages[clientModeID]?.clear(); _messages[clientModeID]?.clear();
} }
void requestChatInputFocus() {
Timer(Duration(milliseconds: 100), () {
if (inputNode.hasListeners && inputNode.canRequestFocus) {
inputNode.requestFocus();
}
});
}
void onVoiceCallWaiting() { void onVoiceCallWaiting() {
_voiceCallStatus.value = VoiceCallStatus.waitingForResponse; _voiceCallStatus.value = VoiceCallStatus.waitingForResponse;
} }

View File

@ -18,7 +18,6 @@ import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/user_model.dart'; import 'package:flutter_hbb/models/user_model.dart';
import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:image/image.dart' as img2; import 'package:image/image.dart' as img2;
import 'package:flutter_custom_cursor/cursor_manager.dart'; import 'package:flutter_custom_cursor/cursor_manager.dart';
@ -26,7 +25,6 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../common.dart'; import '../common.dart';
import '../common/shared_state.dart';
import '../utils/image.dart' as img; import '../utils/image.dart' as img;
import '../mobile/widgets/dialog.dart'; import '../mobile/widgets/dialog.dart';
import 'input_model.dart'; import 'input_model.dart';
@ -1393,13 +1391,13 @@ class FFI {
canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay); canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);
} }
bind.sessionClose(id: id); bind.sessionClose(id: id);
id = '';
imageModel.update(null); imageModel.update(null);
cursorModel.clear(); cursorModel.clear();
ffiModel.clear(); ffiModel.clear();
canvasModel.clear(); canvasModel.clear();
inputModel.resetModifiers(); inputModel.resetModifiers();
debugPrint('model $id closed'); debugPrint('model $id closed');
id = '';
} }
void setMethodCallHandler(FMethod callback) { void setMethodCallHandler(FMethod callback) {

View File

@ -16,7 +16,7 @@ final testClients = [
Client(3, false, false, "UserDDDDDDDDDDDd", "441123123", true, false, false) Client(3, false, false, "UserDDDDDDDDDDDd", "441123123", true, false, false)
]; ];
/// -t lib/cm_main.dart to test cm /// flutter run -d {platform} -t lib/cm_test.dart to test cm
void main(List<String> args) async { void main(List<String> args) async {
isTest = true; isTest = true;
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();