diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart index 9680d1bf3..97815a998 100644 --- a/flutter/lib/common/widgets/overlay.dart +++ b/flutter/lib/common/widgets/overlay.dart @@ -107,7 +107,7 @@ class DraggableChatWindow extends StatelessWidget { icon: IconFont.close, onTap: chatModel.hideChatWindowOverlay, isClose: true, - size: 32, + boxSize: 32, )) ], ), diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index f9521f4dc..f55619ddf 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -6,6 +6,7 @@ import 'package:desktop_drop/desktop_drop.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; +import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/models/file_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; @@ -455,7 +456,7 @@ class _FileManagerPageState extends State icon: const Icon(Icons.restart_alt_rounded)), ), IconButton( - icon: const Icon(Icons.delete), + icon: const Icon(Icons.delete_forever_outlined), splashRadius: 20, onPressed: () { model.jobTable.removeAt(index); @@ -742,32 +743,98 @@ class _FileManagerPageState extends State openDirectory(path, isLocal: isLocal); }); breadCrumbScrollToEnd(isLocal); + final locationBarKey = GlobalKey(debugLabel: "locationBarKey"); + return items.isEmpty ? Offstage() - : Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: BreadCrumb( - items: items, - divider: Text("/").paddingSymmetric(horizontal: 4.0), - overflow: ScrollableOverflow( - controller: getBreadCrumbScrollController(isLocal)), - )), - DropdownButton( - isDense: true, - underline: Offstage(), - items: [ - // TODO: favourite - DropdownMenuItem( - child: Text('/'), - value: '/', - ) - ], - onChanged: (path) { - if (path is String && path.isNotEmpty) { - openDirectory(path, isLocal: isLocal); - } - }) - ]); + : Row( + key: locationBarKey, + 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)), + )), + ActionIcon( + message: "", + icon: Icons.arrow_drop_down, + onTap: () async { + final renderBox = locationBarKey.currentContext + ?.findRenderObject() as RenderBox; + locationBarKey.currentContext?.size; + + final size = renderBox.size; + final offset = renderBox.localToGlobal(Offset.zero); + + final x = offset.dx; + final y = offset.dy + size.height + 1; + + final peerPlatform = (await bind.sessionGetPlatform( + id: _ffi.id, isRemote: !isLocal)) + .toLowerCase(); + final List menuItems; + if (peerPlatform == "windows") { + menuItems = []; + final loadingTag = + _ffi.dialogManager.showLoading("Waiting"); + try { + final fd = + await model.fetchDirectory("/", isLocal, false); + for (var entry in fd.entries) { + menuItems.add(MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + entry.name, + style: style, + ), + proc: () { + openDirectory(entry.name, isLocal: isLocal); + Get.back(); + })); + menuItems.add(MenuEntryDivider()); + } + } finally { + _ffi.dialogManager.dismissByTag(loadingTag); + } + } else { + menuItems = [ + MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + '/', + style: style, + ), + proc: () { + openDirectory('/', isLocal: isLocal); + Get.back(); + }), + MenuEntryDivider() + ]; + } + + mod_menu.showMenu( + context: context, + position: RelativeRect.fromLTRB(x, y, x, y), + elevation: 4, + items: menuItems + .map((e) => e.build( + context, + MenuConfig( + commonColor: + CustomPopupMenuTheme.commonColor, + height: CustomPopupMenuTheme.height, + dividerHeight: + CustomPopupMenuTheme.dividerHeight, + boxWidth: size.width))) + .expand((i) => i) + .toList()); + }, + iconSize: 20, + ) + ]); } List getPathBreadCrumbItems( diff --git a/flutter/lib/desktop/widgets/popup_menu.dart b/flutter/lib/desktop/widgets/popup_menu.dart index 5f06aebfe..20ab31ed9 100644 --- a/flutter/lib/desktop/widgets/popup_menu.dart +++ b/flutter/lib/desktop/widgets/popup_menu.dart @@ -99,12 +99,14 @@ class MenuConfig { final double height; final double dividerHeight; + final double? boxWidth; final Color commonColor; const MenuConfig( {required this.commonColor, this.height = kMinInteractiveDimension, - this.dividerHeight = 16.0}); + this.dividerHeight = 16.0, + this.boxWidth}); } abstract class MenuEntryBase { @@ -190,49 +192,51 @@ class MenuEntryRadios extends MenuEntryBase { return mod_menu.PopupMenuItem( padding: EdgeInsets.zero, height: conf.height, - child: TextButton( - child: Container( - padding: padding, - alignment: AlignmentDirectional.centerStart, - constraints: - BoxConstraints(minHeight: conf.height, maxHeight: conf.height), - child: Row( - children: [ - Text( - opt.text, - style: TextStyle( - color: Theme.of(context).textTheme.titleLarge?.color, - fontSize: MenuConfig.fontSize, - fontWeight: FontWeight.normal), + child: Container( + width: conf.boxWidth, + child: TextButton( + child: Container( + padding: padding, + alignment: AlignmentDirectional.centerStart, + constraints: BoxConstraints( + minHeight: conf.height, maxHeight: conf.height), + child: Row( + children: [ + Text( + opt.text, + style: TextStyle( + color: Theme.of(context).textTheme.titleLarge?.color, + fontSize: MenuConfig.fontSize, + fontWeight: FontWeight.normal), + ), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Transform.scale( + scale: MenuConfig.iconScale, + child: Obx(() => opt.value == curOption.value + ? IconButton( + padding: const EdgeInsets.fromLTRB( + 8.0, 0.0, 8.0, 0.0), + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + onPressed: () {}, + icon: Icon( + Icons.check, + color: conf.commonColor, + )) + : const SizedBox.shrink()), + ))), + ], ), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Transform.scale( - scale: MenuConfig.iconScale, - child: Obx(() => opt.value == curOption.value - ? IconButton( - padding: const EdgeInsets.fromLTRB( - 8.0, 0.0, 8.0, 0.0), - hoverColor: Colors.transparent, - focusColor: Colors.transparent, - onPressed: () {}, - icon: Icon( - Icons.check, - color: conf.commonColor, - )) - : const SizedBox.shrink()), - ))), - ], - ), - ), - onPressed: () { - if (opt.dismissOnClicked && Navigator.canPop(context)) { - Navigator.pop(context); - } - setOption(opt.value); - }, - ), + ), + onPressed: () { + if (opt.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); + } + setOption(opt.value); + }, + )), ); } @@ -285,48 +289,50 @@ class MenuEntrySubRadios extends MenuEntryBase { return mod_menu.PopupMenuItem( padding: EdgeInsets.zero, height: conf.height, - child: TextButton( - child: Container( - padding: padding, - alignment: AlignmentDirectional.centerStart, - constraints: - BoxConstraints(minHeight: conf.height, maxHeight: conf.height), - child: Row( - children: [ - Text( - opt.text, - style: TextStyle( - color: Theme.of(context).textTheme.titleLarge?.color, - fontSize: MenuConfig.fontSize, - fontWeight: FontWeight.normal), + child: Container( + width: conf.boxWidth, + child: TextButton( + child: Container( + padding: padding, + alignment: AlignmentDirectional.centerStart, + constraints: BoxConstraints( + minHeight: conf.height, maxHeight: conf.height), + child: Row( + children: [ + Text( + opt.text, + style: TextStyle( + color: Theme.of(context).textTheme.titleLarge?.color, + fontSize: MenuConfig.fontSize, + fontWeight: FontWeight.normal), + ), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Transform.scale( + scale: MenuConfig.iconScale, + child: Obx(() => opt.value == curOption.value + ? IconButton( + padding: EdgeInsets.zero, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + onPressed: () {}, + icon: Icon( + Icons.check, + color: conf.commonColor, + )) + : const SizedBox.shrink())), + )), + ], ), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Transform.scale( - scale: MenuConfig.iconScale, - child: Obx(() => opt.value == curOption.value - ? IconButton( - padding: EdgeInsets.zero, - hoverColor: Colors.transparent, - focusColor: Colors.transparent, - onPressed: () {}, - icon: Icon( - Icons.check, - color: conf.commonColor, - )) - : const SizedBox.shrink())), - )), - ], - ), - ), - onPressed: () { - if (opt.dismissOnClicked && Navigator.canPop(context)) { - Navigator.pop(context); - } - setOption(opt.value); - }, - ), + ), + onPressed: () { + if (opt.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); + } + setOption(opt.value); + }, + )), ); } @@ -401,55 +407,57 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { mod_menu.PopupMenuItem( padding: EdgeInsets.zero, height: conf.height, - child: TextButton( - child: Container( - padding: padding, - alignment: AlignmentDirectional.centerStart, - height: conf.height, - child: Row(children: [ - Obx(() => Text( - text, - style: textStyle!.value, - )), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Transform.scale( - scale: MenuConfig.iconScale, - child: Obx(() { - if (switchType == SwitchType.sswitch) { - return Switch( - value: curOption.value, - onChanged: (v) { - if (super.dismissOnClicked && - Navigator.canPop(context)) { - Navigator.pop(context); - } - setOption(v); - }, - ); - } else { - return Checkbox( - value: curOption.value, - onChanged: (v) { - if (super.dismissOnClicked && - Navigator.canPop(context)) { - Navigator.pop(context); - } - setOption(v); - }, - ); - } - })), - )) - ])), - onPressed: () { - if (super.dismissOnClicked && Navigator.canPop(context)) { - Navigator.pop(context); - } - setOption(!curOption.value); - }, - ), + child: Container( + width: conf.boxWidth, + child: TextButton( + child: Container( + padding: padding, + alignment: AlignmentDirectional.centerStart, + height: conf.height, + child: Row(children: [ + Obx(() => Text( + text, + style: textStyle!.value, + )), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Transform.scale( + scale: MenuConfig.iconScale, + child: Obx(() { + if (switchType == SwitchType.sswitch) { + return Switch( + value: curOption.value, + onChanged: (v) { + if (super.dismissOnClicked && + Navigator.canPop(context)) { + Navigator.pop(context); + } + setOption(v); + }, + ); + } else { + return Checkbox( + value: curOption.value, + onChanged: (v) { + if (super.dismissOnClicked && + Navigator.canPop(context)) { + Navigator.pop(context); + } + setOption(v); + }, + ); + } + })), + )) + ])), + onPressed: () { + if (super.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); + } + setOption(!curOption.value); + }, + )), ) ]; } @@ -606,7 +614,9 @@ class MenuEntryButton extends MenuEntryBase { fontSize: MenuConfig.fontSize, fontWeight: FontWeight.normal); super.enabled ??= true.obs; - return Obx(() => TextButton( + return Obx(() => Container( + width: conf.boxWidth, + child: TextButton( onPressed: super.enabled!.value ? () { if (super.dismissOnClicked && Navigator.canPop(context)) { @@ -623,7 +633,7 @@ class MenuEntryButton extends MenuEntryBase { child: childBuilder( super.enabled!.value ? enabledStyle : disabledStyle), ), - )); + ))); } @override diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 50c238a36..1d774143c 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -811,14 +811,16 @@ class ActionIcon extends StatelessWidget { final IconData icon; final Function() onTap; final bool isClose; - final double? size; + final double iconSize; + final double boxSize; const ActionIcon( {Key? key, required this.message, required this.icon, required this.onTap, - required this.isClose, - this.size}) + this.isClose = false, + this.iconSize = _kActionIconSize, + this.boxSize = _kTabBarHeight - 1}) : super(key: key); @override @@ -834,14 +836,14 @@ class ActionIcon extends StatelessWidget { onHover: (value) => hover.value = value, onTap: onTap, child: SizedBox( - height: size ?? (_kTabBarHeight - 1), - width: size ?? (_kTabBarHeight - 1), + height: boxSize, + width: boxSize, child: Icon( icon, color: hover.value && isClose ? Colors.white : MyTheme.tabbar(context).unSelectedIconColor, - size: _kActionIconSize, + size: iconSize, ), ), ), diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 38417f25f..5009e68a1 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -221,7 +221,7 @@ void runConnectionManagerScreen() async { windowManager.setOpacity(1) ]); // ensure - windowManager.setAlignment(Alignment.topRight); + windowManager.setAlignment(Alignment.topRight); }); } diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 8fb29bd41..d60858c6e 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -402,6 +402,10 @@ class FileModel extends ChangeNotifier { } } + Future fetchDirectory(path, isLocal, showHidden) async { + return await _fileFetcher.fetchDirectory(path, isLocal, showHidden); + } + void pushHistory(bool isLocal) { final history = isLocal ? localHistory : remoteHistory; final currPath = isLocal ? currentLocalDir.path : currentRemoteDir.path;