Merge pull request #1763 from Heap-Hop/opt/cm_and_file

opt desktop cm and file transfer UI
This commit is contained in:
RustDesk
2022-10-21 00:38:11 +08:00
committed by GitHub
10 changed files with 466 additions and 242 deletions

View File

@@ -3,6 +3,7 @@ import 'dart:io';
import 'dart:math';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
@@ -229,13 +230,13 @@ class _FileManagerPageState extends State<FileManagerPage>
final entries = fd.entries;
final sortIndex = (SortBy style) {
switch (style) {
case SortBy.Name:
case SortBy.name:
return 0;
case SortBy.Type:
case SortBy.type:
return 0;
case SortBy.Modified:
case SortBy.modified:
return 1;
case SortBy.Size:
case SortBy.size:
return 2;
}
}(model.getSortStyle(isLocal));
@@ -265,7 +266,7 @@ class _FileManagerPageState extends State<FileManagerPage>
translate("Name"),
).marginSymmetric(horizontal: 4),
onSort: (columnIndex, ascending) {
model.changeSortStyle(SortBy.Name,
model.changeSortStyle(SortBy.name,
isLocal: isLocal, ascending: ascending);
}),
DataColumn(
@@ -273,13 +274,13 @@ class _FileManagerPageState extends State<FileManagerPage>
translate("Modified"),
),
onSort: (columnIndex, ascending) {
model.changeSortStyle(SortBy.Modified,
model.changeSortStyle(SortBy.modified,
isLocal: isLocal, ascending: ascending);
}),
DataColumn(
label: Text(translate("Size")),
onSort: (columnIndex, ascending) {
model.changeSortStyle(SortBy.Size,
model.changeSortStyle(SortBy.size,
isLocal: isLocal, ascending: ascending);
}),
],
@@ -304,18 +305,25 @@ class _FileManagerPageState extends State<FileManagerPage>
waitDuration: Duration(milliseconds: 500),
message: entry.name,
child: Row(children: [
Icon(
entry.isFile
? Icons.feed_outlined
: entry.isDrive
? Icons.computer
: Icons.folder,
size: 20,
color: Theme.of(context)
.iconTheme
.color
?.withOpacity(0.7),
).marginSymmetric(horizontal: 2),
entry.isDrive
? Image(
image: iconHardDrive,
fit: BoxFit.scaleDown,
color: Theme.of(context)
.iconTheme
.color
?.withOpacity(0.7))
.paddingAll(4)
: Icon(
entry.isFile
? Icons.feed_outlined
: Icons.folder,
size: 20,
color: Theme.of(context)
.iconTheme
.color
?.withOpacity(0.7),
).marginSymmetric(horizontal: 2),
Expanded(
child: Text(entry.name,
overflow: TextOverflow.ellipsis))
@@ -546,13 +554,6 @@ class _FileManagerPageState extends State<FileManagerPage>
children: [
Row(
children: [
IconButton(
onPressed: () {
model.goHome(isLocal: isLocal);
},
icon: const Icon(Icons.home_outlined),
splashRadius: 20,
),
IconButton(
icon: const Icon(Icons.arrow_back),
splashRadius: 20,
@@ -649,6 +650,13 @@ class _FileManagerPageState extends State<FileManagerPage>
mainAxisAlignment:
isLocal ? MainAxisAlignment.start : MainAxisAlignment.end,
children: [
IconButton(
onPressed: () {
model.goHome(isLocal: isLocal);
},
icon: const Icon(Icons.home_outlined),
splashRadius: 20,
),
IconButton(
onPressed: () {
final name = TextEditingController();
@@ -786,14 +794,23 @@ class _FileManagerPageState extends State<FileManagerPage>
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: BreadCrumb(
items: items,
divider: Text("/",
style: TextStyle(color: Theme.of(context).hintColor))
.paddingSymmetric(horizontal: 2.0),
overflow: ScrollableOverflow(
controller: getBreadCrumbScrollController(isLocal)),
)),
child: Listener(
// handle mouse wheel
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
final sc = getBreadCrumbScrollController(isLocal);
sc.jumpTo(sc.offset + e.scrollDelta.dy / 4);
}
},
child: BreadCrumb(
items: items,
divider: Text("/",
style: TextStyle(
color: Theme.of(context).hintColor)),
overflow: ScrollableOverflow(
controller:
getBreadCrumbScrollController(isLocal)),
))),
ActionIcon(
message: "",
icon: Icons.arrow_drop_down,
@@ -833,15 +850,25 @@ class _FileManagerPageState extends State<FileManagerPage>
await model.fetchDirectory("/", isLocal, isLocal);
for (var entry in fd.entries) {
menuItems.add(MenuEntryButton(
childBuilder: (TextStyle? style) => Text(
entry.name,
style: style,
),
childBuilder: (TextStyle? style) =>
Row(children: [
Image(
image: iconHardDrive,
fit: BoxFit.scaleDown,
color: Theme.of(context)
.iconTheme
.color
?.withOpacity(0.7)),
SizedBox(width: 10),
Text(
entry.name,
style: style,
)
]),
proc: () {
openDirectory(entry.name, isLocal: isLocal);
},
dismissOnClicked: true));
menuItems.add(MenuEntryDivider());
}
} finally {
if (!isLocal) {
@@ -849,7 +876,7 @@ class _FileManagerPageState extends State<FileManagerPage>
}
}
}
menuItems.add(MenuEntryDivider());
mod_menu.showMenu(
context: context,
position: RelativeRect.fromLTRB(x, y, x, y),
@@ -879,10 +906,11 @@ class _FileManagerPageState extends State<FileManagerPage>
final breadCrumbList = List<BreadCrumbItem>.empty(growable: true);
breadCrumbList.addAll(list.asMap().entries.map((e) => BreadCrumbItem(
content: TextButton(
child: Text(e.value),
style:
ButtonStyle(minimumSize: MaterialStateProperty.all(Size(0, 0))),
onPressed: () => onPressed(list.sublist(0, e.key + 1))))));
child: Text(e.value),
style: ButtonStyle(
minimumSize: MaterialStateProperty.all(Size(0, 0))),
onPressed: () => onPressed(list.sublist(0, e.key + 1)))
.marginSymmetric(horizontal: 4))));
return breadCrumbList;
}

View File

@@ -124,6 +124,22 @@ class ConnectionManagerState extends State<ConnectionManager> {
showMinimize: true,
showClose: true,
controller: serverModel.tabController,
maxLabelWidth: 100,
tail: buildScrollJumper(),
selectedTabBackgroundColor:
Theme.of(context).hintColor.withOpacity(0.2),
tabBuilder: (key, icon, label, themeConf) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
Tooltip(
message: key,
waitDuration: Duration(seconds: 1),
child: label),
],
);
},
pageViewBuilder: (pageView) => Row(children: [
Expanded(child: pageView),
Consumer<ChatModel>(
@@ -158,6 +174,21 @@ class ConnectionManagerState extends State<ConnectionManager> {
),
);
}
Widget buildScrollJumper() {
final offstage = gFFI.serverModel.clients.length < 2;
final sc = gFFI.serverModel.tabController.state.value.scrollController;
return Offstage(
offstage: offstage,
child: Row(
children: [
ActionIcon(
icon: Icons.arrow_left, iconSize: 22, onTap: sc.backward),
ActionIcon(
icon: Icons.arrow_right, iconSize: 22, onTap: sc.forward),
],
));
}
}
Widget buildConnectionCard(Client client) {

View File

@@ -3,12 +3,14 @@ import 'dart:async';
import 'dart:math';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide TabBarTheme;
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
import 'package:scroll_pos/scroll_pos.dart';
import 'package:window_manager/window_manager.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -132,7 +134,8 @@ class DesktopTabController {
if (val.scrollController.hasClients &&
val.scrollController.canScroll &&
val.scrollController.itemCount > index) {
val.scrollController.scrollToItem(index, center: true, animate: true);
val.scrollController
.scrollToItem(index, center: false, animate: true);
}
}));
});
@@ -175,6 +178,9 @@ typedef TabBuilder = Widget Function(
String key, Widget icon, Widget label, TabThemeConf themeConf);
typedef LabelGetter = Rx<String> Function(String key);
/// [_lastClickTime], help to handle double click
int _lastClickTime = DateTime.now().millisecondsSinceEpoch;
class DesktopTab extends StatelessWidget {
final Function(String)? onTabClose;
final bool showTabBar;
@@ -188,10 +194,14 @@ class DesktopTab extends StatelessWidget {
final Future<bool> Function()? onWindowCloseButton;
final TabBuilder? tabBuilder;
final LabelGetter? labelGetter;
final double? maxLabelWidth;
final Color? selectedTabBackgroundColor;
final Color? unSelectedTabBackgroundColor;
final DesktopTabController controller;
Rx<DesktopTabState> get state => controller.state;
final isMaximized = false.obs;
final _scrollDebounce = Debouncer(delay: Duration(milliseconds: 50));
late final DesktopTabType tabType;
late final bool isMainWindow;
@@ -211,6 +221,9 @@ class DesktopTab extends StatelessWidget {
this.onWindowCloseButton,
this.tabBuilder,
this.labelGetter,
this.maxLabelWidth,
this.selectedTabBackgroundColor,
this.unSelectedTabBackgroundColor,
}) : super(key: key) {
tabType = controller.tabType;
isMainWindow =
@@ -292,46 +305,76 @@ class DesktopTab extends StatelessWidget {
Widget _buildBar() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Offstage(
offstage: !Platform.isMacOS,
child: const SizedBox(
width: 78,
)),
GestureDetector(
onDoubleTap: showMaximize
? () => toggleMaximize(isMainWindow)
.then((value) => isMaximized.value = value)
Expanded(
child: GestureDetector(
// custom double tap handler
onTap: showMaximize
? () {
final current = DateTime.now().millisecondsSinceEpoch;
final elapsed = current - _lastClickTime;
_lastClickTime = current;
if (elapsed < kDesktopDoubleClickTimeMilli) {
// onDoubleTap
toggleMaximize(isMainWindow)
.then((value) => isMaximized.value = value);
}
}
: null,
onPanStart: (_) => startDragging(isMainWindow),
child: Row(children: [
Offstage(
offstage: !showLogo,
child: SvgPicture.asset(
'assets/logo.svg',
width: 16,
height: 16,
)),
Offstage(
offstage: !showTitle,
child: const Text(
"RustDesk",
style: TextStyle(fontSize: 13),
).marginOnly(left: 2))
]).marginOnly(
left: 5,
right: 10,
)),
_ListView(
controller: controller,
onTabClose: onTabClose,
tabBuilder: tabBuilder,
labelGetter: labelGetter,
),
],
),
child: Row(
children: [
Offstage(
offstage: !Platform.isMacOS,
child: const SizedBox(
width: 78,
)),
Row(children: [
Offstage(
offstage: !showLogo,
child: SvgPicture.asset(
'assets/logo.svg',
width: 16,
height: 16,
)),
Offstage(
offstage: !showTitle,
child: const Text(
"RustDesk",
style: TextStyle(fontSize: 13),
).marginOnly(left: 2))
]).marginOnly(
left: 5,
right: 10,
),
Expanded(
child: Listener(
// handle mouse wheel
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
final sc =
controller.state.value.scrollController;
if (!sc.canScroll) return;
_scrollDebounce.call(() {
sc.animateTo(sc.offset + e.scrollDelta.dy,
duration: Duration(milliseconds: 200),
curve: Curves.ease);
});
}
},
child: _ListView(
controller: controller,
onTabClose: onTabClose,
tabBuilder: tabBuilder,
labelGetter: labelGetter,
maxLabelWidth: maxLabelWidth,
selectedTabBackgroundColor:
selectedTabBackgroundColor,
unSelectedTabBackgroundColor:
unSelectedTabBackgroundColor))),
],
))),
WindowActionPanel(
isMainWindow: isMainWindow,
tabType: tabType,
@@ -435,14 +478,9 @@ class WindowActionPanelState extends State<WindowActionPanel>
@override
Widget build(BuildContext context) {
return Expanded(
child: Row(
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
child: GestureDetector(
onDoubleTap: widget.showMaximize ? _toggleMaximize : null,
onPanStart: (_) => startDragging(widget.isMainWindow),
)),
Offstage(offstage: widget.tail == null, child: widget.tail),
Offstage(
offstage: !widget.showMinimize,
@@ -489,7 +527,7 @@ class WindowActionPanelState extends State<WindowActionPanel>
isClose: true,
)),
],
));
);
}
void _toggleMaximize() {
@@ -580,13 +618,15 @@ Future<bool> closeConfirmDialog() async {
return res == true;
}
// ignore: must_be_immutable
class _ListView extends StatelessWidget {
final DesktopTabController controller;
final Function(String key)? onTabClose;
final TabBuilder? tabBuilder;
final LabelGetter? labelGetter;
final double? maxLabelWidth;
final Color? selectedTabBackgroundColor;
final Color? unSelectedTabBackgroundColor;
Rx<DesktopTabState> get state => controller.state;
@@ -594,7 +634,10 @@ class _ListView extends StatelessWidget {
{required this.controller,
required this.onTabClose,
this.tabBuilder,
this.labelGetter});
this.labelGetter,
this.maxLabelWidth,
this.selectedTabBackgroundColor,
this.unSelectedTabBackgroundColor});
/// Check whether to show ListView
///
@@ -636,7 +679,7 @@ class _ListView extends StatelessWidget {
onSelected: () => controller.jumpTo(index),
tabBuilder: tabBuilder == null
? null
: (Widget icon, Widget labelWidget,
: (String key, Widget icon, Widget labelWidget,
TabThemeConf themeConf) {
return tabBuilder!(
tab.label,
@@ -645,6 +688,9 @@ class _ListView extends StatelessWidget {
themeConf,
);
},
maxLabelWidth: maxLabelWidth,
selectedTabBackgroundColor: selectedTabBackgroundColor,
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
);
}).toList()));
}
@@ -659,8 +705,10 @@ class _Tab extends StatefulWidget {
final int selected;
final Function() onClose;
final Function() onSelected;
final Widget Function(Widget icon, Widget label, TabThemeConf themeConf)?
tabBuilder;
final TabBuilder? tabBuilder;
final double? maxLabelWidth;
final Color? selectedTabBackgroundColor;
final Color? unSelectedTabBackgroundColor;
const _Tab({
Key? key,
@@ -673,6 +721,9 @@ class _Tab extends StatefulWidget {
required this.selected,
required this.onClose,
required this.onSelected,
this.maxLabelWidth,
this.selectedTabBackgroundColor,
this.unSelectedTabBackgroundColor,
}) : super(key: key);
@override
@@ -697,14 +748,17 @@ class _TabState extends State<_Tab> with RestorationMixin {
: MyTheme.tabbar(context).unSelectedTabIconColor,
).paddingOnly(right: 5));
final labelWidget = Obx(() {
return Text(
translate(widget.label.value),
textAlign: TextAlign.center,
style: TextStyle(
color: isSelected
? MyTheme.tabbar(context).selectedTextColor
: MyTheme.tabbar(context).unSelectedTextColor),
);
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
child: Text(
translate(widget.label.value),
textAlign: TextAlign.center,
style: TextStyle(
color: isSelected
? MyTheme.tabbar(context).selectedTextColor
: MyTheme.tabbar(context).unSelectedTextColor),
overflow: TextOverflow.ellipsis,
));
});
if (widget.tabBuilder == null) {
@@ -716,8 +770,8 @@ class _TabState extends State<_Tab> with RestorationMixin {
],
);
} else {
return widget.tabBuilder!(
icon, labelWidget, TabThemeConf(iconSize: _kIconSize));
return widget.tabBuilder!(widget.label.value, icon, labelWidget,
TabThemeConf(iconSize: _kIconSize));
}
}
@@ -734,32 +788,36 @@ class _TabState extends State<_Tab> with RestorationMixin {
restoreHover.value = value;
},
onTap: () => widget.onSelected(),
child: Row(
children: [
SizedBox(
height: _kTabBarHeight,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildTabContent(),
Obx((() => _CloseButton(
visiable: hover.value && widget.closable,
tabSelected: isSelected,
onClose: () => widget.onClose(),
)))
])).paddingSymmetric(horizontal: 10),
Offstage(
offstage: !showDivider,
child: VerticalDivider(
width: 1,
indent: _kDividerIndent,
endIndent: _kDividerIndent,
color: MyTheme.tabbar(context).dividerColor,
thickness: 1,
),
)
],
),
child: Container(
color: isSelected
? widget.selectedTabBackgroundColor
: widget.unSelectedTabBackgroundColor,
child: Row(
children: [
SizedBox(
height: _kTabBarHeight,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildTabContent(),
Obx((() => _CloseButton(
visiable: hover.value && widget.closable,
tabSelected: isSelected,
onClose: () => widget.onClose(),
)))
])).paddingSymmetric(horizontal: 10),
Offstage(
offstage: !showDivider,
child: VerticalDivider(
width: 1,
indent: _kDividerIndent,
endIndent: _kDividerIndent,
color: MyTheme.tabbar(context).dividerColor,
thickness: 1,
),
)
],
)),
),
);
}
@@ -807,7 +865,7 @@ class _CloseButton extends StatelessWidget {
}
class ActionIcon extends StatelessWidget {
final String message;
final String? message;
final IconData icon;
final Function() onTap;
final bool isClose;
@@ -815,7 +873,7 @@ class ActionIcon extends StatelessWidget {
final double boxSize;
const ActionIcon(
{Key? key,
required this.message,
this.message,
required this.icon,
required this.onTap,
this.isClose = false,
@@ -827,7 +885,7 @@ class ActionIcon extends StatelessWidget {
Widget build(BuildContext context) {
RxBool hover = false.obs;
return Obx(() => Tooltip(
message: translate(message),
message: message != null ? translate(message!) : "",
waitDuration: const Duration(seconds: 1),
child: InkWell(
hoverColor: isClose