mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-24 12:29:04 +08:00
Merge pull request #1944 from fufesou/flutter_desktop_tab_menu
Flutter desktop tab menu
This commit is contained in:
commit
42849e3759
@ -86,7 +86,7 @@ class IconFont {
|
|||||||
static const IconData add = IconData(0xe664, fontFamily: _family1);
|
static const IconData add = IconData(0xe664, fontFamily: _family1);
|
||||||
static const IconData menu = IconData(0xe628, fontFamily: _family1);
|
static const IconData menu = IconData(0xe628, fontFamily: _family1);
|
||||||
static const IconData search = IconData(0xe6a4, fontFamily: _family2);
|
static const IconData search = IconData(0xe6a4, fontFamily: _family2);
|
||||||
static const IconData round_close = IconData(0xe6ed, fontFamily: _family2);
|
static const IconData roundClose = IconData(0xe6ed, fontFamily: _family2);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||||
@ -1330,11 +1330,8 @@ Future<Map<String, String>> getHttpHeaders() async {
|
|||||||
|
|
||||||
// Simple wrapper of built-in types for refrence use.
|
// Simple wrapper of built-in types for refrence use.
|
||||||
class SimpleWrapper<T> {
|
class SimpleWrapper<T> {
|
||||||
T t;
|
T value;
|
||||||
SimpleWrapper(this.t);
|
SimpleWrapper(this.value);
|
||||||
|
|
||||||
T get value => t;
|
|
||||||
set value(T t) => this.t = t;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// call this to reload current window.
|
/// call this to reload current window.
|
||||||
|
@ -25,15 +25,24 @@ bool _isCustomCursorInited = false;
|
|||||||
final SimpleWrapper<bool> _firstEnterImage = SimpleWrapper(false);
|
final SimpleWrapper<bool> _firstEnterImage = SimpleWrapper(false);
|
||||||
|
|
||||||
class RemotePage extends StatefulWidget {
|
class RemotePage extends StatefulWidget {
|
||||||
const RemotePage({
|
RemotePage({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.id,
|
required this.id,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
|
final SimpleWrapper<State<RemotePage>?> _lastState = SimpleWrapper(null);
|
||||||
|
|
||||||
|
FFI get ffi => (_lastState.value! as _RemotePageState)._ffi;
|
||||||
|
RxBool get showMenubar =>
|
||||||
|
(_lastState.value! as _RemotePageState)._showMenubar;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RemotePage> createState() => _RemotePageState();
|
State<RemotePage> createState() {
|
||||||
|
final state = _RemotePageState();
|
||||||
|
_lastState.value = state;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RemotePageState extends State<RemotePage>
|
class _RemotePageState extends State<RemotePage>
|
||||||
@ -41,6 +50,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
String keyboardMode = "legacy";
|
String keyboardMode = "legacy";
|
||||||
final _cursorOverImage = false.obs;
|
final _cursorOverImage = false.obs;
|
||||||
|
final _showMenubar = false.obs;
|
||||||
late RxBool _showRemoteCursor;
|
late RxBool _showRemoteCursor;
|
||||||
late RxBool _remoteCursorMoved;
|
late RxBool _remoteCursorMoved;
|
||||||
late RxBool _keyboardEnabled;
|
late RxBool _keyboardEnabled;
|
||||||
@ -229,6 +239,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
paints.add(RemoteMenubar(
|
paints.add(RemoteMenubar(
|
||||||
id: widget.id,
|
id: widget.id,
|
||||||
ffi: _ffi,
|
ffi: _ffi,
|
||||||
|
show: _showMenubar,
|
||||||
onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Menubar = func,
|
onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Menubar = func,
|
||||||
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Menubar = null,
|
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Menubar = null,
|
||||||
));
|
));
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -9,12 +10,23 @@ import 'package:flutter_hbb/consts.dart';
|
|||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/remote_page.dart';
|
import 'package:flutter_hbb/desktop/pages/remote_page.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||||
|
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
|
||||||
|
as mod_menu;
|
||||||
|
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
|
||||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
|
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
|
|
||||||
|
class _MenuTheme {
|
||||||
|
static const Color commonColor = MyTheme.accent;
|
||||||
|
// kMinInteractiveDimension
|
||||||
|
static const double height = 20.0;
|
||||||
|
static const double dividerHeight = 12.0;
|
||||||
|
}
|
||||||
|
|
||||||
class ConnectionTabPage extends StatefulWidget {
|
class ConnectionTabPage extends StatefulWidget {
|
||||||
final Map<String, dynamic> params;
|
final Map<String, dynamic> params;
|
||||||
|
|
||||||
@ -123,7 +135,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
connectionType.secure.value == ConnectionType.strSecure
|
connectionType.secure.value == ConnectionType.strSecure
|
||||||
? 'Secure Connection'
|
? 'Secure Connection'
|
||||||
: 'Insecure Connection');
|
: 'Insecure Connection');
|
||||||
return Row(
|
final tab = Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
icon,
|
icon,
|
||||||
@ -138,6 +150,23 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
label,
|
label,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return Listener(
|
||||||
|
onPointerDown: (e) {
|
||||||
|
if (e.kind != ui.PointerDeviceKind.mouse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.buttons == 2) {
|
||||||
|
showRightMenu(
|
||||||
|
(CancelFunc cancelFunc) {
|
||||||
|
return _tabMenuBuilder(key, cancelFunc);
|
||||||
|
},
|
||||||
|
target: e.position,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: tab,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
@ -151,6 +180,146 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// to-do: some dup code to ../widgets/remote_menubar
|
||||||
|
Widget _tabMenuBuilder(String key, CancelFunc cancelFunc) {
|
||||||
|
final List<MenuEntryBase<String>> menu = [];
|
||||||
|
const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0);
|
||||||
|
final remotePage = tabController.state.value.tabs
|
||||||
|
.firstWhere((tab) => tab.key == key)
|
||||||
|
.page as RemotePage;
|
||||||
|
final ffi = remotePage.ffi;
|
||||||
|
final pi = ffi.ffiModel.pi;
|
||||||
|
final perms = ffi.ffiModel.permissions;
|
||||||
|
final showMenuBar = remotePage.showMenubar;
|
||||||
|
menu.addAll([
|
||||||
|
MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Close'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
tabController.closeBy(key);
|
||||||
|
cancelFunc();
|
||||||
|
},
|
||||||
|
padding: padding,
|
||||||
|
),
|
||||||
|
MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Obx(() => Text(
|
||||||
|
translate(showMenuBar.isTrue ? 'Hide Menubar' : 'Show Menubar'),
|
||||||
|
style: style,
|
||||||
|
)),
|
||||||
|
proc: () {
|
||||||
|
showMenuBar.value = !showMenuBar.value;
|
||||||
|
cancelFunc();
|
||||||
|
},
|
||||||
|
padding: padding,
|
||||||
|
),
|
||||||
|
MenuEntryDivider<String>(),
|
||||||
|
MenuEntryRadios<String>(
|
||||||
|
text: translate('Ratio'),
|
||||||
|
optionsGetter: () => [
|
||||||
|
MenuEntryRadioOption(
|
||||||
|
text: translate('Scale original'),
|
||||||
|
value: 'original',
|
||||||
|
dismissOnClicked: true,
|
||||||
|
),
|
||||||
|
MenuEntryRadioOption(
|
||||||
|
text: translate('Scale adaptive'),
|
||||||
|
value: 'adaptive',
|
||||||
|
dismissOnClicked: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
curOptionGetter: () async {
|
||||||
|
return await bind.sessionGetOption(id: key, arg: 'view-style') ??
|
||||||
|
'adaptive';
|
||||||
|
},
|
||||||
|
optionSetter: (String oldValue, String newValue) async {
|
||||||
|
await bind.sessionPeerOption(
|
||||||
|
id: key, name: "view-style", value: newValue);
|
||||||
|
ffi.canvasModel.updateViewStyle();
|
||||||
|
cancelFunc();
|
||||||
|
},
|
||||||
|
padding: padding,
|
||||||
|
),
|
||||||
|
MenuEntryDivider<String>(),
|
||||||
|
MenuEntryRadios<String>(
|
||||||
|
text: translate('Scroll Style'),
|
||||||
|
optionsGetter: () => [
|
||||||
|
MenuEntryRadioOption(
|
||||||
|
text: translate('ScrollAuto'),
|
||||||
|
value: 'scrollauto',
|
||||||
|
dismissOnClicked: true,
|
||||||
|
),
|
||||||
|
MenuEntryRadioOption(
|
||||||
|
text: translate('Scrollbar'),
|
||||||
|
value: 'scrollbar',
|
||||||
|
dismissOnClicked: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
curOptionGetter: () async {
|
||||||
|
return await bind.sessionGetOption(id: key, arg: 'scroll-style') ??
|
||||||
|
'';
|
||||||
|
},
|
||||||
|
optionSetter: (String oldValue, String newValue) async {
|
||||||
|
await bind.sessionPeerOption(
|
||||||
|
id: key, name: "scroll-style", value: newValue);
|
||||||
|
ffi.canvasModel.updateScrollStyle();
|
||||||
|
cancelFunc();
|
||||||
|
},
|
||||||
|
padding: padding,
|
||||||
|
dismissOnClicked: true,
|
||||||
|
),
|
||||||
|
MenuEntryDivider<String>(),
|
||||||
|
() {
|
||||||
|
final state = ShowRemoteCursorState.find(key);
|
||||||
|
return MenuEntrySwitch2<String>(
|
||||||
|
switchType: SwitchType.scheckbox,
|
||||||
|
text: translate('Show remote cursor'),
|
||||||
|
getter: () {
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
setter: (bool v) async {
|
||||||
|
state.value = v;
|
||||||
|
await bind.sessionToggleOption(
|
||||||
|
id: key, value: 'show-remote-cursor');
|
||||||
|
cancelFunc();
|
||||||
|
},
|
||||||
|
padding: padding,
|
||||||
|
);
|
||||||
|
}()
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (perms['keyboard'] != false) {
|
||||||
|
if (pi.platform == 'Linux' || pi.sasEnabled) {
|
||||||
|
menu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
'${translate("Insert")} Ctrl + Alt + Del',
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
bind.sessionCtrlAltDel(id: key);
|
||||||
|
cancelFunc();
|
||||||
|
},
|
||||||
|
padding: padding,
|
||||||
|
dismissOnClicked: true,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mod_menu.PopupMenu<String>(
|
||||||
|
items: menu
|
||||||
|
.map((entry) => entry.build(
|
||||||
|
context,
|
||||||
|
const MenuConfig(
|
||||||
|
commonColor: _MenuTheme.commonColor,
|
||||||
|
height: _MenuTheme.height,
|
||||||
|
dividerHeight: _MenuTheme.dividerHeight,
|
||||||
|
)))
|
||||||
|
.expand((i) => i)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void onRemoveId(String id) {
|
void onRemoveId(String id) {
|
||||||
if (tabController.state.value.tabs.isEmpty) {
|
if (tabController.state.value.tabs.isEmpty) {
|
||||||
WindowController.fromWindowId(windowId()).hide();
|
WindowController.fromWindowId(windowId()).hide();
|
||||||
|
@ -139,8 +139,7 @@ class _MenuItem extends SingleChildRenderObjectWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
required this.onLayout,
|
required this.onLayout,
|
||||||
required Widget? child,
|
required Widget? child,
|
||||||
}) : assert(onLayout != null),
|
}) : super(key: key, child: child);
|
||||||
super(key: key, child: child);
|
|
||||||
|
|
||||||
final ValueChanged<Size> onLayout;
|
final ValueChanged<Size> onLayout;
|
||||||
|
|
||||||
@ -157,9 +156,7 @@ class _MenuItem extends SingleChildRenderObjectWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RenderMenuItem extends RenderShiftedBox {
|
class _RenderMenuItem extends RenderShiftedBox {
|
||||||
_RenderMenuItem(this.onLayout, [RenderBox? child])
|
_RenderMenuItem(this.onLayout, [RenderBox? child]) : super(child);
|
||||||
: assert(onLayout != null),
|
|
||||||
super(child);
|
|
||||||
|
|
||||||
ValueChanged<Size> onLayout;
|
ValueChanged<Size> onLayout;
|
||||||
|
|
||||||
@ -240,9 +237,7 @@ class PopupMenuItem<T> extends PopupMenuEntry<T> {
|
|||||||
this.textStyle,
|
this.textStyle,
|
||||||
this.mouseCursor,
|
this.mouseCursor,
|
||||||
required this.child,
|
required this.child,
|
||||||
}) : assert(enabled != null),
|
}) : super(key: key);
|
||||||
assert(height != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
/// The value that will be returned by [showMenu] if this entry is selected.
|
/// The value that will be returned by [showMenu] if this entry is selected.
|
||||||
final T? value;
|
final T? value;
|
||||||
@ -382,11 +377,15 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
|
|||||||
child: Semantics(
|
child: Semantics(
|
||||||
enabled: widget.enabled,
|
enabled: widget.enabled,
|
||||||
button: true,
|
button: true,
|
||||||
child: InkWell(
|
// child: InkWell(
|
||||||
onTap: widget.enabled ? handleTap : null,
|
// onTap: widget.enabled ? handleTap : null,
|
||||||
canRequestFocus: widget.enabled,
|
// canRequestFocus: widget.enabled,
|
||||||
mouseCursor: _EffectiveMouseCursor(
|
// mouseCursor: _EffectiveMouseCursor(
|
||||||
widget.mouseCursor, popupMenuTheme.mouseCursor),
|
// widget.mouseCursor, popupMenuTheme.mouseCursor),
|
||||||
|
// child: item,
|
||||||
|
// ),
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: widget.enabled ? handleTap : null,
|
||||||
child: item,
|
child: item,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -471,8 +470,7 @@ class CheckedPopupMenuItem<T> extends PopupMenuItem<T> {
|
|||||||
EdgeInsets? padding,
|
EdgeInsets? padding,
|
||||||
double height = kMinInteractiveDimension,
|
double height = kMinInteractiveDimension,
|
||||||
Widget? child,
|
Widget? child,
|
||||||
}) : assert(checked != null),
|
}) : super(
|
||||||
super(
|
|
||||||
key: key,
|
key: key,
|
||||||
value: value,
|
value: value,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
@ -524,10 +522,11 @@ class _CheckedPopupMenuItemState<T>
|
|||||||
@override
|
@override
|
||||||
void handleTap() {
|
void handleTap() {
|
||||||
// This fades the checkmark in or out when tapped.
|
// This fades the checkmark in or out when tapped.
|
||||||
if (widget.checked)
|
if (widget.checked) {
|
||||||
_controller.reverse();
|
_controller.reverse();
|
||||||
else
|
} else {
|
||||||
_controller.forward();
|
_controller.forward();
|
||||||
|
}
|
||||||
super.handleTap();
|
super.handleTap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -699,7 +698,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
|||||||
final double buttonHeight = size.height - position.top - position.bottom;
|
final double buttonHeight = size.height - position.top - position.bottom;
|
||||||
// Find the ideal vertical position.
|
// Find the ideal vertical position.
|
||||||
double y = position.top;
|
double y = position.top;
|
||||||
if (selectedItemIndex != null && itemSizes != null) {
|
if (selectedItemIndex != null) {
|
||||||
double selectedItemOffset = _kMenuVerticalPadding;
|
double selectedItemOffset = _kMenuVerticalPadding;
|
||||||
for (int index = 0; index < selectedItemIndex!; index += 1) {
|
for (int index = 0; index < selectedItemIndex!; index += 1) {
|
||||||
selectedItemOffset += itemSizes[index]!.height;
|
selectedItemOffset += itemSizes[index]!.height;
|
||||||
@ -718,7 +717,6 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
|||||||
// x = position.left;
|
// x = position.left;
|
||||||
// } else {
|
// } else {
|
||||||
// Menu button is equidistant from both edges, so grow in reading direction.
|
// Menu button is equidistant from both edges, so grow in reading direction.
|
||||||
assert(textDirection != null);
|
|
||||||
switch (textDirection) {
|
switch (textDirection) {
|
||||||
case TextDirection.rtl:
|
case TextDirection.rtl:
|
||||||
x = size.width - position.right - childSize.width;
|
x = size.width - position.right - childSize.width;
|
||||||
@ -881,6 +879,103 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PopupMenu<T> extends StatelessWidget {
|
||||||
|
PopupMenu({
|
||||||
|
Key? key,
|
||||||
|
required this.items,
|
||||||
|
this.initialValue,
|
||||||
|
this.semanticLabel,
|
||||||
|
this.constraints,
|
||||||
|
}) : itemSizes = List<Size?>.filled(items.length, null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final List<PopupMenuEntry<T>> items;
|
||||||
|
final List<Size?> itemSizes;
|
||||||
|
final T? initialValue;
|
||||||
|
final String? semanticLabel;
|
||||||
|
final BoxConstraints? constraints;
|
||||||
|
|
||||||
|
Widget _buildMenu(BuildContext context) {
|
||||||
|
final List<Widget> children = <Widget>[];
|
||||||
|
for (int i = 0; i < items.length; i += 1) {
|
||||||
|
Widget item = items[i];
|
||||||
|
if (initialValue != null && items[i].represents(initialValue)) {
|
||||||
|
item = Container(
|
||||||
|
color: Theme.of(context).highlightColor,
|
||||||
|
child: item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
children.add(
|
||||||
|
_MenuItem(
|
||||||
|
onLayout: (Size size) {
|
||||||
|
itemSizes[i] = size;
|
||||||
|
},
|
||||||
|
child: item,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final child = ConstrainedBox(
|
||||||
|
constraints: constraints ??
|
||||||
|
const BoxConstraints(
|
||||||
|
minWidth: _kMenuMinWidth,
|
||||||
|
maxWidth: _kMenuMaxWidth,
|
||||||
|
),
|
||||||
|
child: IntrinsicWidth(
|
||||||
|
stepWidth: _kMenuWidthStep,
|
||||||
|
child: Semantics(
|
||||||
|
scopesRoute: true,
|
||||||
|
namesRoute: true,
|
||||||
|
explicitChildNodes: true,
|
||||||
|
label: semanticLabel,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: _kMenuVerticalPadding,
|
||||||
|
),
|
||||||
|
controller: ScrollController(),
|
||||||
|
child: ListBody(children: children),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||||
|
return Material(
|
||||||
|
shape: popupMenuTheme.shape,
|
||||||
|
color: popupMenuTheme.color,
|
||||||
|
type: MaterialType.card,
|
||||||
|
elevation: popupMenuTheme.elevation ?? 8.0,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
int? selectedItemIndex;
|
||||||
|
if (initialValue != null) {
|
||||||
|
for (int index = 0;
|
||||||
|
selectedItemIndex == null && index < items.length;
|
||||||
|
index += 1) {
|
||||||
|
if (items[index].represents(initialValue)) selectedItemIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
removeBottom: true,
|
||||||
|
removeLeft: true,
|
||||||
|
removeRight: true,
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return InheritedTheme.capture(from: context, to: context)
|
||||||
|
.wrap(_buildMenu(context));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Show a popup menu that contains the `items` at `position`.
|
/// Show a popup menu that contains the `items` at `position`.
|
||||||
///
|
///
|
||||||
/// `items` should be non-null and not empty.
|
/// `items` should be non-null and not empty.
|
||||||
@ -948,10 +1043,7 @@ Future<T?> showMenu<T>({
|
|||||||
bool useRootNavigator = false,
|
bool useRootNavigator = false,
|
||||||
BoxConstraints? constraints,
|
BoxConstraints? constraints,
|
||||||
}) {
|
}) {
|
||||||
assert(context != null);
|
assert(items.isNotEmpty);
|
||||||
assert(position != null);
|
|
||||||
assert(useRootNavigator != null);
|
|
||||||
assert(items != null && items.isNotEmpty);
|
|
||||||
assert(debugCheckHasMaterialLocalizations(context));
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
|
||||||
switch (Theme.of(context).platform) {
|
switch (Theme.of(context).platform) {
|
||||||
@ -1050,9 +1142,7 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||||||
this.enableFeedback,
|
this.enableFeedback,
|
||||||
this.constraints,
|
this.constraints,
|
||||||
this.position = PopupMenuPosition.over,
|
this.position = PopupMenuPosition.over,
|
||||||
}) : assert(itemBuilder != null),
|
}) : assert(
|
||||||
assert(enabled != null),
|
|
||||||
assert(
|
|
||||||
!(child != null && icon != null),
|
!(child != null && icon != null),
|
||||||
'You can only pass [child] or [icon], not both.',
|
'You can only pass [child] or [icon], not both.',
|
||||||
),
|
),
|
||||||
@ -1310,6 +1400,7 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
|||||||
// This MaterialStateProperty is passed along to the menu item's InkWell which
|
// This MaterialStateProperty is passed along to the menu item's InkWell which
|
||||||
// resolves the property against MaterialState.disabled, MaterialState.hovered,
|
// resolves the property against MaterialState.disabled, MaterialState.hovered,
|
||||||
// MaterialState.focused.
|
// MaterialState.focused.
|
||||||
|
// ignore: unused_element
|
||||||
class _EffectiveMouseCursor extends MaterialStateMouseCursor {
|
class _EffectiveMouseCursor extends MaterialStateMouseCursor {
|
||||||
const _EffectiveMouseCursor(this.widgetCursor, this.themeCursor);
|
const _EffectiveMouseCursor(this.widgetCursor, this.themeCursor);
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ class _MenubarTheme {
|
|||||||
class RemoteMenubar extends StatefulWidget {
|
class RemoteMenubar extends StatefulWidget {
|
||||||
final String id;
|
final String id;
|
||||||
final FFI ffi;
|
final FFI ffi;
|
||||||
|
final RxBool show;
|
||||||
final Function(Function(bool)) onEnterOrLeaveImageSetter;
|
final Function(Function(bool)) onEnterOrLeaveImageSetter;
|
||||||
final Function() onEnterOrLeaveImageCleaner;
|
final Function() onEnterOrLeaveImageCleaner;
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ class RemoteMenubar extends StatefulWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.ffi,
|
required this.ffi,
|
||||||
|
required this.show,
|
||||||
required this.onEnterOrLeaveImageSetter,
|
required this.onEnterOrLeaveImageSetter,
|
||||||
required this.onEnterOrLeaveImageCleaner,
|
required this.onEnterOrLeaveImageCleaner,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -47,7 +49,6 @@ class RemoteMenubar extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RemoteMenubarState extends State<RemoteMenubar> {
|
class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||||
final RxBool _show = false.obs;
|
|
||||||
final Rx<Color> _hideColor = Colors.white12.obs;
|
final Rx<Color> _hideColor = Colors.white12.obs;
|
||||||
final _rxHideReplay = rxdart.ReplaySubject<int>();
|
final _rxHideReplay = rxdart.ReplaySubject<int>();
|
||||||
final _pinMenubar = false.obs;
|
final _pinMenubar = false.obs;
|
||||||
@ -62,6 +63,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RxBool get show => widget.show;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -79,8 +82,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
.throttleTime(const Duration(milliseconds: 5000),
|
.throttleTime(const Duration(milliseconds: 5000),
|
||||||
trailing: true, leading: false)
|
trailing: true, leading: false)
|
||||||
.listen((int v) {
|
.listen((int v) {
|
||||||
if (_pinMenubar.isFalse && _show.isTrue && _isCursorOverImage) {
|
if (_pinMenubar.isFalse && show.isTrue && _isCursorOverImage) {
|
||||||
_show.value = false;
|
show.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -97,13 +100,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => _show.value ? _buildMenubar(context) : _buildShowHide(context)),
|
() => show.value ? _buildMenubar(context) : _buildShowHide(context)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildShowHide(BuildContext context) {
|
Widget _buildShowHide(BuildContext context) {
|
||||||
return Obx(() => Tooltip(
|
return Obx(() => Tooltip(
|
||||||
message: translate(_show.value ? "Hide Menubar" : "Show Menubar"),
|
message: translate(show.value ? 'Hide Menubar' : 'Show Menubar'),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 13,
|
height: 13,
|
||||||
@ -112,9 +115,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
_hideColor.value = v ? Colors.white60 : Colors.white24;
|
_hideColor.value = v ? Colors.white60 : Colors.white24;
|
||||||
},
|
},
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_show.value = !_show.value;
|
show.value = !show.value;
|
||||||
_hideColor.value = Colors.white24;
|
_hideColor.value = Colors.white24;
|
||||||
if (_show.isTrue) {
|
if (show.isTrue) {
|
||||||
_updateScreen();
|
_updateScreen();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -517,7 +520,6 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
displayMenu.add(MenuEntryDivider());
|
displayMenu.add(MenuEntryDivider());
|
||||||
|
|
||||||
if (perms['keyboard'] != false) {
|
if (perms['keyboard'] != false) {
|
||||||
if (pi.platform == 'Linux' || pi.sasEnabled) {
|
if (pi.platform == 'Linux' || pi.sasEnabled) {
|
||||||
displayMenu.add(MenuEntryButton<String>(
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
@ -15,6 +16,7 @@ import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
|||||||
import 'package:scroll_pos/scroll_pos.dart';
|
import 'package:scroll_pos/scroll_pos.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
|
|
||||||
import '../../utils/multi_window_manager.dart';
|
import '../../utils/multi_window_manager.dart';
|
||||||
|
|
||||||
@ -66,6 +68,26 @@ class DesktopTabState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CancelFunc showRightMenu(ToastBuilder builder,
|
||||||
|
{BuildContext? context, Offset? target}) {
|
||||||
|
return BotToast.showAttachedWidget(
|
||||||
|
target: target,
|
||||||
|
targetContext: context,
|
||||||
|
verticalOffset: 0,
|
||||||
|
horizontalOffset: 0,
|
||||||
|
duration: Duration(seconds: 4),
|
||||||
|
animationDuration: Duration(milliseconds: 0),
|
||||||
|
animationReverseDuration: Duration(milliseconds: 0),
|
||||||
|
preferDirection: PreferDirection.rightTop,
|
||||||
|
ignoreContentClick: false,
|
||||||
|
onlyOne: true,
|
||||||
|
allowClick: true,
|
||||||
|
enableSafeArea: true,
|
||||||
|
backgroundColor: Color(0x00000000),
|
||||||
|
attachedBuilder: builder,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class DesktopTabController {
|
class DesktopTabController {
|
||||||
final state = DesktopTabState().obs;
|
final state = DesktopTabState().obs;
|
||||||
final DesktopTabType tabType;
|
final DesktopTabType tabType;
|
||||||
@ -174,6 +196,7 @@ class TabThemeConf {
|
|||||||
|
|
||||||
typedef TabBuilder = Widget Function(
|
typedef TabBuilder = Widget Function(
|
||||||
String key, Widget icon, Widget label, TabThemeConf themeConf);
|
String key, Widget icon, Widget label, TabThemeConf themeConf);
|
||||||
|
typedef TabMenuBuilder = Widget Function(String key);
|
||||||
typedef LabelGetter = Rx<String> Function(String key);
|
typedef LabelGetter = Rx<String> Function(String key);
|
||||||
|
|
||||||
/// [_lastClickTime], help to handle double click
|
/// [_lastClickTime], help to handle double click
|
||||||
@ -187,6 +210,8 @@ class DesktopTab extends StatelessWidget {
|
|||||||
final bool showMaximize;
|
final bool showMaximize;
|
||||||
final bool showClose;
|
final bool showClose;
|
||||||
final Widget Function(Widget pageView)? pageViewBuilder;
|
final Widget Function(Widget pageView)? pageViewBuilder;
|
||||||
|
// Right click tab menu
|
||||||
|
final TabMenuBuilder? tabMenuBuilder;
|
||||||
final Widget? tail;
|
final Widget? tail;
|
||||||
final Future<bool> Function()? onWindowCloseButton;
|
final Future<bool> Function()? onWindowCloseButton;
|
||||||
final TabBuilder? tabBuilder;
|
final TabBuilder? tabBuilder;
|
||||||
@ -213,6 +238,7 @@ class DesktopTab extends StatelessWidget {
|
|||||||
this.showMaximize = true,
|
this.showMaximize = true,
|
||||||
this.showClose = true,
|
this.showClose = true,
|
||||||
this.pageViewBuilder,
|
this.pageViewBuilder,
|
||||||
|
this.tabMenuBuilder,
|
||||||
this.tail,
|
this.tail,
|
||||||
this.onWindowCloseButton,
|
this.onWindowCloseButton,
|
||||||
this.tabBuilder,
|
this.tabBuilder,
|
||||||
@ -362,6 +388,7 @@ class DesktopTab extends StatelessWidget {
|
|||||||
child: _ListView(
|
child: _ListView(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
tabBuilder: tabBuilder,
|
tabBuilder: tabBuilder,
|
||||||
|
tabMenuBuilder: tabMenuBuilder,
|
||||||
labelGetter: labelGetter,
|
labelGetter: labelGetter,
|
||||||
maxLabelWidth: maxLabelWidth,
|
maxLabelWidth: maxLabelWidth,
|
||||||
selectedTabBackgroundColor:
|
selectedTabBackgroundColor:
|
||||||
@ -619,6 +646,7 @@ class _ListView extends StatelessWidget {
|
|||||||
final DesktopTabController controller;
|
final DesktopTabController controller;
|
||||||
|
|
||||||
final TabBuilder? tabBuilder;
|
final TabBuilder? tabBuilder;
|
||||||
|
final TabMenuBuilder? tabMenuBuilder;
|
||||||
final LabelGetter? labelGetter;
|
final LabelGetter? labelGetter;
|
||||||
final double? maxLabelWidth;
|
final double? maxLabelWidth;
|
||||||
final Color? selectedTabBackgroundColor;
|
final Color? selectedTabBackgroundColor;
|
||||||
@ -626,13 +654,15 @@ class _ListView extends StatelessWidget {
|
|||||||
|
|
||||||
Rx<DesktopTabState> get state => controller.state;
|
Rx<DesktopTabState> get state => controller.state;
|
||||||
|
|
||||||
const _ListView(
|
const _ListView({
|
||||||
{required this.controller,
|
required this.controller,
|
||||||
this.tabBuilder,
|
this.tabBuilder,
|
||||||
this.labelGetter,
|
this.tabMenuBuilder,
|
||||||
this.maxLabelWidth,
|
this.labelGetter,
|
||||||
this.selectedTabBackgroundColor,
|
this.maxLabelWidth,
|
||||||
this.unSelectedTabBackgroundColor});
|
this.selectedTabBackgroundColor,
|
||||||
|
this.unSelectedTabBackgroundColor,
|
||||||
|
});
|
||||||
|
|
||||||
/// Check whether to show ListView
|
/// Check whether to show ListView
|
||||||
///
|
///
|
||||||
@ -678,6 +708,7 @@ class _ListView extends StatelessWidget {
|
|||||||
tab.onTap?.call();
|
tab.onTap?.call();
|
||||||
},
|
},
|
||||||
tabBuilder: tabBuilder,
|
tabBuilder: tabBuilder,
|
||||||
|
tabMenuBuilder: tabMenuBuilder,
|
||||||
maxLabelWidth: maxLabelWidth,
|
maxLabelWidth: maxLabelWidth,
|
||||||
selectedTabBackgroundColor: selectedTabBackgroundColor,
|
selectedTabBackgroundColor: selectedTabBackgroundColor,
|
||||||
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
|
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
|
||||||
@ -697,6 +728,7 @@ class _Tab extends StatefulWidget {
|
|||||||
final Function() onClose;
|
final Function() onClose;
|
||||||
final Function() onTap;
|
final Function() onTap;
|
||||||
final TabBuilder? tabBuilder;
|
final TabBuilder? tabBuilder;
|
||||||
|
final TabMenuBuilder? tabMenuBuilder;
|
||||||
final double? maxLabelWidth;
|
final double? maxLabelWidth;
|
||||||
final Color? selectedTabBackgroundColor;
|
final Color? selectedTabBackgroundColor;
|
||||||
final Color? unSelectedTabBackgroundColor;
|
final Color? unSelectedTabBackgroundColor;
|
||||||
@ -709,6 +741,7 @@ class _Tab extends StatefulWidget {
|
|||||||
this.selectedIcon,
|
this.selectedIcon,
|
||||||
this.unselectedIcon,
|
this.unselectedIcon,
|
||||||
this.tabBuilder,
|
this.tabBuilder,
|
||||||
|
this.tabMenuBuilder,
|
||||||
required this.closable,
|
required this.closable,
|
||||||
required this.selected,
|
required this.selected,
|
||||||
required this.onClose,
|
required this.onClose,
|
||||||
@ -753,18 +786,43 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (widget.tabBuilder == null) {
|
Widget getWidgetWithBuilder() {
|
||||||
return Row(
|
if (widget.tabBuilder == null) {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
return Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
icon,
|
||||||
|
labelWidget,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return widget.tabBuilder!(
|
||||||
|
widget.tabInfoKey,
|
||||||
icon,
|
icon,
|
||||||
labelWidget,
|
labelWidget,
|
||||||
],
|
TabThemeConf(iconSize: _kIconSize),
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
return widget.tabBuilder!(widget.tabInfoKey, icon, labelWidget,
|
|
||||||
TabThemeConf(iconSize: _kIconSize));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Listener(
|
||||||
|
onPointerDown: (e) {
|
||||||
|
if (e.kind != ui.PointerDeviceKind.mouse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.buttons == 2) {
|
||||||
|
if (widget.tabMenuBuilder != null) {
|
||||||
|
showRightMenu(
|
||||||
|
(cacel) {
|
||||||
|
return widget.tabMenuBuilder!(widget.tabInfoKey);
|
||||||
|
},
|
||||||
|
target: e.position,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: getWidgetWithBuilder(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -781,35 +839,36 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
|||||||
},
|
},
|
||||||
onTap: () => widget.onTap(),
|
onTap: () => widget.onTap(),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? widget.selectedTabBackgroundColor
|
? widget.selectedTabBackgroundColor
|
||||||
: widget.unSelectedTabBackgroundColor,
|
: widget.unSelectedTabBackgroundColor,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: _kTabBarHeight,
|
height: _kTabBarHeight,
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
_buildTabContent(),
|
_buildTabContent(),
|
||||||
Obx((() => _CloseButton(
|
Obx((() => _CloseButton(
|
||||||
visiable: hover.value && widget.closable,
|
visiable: hover.value && widget.closable,
|
||||||
tabSelected: isSelected,
|
tabSelected: isSelected,
|
||||||
onClose: () => widget.onClose(),
|
onClose: () => widget.onClose(),
|
||||||
)))
|
)))
|
||||||
])).paddingSymmetric(horizontal: 10),
|
])).paddingSymmetric(horizontal: 10),
|
||||||
Offstage(
|
Offstage(
|
||||||
offstage: !showDivider,
|
offstage: !showDivider,
|
||||||
child: VerticalDivider(
|
child: VerticalDivider(
|
||||||
width: 1,
|
width: 1,
|
||||||
indent: _kDividerIndent,
|
indent: _kDividerIndent,
|
||||||
endIndent: _kDividerIndent,
|
endIndent: _kDividerIndent,
|
||||||
color: MyTheme.tabbar(context).dividerColor,
|
color: MyTheme.tabbar(context).dividerColor,
|
||||||
thickness: 1,
|
thickness: 1,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
|
|
||||||
// import 'package:window_manager/window_manager.dart';
|
// import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
@ -53,15 +54,27 @@ Future<void> main(List<String> args) async {
|
|||||||
switch (wType) {
|
switch (wType) {
|
||||||
case WindowType.RemoteDesktop:
|
case WindowType.RemoteDesktop:
|
||||||
desktopType = DesktopType.remote;
|
desktopType = DesktopType.remote;
|
||||||
runRemoteScreen(argument);
|
runMultiWindow(
|
||||||
|
argument,
|
||||||
|
kAppTypeDesktopRemote,
|
||||||
|
'RustDesk - Remote Desktop',
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case WindowType.FileTransfer:
|
case WindowType.FileTransfer:
|
||||||
desktopType = DesktopType.fileTransfer;
|
desktopType = DesktopType.fileTransfer;
|
||||||
runFileTransferScreen(argument);
|
runMultiWindow(
|
||||||
|
argument,
|
||||||
|
kAppTypeDesktopFileTransfer,
|
||||||
|
'RustDesk - File Transfer',
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case WindowType.PortForward:
|
case WindowType.PortForward:
|
||||||
desktopType = DesktopType.portForward;
|
desktopType = DesktopType.portForward;
|
||||||
runPortForwardScreen(argument);
|
runMultiWindow(
|
||||||
|
argument,
|
||||||
|
kAppTypeDesktopPortForward,
|
||||||
|
'RustDesk - Port Forward',
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -120,84 +133,18 @@ void runMobileApp() async {
|
|||||||
runApp(App());
|
runApp(App());
|
||||||
}
|
}
|
||||||
|
|
||||||
void runRemoteScreen(Map<String, dynamic> argument) async {
|
void runMultiWindow(
|
||||||
await initEnv(kAppTypeDesktopRemote);
|
Map<String, dynamic> argument,
|
||||||
runApp(RefreshWrapper(
|
String appType,
|
||||||
builder: (context) => GetMaterialApp(
|
String title,
|
||||||
navigatorKey: globalKey,
|
) async {
|
||||||
debugShowCheckedModeBanner: false,
|
await initEnv(appType);
|
||||||
title: 'RustDesk - Remote Desktop',
|
_runApp(
|
||||||
theme: MyTheme.lightTheme,
|
title,
|
||||||
darkTheme: MyTheme.darkTheme,
|
DesktopRemoteScreen(
|
||||||
themeMode: MyTheme.currentThemeMode(),
|
params: argument,
|
||||||
home: DesktopRemoteScreen(
|
|
||||||
params: argument,
|
|
||||||
),
|
|
||||||
localizationsDelegates: const [
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
],
|
|
||||||
supportedLocales: supportedLocales,
|
|
||||||
navigatorObservers: const [
|
|
||||||
// FirebaseAnalyticsObserver(analytics: analytics),
|
|
||||||
],
|
|
||||||
builder: _keepScaleBuilder(),
|
|
||||||
),
|
),
|
||||||
));
|
MyTheme.currentThemeMode(),
|
||||||
}
|
|
||||||
|
|
||||||
void runFileTransferScreen(Map<String, dynamic> argument) async {
|
|
||||||
await initEnv(kAppTypeDesktopFileTransfer);
|
|
||||||
runApp(
|
|
||||||
RefreshWrapper(
|
|
||||||
builder: (context) => GetMaterialApp(
|
|
||||||
navigatorKey: globalKey,
|
|
||||||
debugShowCheckedModeBanner: false,
|
|
||||||
title: 'RustDesk - File Transfer',
|
|
||||||
theme: MyTheme.lightTheme,
|
|
||||||
darkTheme: MyTheme.darkTheme,
|
|
||||||
themeMode: MyTheme.currentThemeMode(),
|
|
||||||
home: DesktopFileTransferScreen(params: argument),
|
|
||||||
localizationsDelegates: const [
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
],
|
|
||||||
supportedLocales: supportedLocales,
|
|
||||||
navigatorObservers: const [
|
|
||||||
// FirebaseAnalyticsObserver(analytics: analytics),
|
|
||||||
],
|
|
||||||
builder: _keepScaleBuilder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void runPortForwardScreen(Map<String, dynamic> argument) async {
|
|
||||||
await initEnv(kAppTypeDesktopPortForward);
|
|
||||||
runApp(
|
|
||||||
RefreshWrapper(builder: (context) {
|
|
||||||
return GetMaterialApp(
|
|
||||||
navigatorKey: globalKey,
|
|
||||||
debugShowCheckedModeBanner: false,
|
|
||||||
title: 'RustDesk - Port Forward',
|
|
||||||
theme: MyTheme.lightTheme,
|
|
||||||
darkTheme: MyTheme.darkTheme,
|
|
||||||
themeMode: MyTheme.currentThemeMode(),
|
|
||||||
home: DesktopPortForwardScreen(params: argument),
|
|
||||||
localizationsDelegates: const [
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
],
|
|
||||||
supportedLocales: supportedLocales,
|
|
||||||
navigatorObservers: const [
|
|
||||||
// FirebaseAnalyticsObserver(analytics: analytics),
|
|
||||||
],
|
|
||||||
builder: _keepScaleBuilder(),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,21 +153,11 @@ void runConnectionManagerScreen() async {
|
|||||||
// initialize window
|
// initialize window
|
||||||
WindowOptions windowOptions =
|
WindowOptions windowOptions =
|
||||||
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
|
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
|
||||||
runApp(RefreshWrapper(builder: (context) {
|
_runApp(
|
||||||
return GetMaterialApp(
|
'',
|
||||||
debugShowCheckedModeBanner: false,
|
const DesktopServerPage(),
|
||||||
theme: MyTheme.lightTheme,
|
MyTheme.currentThemeMode(),
|
||||||
darkTheme: MyTheme.darkTheme,
|
);
|
||||||
themeMode: MyTheme.currentThemeMode(),
|
|
||||||
localizationsDelegates: const [
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
],
|
|
||||||
supportedLocales: supportedLocales,
|
|
||||||
home: const DesktopServerPage(),
|
|
||||||
builder: _keepScaleBuilder());
|
|
||||||
}));
|
|
||||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||||
await windowManager.show();
|
await windowManager.show();
|
||||||
// ensure initial window size to be changed
|
// ensure initial window size to be changed
|
||||||
@ -235,23 +172,44 @@ void runConnectionManagerScreen() async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _runApp(
|
||||||
|
String title,
|
||||||
|
Widget home,
|
||||||
|
ThemeMode themeMode,
|
||||||
|
) {
|
||||||
|
final botToastBuilder = BotToastInit();
|
||||||
|
runApp(RefreshWrapper(
|
||||||
|
builder: (context) => GetMaterialApp(
|
||||||
|
navigatorKey: globalKey,
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
title: title,
|
||||||
|
theme: MyTheme.lightTheme,
|
||||||
|
darkTheme: MyTheme.darkTheme,
|
||||||
|
themeMode: themeMode,
|
||||||
|
home: home,
|
||||||
|
localizationsDelegates: const [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: supportedLocales,
|
||||||
|
navigatorObservers: [
|
||||||
|
// FirebaseAnalyticsObserver(analytics: analytics),
|
||||||
|
BotToastNavigatorObserver(),
|
||||||
|
],
|
||||||
|
builder: (context, child) {
|
||||||
|
child = _keepScaleBuilder(context, child);
|
||||||
|
child = botToastBuilder(context, child);
|
||||||
|
return child;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
void runInstallPage() async {
|
void runInstallPage() async {
|
||||||
await windowManager.ensureInitialized();
|
await windowManager.ensureInitialized();
|
||||||
await initEnv(kAppTypeMain);
|
await initEnv(kAppTypeMain);
|
||||||
runApp(RefreshWrapper(
|
_runApp('', const InstallPage(), ThemeMode.light);
|
||||||
builder: (context) => GetMaterialApp(
|
|
||||||
debugShowCheckedModeBanner: false,
|
|
||||||
theme: MyTheme.lightTheme,
|
|
||||||
themeMode: ThemeMode.light,
|
|
||||||
localizationsDelegates: const [
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
],
|
|
||||||
supportedLocales: supportedLocales,
|
|
||||||
home: const InstallPage(),
|
|
||||||
builder: _keepScaleBuilder()),
|
|
||||||
));
|
|
||||||
windowManager.waitUntilReadyToShow(
|
windowManager.waitUntilReadyToShow(
|
||||||
WindowOptions(size: Size(800, 600), center: true), () async {
|
WindowOptions(size: Size(800, 600), center: true), () async {
|
||||||
windowManager.show();
|
windowManager.show();
|
||||||
@ -303,6 +261,7 @@ class _AppState extends State<App> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// final analytics = FirebaseAnalytics.instance;
|
// final analytics = FirebaseAnalytics.instance;
|
||||||
|
final botToastBuilder = BotToastInit();
|
||||||
return RefreshWrapper(builder: (context) {
|
return RefreshWrapper(builder: (context) {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
@ -325,15 +284,16 @@ class _AppState extends State<App> {
|
|||||||
: !isAndroid
|
: !isAndroid
|
||||||
? WebHomePage()
|
? WebHomePage()
|
||||||
: HomePage(),
|
: HomePage(),
|
||||||
navigatorObservers: const [
|
|
||||||
// FirebaseAnalyticsObserver(analytics: analytics),
|
|
||||||
],
|
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
GlobalWidgetsLocalizations.delegate,
|
GlobalWidgetsLocalizations.delegate,
|
||||||
GlobalCupertinoLocalizations.delegate,
|
GlobalCupertinoLocalizations.delegate,
|
||||||
],
|
],
|
||||||
supportedLocales: supportedLocales,
|
supportedLocales: supportedLocales,
|
||||||
|
navigatorObservers: [
|
||||||
|
// FirebaseAnalyticsObserver(analytics: analytics),
|
||||||
|
BotToastNavigatorObserver(),
|
||||||
|
],
|
||||||
builder: isAndroid
|
builder: isAndroid
|
||||||
? (context, child) => AccessibilityListener(
|
? (context, child) => AccessibilityListener(
|
||||||
child: MediaQuery(
|
child: MediaQuery(
|
||||||
@ -343,22 +303,24 @@ class _AppState extends State<App> {
|
|||||||
child: child ?? Container(),
|
child: child ?? Container(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: _keepScaleBuilder(),
|
: (context, child) {
|
||||||
|
child = _keepScaleBuilder(context, child);
|
||||||
|
child = botToastBuilder(context, child);
|
||||||
|
return child;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_keepScaleBuilder() {
|
Widget _keepScaleBuilder(BuildContext context, Widget? child) {
|
||||||
return (BuildContext context, Widget? child) {
|
return MediaQuery(
|
||||||
return MediaQuery(
|
data: MediaQuery.of(context).copyWith(
|
||||||
data: MediaQuery.of(context).copyWith(
|
textScaleFactor: 1.0,
|
||||||
textScaleFactor: 1.0,
|
),
|
||||||
),
|
child: child ?? Container(),
|
||||||
child: child ?? Container(),
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerEventHandler() {
|
_registerEventHandler() {
|
||||||
|
@ -38,8 +38,8 @@ class ChatModel with ChangeNotifier {
|
|||||||
firstName: "Me",
|
firstName: "Me",
|
||||||
);
|
);
|
||||||
|
|
||||||
late final Map<int, MessageBody> _messages = Map()
|
late final Map<int, MessageBody> _messages = {}..[clientModeID] =
|
||||||
..[clientModeID] = MessageBody(me, []);
|
MessageBody(me, []);
|
||||||
|
|
||||||
var _currentID = clientModeID;
|
var _currentID = clientModeID;
|
||||||
late bool _isShowChatPage = false;
|
late bool _isShowChatPage = false;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -11,7 +12,6 @@ import '../../models/platform_model.dart';
|
|||||||
import '../common.dart';
|
import '../common.dart';
|
||||||
import '../consts.dart';
|
import '../consts.dart';
|
||||||
import './state_model.dart';
|
import './state_model.dart';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
/// Mouse button enum.
|
/// Mouse button enum.
|
||||||
enum MouseButtons { left, right, wheel }
|
enum MouseButtons { left, right, wheel }
|
||||||
|
@ -10,7 +10,7 @@ import 'package:window_manager/window_manager.dart';
|
|||||||
|
|
||||||
import '../common.dart';
|
import '../common.dart';
|
||||||
import '../common/formatter/id_formatter.dart';
|
import '../common/formatter/id_formatter.dart';
|
||||||
import '../desktop/pages/server_page.dart' as Desktop;
|
import '../desktop/pages/server_page.dart' as desktop;
|
||||||
import '../desktop/widgets/tabbar_widget.dart';
|
import '../desktop/widgets/tabbar_widget.dart';
|
||||||
import '../mobile/pages/server_page.dart';
|
import '../mobile/pages/server_page.dart';
|
||||||
import 'model.dart';
|
import 'model.dart';
|
||||||
@ -261,7 +261,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Start the screen sharing service.
|
/// Start the screen sharing service.
|
||||||
Future<Null> startService() async {
|
Future<void> startService() async {
|
||||||
_isStart = true;
|
_isStart = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
parent.target?.ffiModel.updateEventListener("");
|
parent.target?.ffiModel.updateEventListener("");
|
||||||
@ -276,7 +276,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Stop the screen sharing service.
|
/// Stop the screen sharing service.
|
||||||
Future<Null> stopService() async {
|
Future<void> stopService() async {
|
||||||
_isStart = false;
|
_isStart = false;
|
||||||
closeAll();
|
closeAll();
|
||||||
await parent.target?.invokeMethod("stop_service");
|
await parent.target?.invokeMethod("stop_service");
|
||||||
@ -288,7 +288,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Null> initInput() async {
|
Future<void> initInput() async {
|
||||||
await parent.target?.invokeMethod("init_input");
|
await parent.target?.invokeMethod("init_input");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,7 +412,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
page: Desktop.buildConnectionCard(client)));
|
page: desktop.buildConnectionCard(client)));
|
||||||
Future.delayed(Duration.zero, () async {
|
Future.delayed(Duration.zero, () async {
|
||||||
window_on_top(null);
|
window_on_top(null);
|
||||||
});
|
});
|
||||||
@ -521,9 +521,9 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
closeAll() {
|
closeAll() {
|
||||||
_clients.forEach((client) {
|
for (var client in _clients) {
|
||||||
bind.cmCloseConnection(connId: client.id);
|
bind.cmCloseConnection(connId: client.id);
|
||||||
});
|
}
|
||||||
_clients.clear();
|
_clients.clear();
|
||||||
tabController.state.value.tabs.clear();
|
tabController.state.value.tabs.clear();
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ class PlatformFFI {
|
|||||||
|
|
||||||
static get localeName => window.navigator.language;
|
static get localeName => window.navigator.language;
|
||||||
|
|
||||||
static Future<Null> init(String _appType) async {
|
static Future<void> init(String _appType) async {
|
||||||
isWeb = true;
|
isWeb = true;
|
||||||
isWebDesktop = !context.callMethod('isMobile');
|
isWebDesktop = !context.callMethod('isMobile');
|
||||||
context.callMethod('init');
|
context.callMethod('init');
|
||||||
@ -57,13 +57,13 @@ class PlatformFFI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void stopDesktopWebListener() {
|
static void stopDesktopWebListener() {
|
||||||
mouseListeners.forEach((l) {
|
for (var ml in mouseListeners) {
|
||||||
l.cancel();
|
ml.cancel();
|
||||||
});
|
}
|
||||||
mouseListeners.clear();
|
mouseListeners.clear();
|
||||||
keyListeners.forEach((l) {
|
for (var kl in keyListeners) {
|
||||||
l.cancel();
|
kl.cancel();
|
||||||
});
|
}
|
||||||
keyListeners.clear();
|
keyListeners.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +102,7 @@ dependencies:
|
|||||||
ref: 5be5113d59c753989dbf1106241379e3fd4c9b18
|
ref: 5be5113d59c753989dbf1106241379e3fd4c9b18
|
||||||
path: ^1.8.1
|
path: ^1.8.1
|
||||||
auto_size_text: ^3.0.0
|
auto_size_text: ^3.0.0
|
||||||
|
bot_toast: ^4.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
icons_launcher: ^2.0.4
|
icons_launcher: ^2.0.4
|
||||||
|
Loading…
Reference in New Issue
Block a user