mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-05-12 15:18:38 -05:00
[client] refactor menu options
This commit is contained in:
@@ -36,7 +36,7 @@ class BookmarkRepository {
|
||||
}
|
||||
|
||||
Selectable<int> bookmarksCount(Iterable<String> ids) {
|
||||
return account.db.bookmarks.count(where: (b) => b.resourceId.isIn(ids));
|
||||
return account.db.bookmarks.count(where: (b) => b.resourceId.isIn(ids) & b.deleted.equals(false));
|
||||
}
|
||||
|
||||
Future<ApiResult> refresh() async {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:phylum/app_shortcuts.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/ui/explorer/explorer_view_controller.dart';
|
||||
import 'package:phylum/ui/explorer/menu_options.dart';
|
||||
import 'package:phylum/ui/menu/menu_option.dart';
|
||||
import 'package:phylum/ui/menu/option_groups.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ExplorerGestureHandler extends StatelessWidget {
|
||||
@@ -25,13 +27,16 @@ class ExplorerGestureHandler extends StatelessWidget {
|
||||
details.globalPosition.dx,
|
||||
details.globalPosition.dy,
|
||||
);
|
||||
final resources = [resource];
|
||||
final items = await buildPopupMenuItems(context.read<PhylumAccount>(), parentFolderOptions, resources);
|
||||
if (!context.mounted) return;
|
||||
final selection = await showMenu(
|
||||
context: context,
|
||||
position: position,
|
||||
items: buildExplorerPopupMenuItems(context, [resource]).toList(growable: false),
|
||||
items: items.toList(growable: false),
|
||||
);
|
||||
if (context.mounted && selection != null) {
|
||||
handleResourceOption(context, [resource], selection);
|
||||
handleOption(context, resources, selection);
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/ui/explorer/resource_icon_extension.dart';
|
||||
import 'package:phylum/ui/menu/option_groups.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'explorer_view_controller.dart';
|
||||
import 'menu_options.dart';
|
||||
import '../menu/bottom_sheet.dart';
|
||||
|
||||
class ResourceDetailsRow extends StatelessWidget {
|
||||
final Resource r;
|
||||
@@ -47,7 +48,7 @@ class ResourceDetailsRow extends StatelessWidget {
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
trailing: ResourceOptionsButton(r: r),
|
||||
trailing: IconButton(icon: Icon(Icons.adaptive.more), onPressed: () => showMenuOptionsBottomSheet(context, resourceOptions, [r])),
|
||||
shape: RoundedRectangleBorder(side: border, borderRadius: BorderRadius.circular(4.0)),
|
||||
selected: highlight,
|
||||
selectedColor: dim ? theme.colorScheme.primary.withOpacity(0.75) : null,
|
||||
|
||||
@@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:phylum/app_shortcuts.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/ui/explorer/explorer_view_controller.dart';
|
||||
import 'package:phylum/ui/explorer/resource_drop_and_drop.dart';
|
||||
import 'package:phylum/ui/explorer/menu_options.dart';
|
||||
import 'package:phylum/ui/menu/menu_option.dart';
|
||||
import 'package:phylum/ui/menu/option_groups.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ResourceItemGestureHandler extends StatefulWidget {
|
||||
@@ -78,13 +80,15 @@ class _ResourceItemGestureHandlerState extends State<ResourceItemGestureHandler>
|
||||
details.globalPosition.dy,
|
||||
);
|
||||
final resources = state.selected;
|
||||
final items = await buildPopupMenuItems(context.read<PhylumAccount>(), resourceOptions, resources);
|
||||
if (!context.mounted) return;
|
||||
final selection = await showMenu(
|
||||
context: context,
|
||||
position: position,
|
||||
items: buildChildPopupMenuItems(context, resources).toList(growable: false),
|
||||
items: items.toList(growable: false),
|
||||
);
|
||||
if (context.mounted && selection != null) {
|
||||
handleResourceOption(context, resources, selection);
|
||||
handleOption(context, resources, selection);
|
||||
}
|
||||
},
|
||||
onDoubleTap: () {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/ui/explorer/resource_icon_extension.dart';
|
||||
import 'package:phylum/ui/menu/menu_option.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'option_groups.dart';
|
||||
|
||||
void showMenuOptionsBottomSheet(BuildContext context, MenuOptionGroups options, Iterable<Resource> resources) async {
|
||||
final items = await buildPopupMenuItems(context.read<PhylumAccount>(), options, resources);
|
||||
if (!context.mounted) return;
|
||||
final selected = await showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
DecoratedBox(
|
||||
position: DecorationPosition.foreground,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: Divider.createBorderSide(context),
|
||||
),
|
||||
),
|
||||
child: resources.length == 1
|
||||
? ListTile(
|
||||
leading: resources.first.getIcon(),
|
||||
title: Text(
|
||||
resources.first.name,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
),
|
||||
)
|
||||
: ListTile(
|
||||
title: Text('${resources.length} items'),
|
||||
),
|
||||
),
|
||||
...items,
|
||||
],
|
||||
));
|
||||
|
||||
if (!context.mounted || selected == null) {
|
||||
return;
|
||||
}
|
||||
handleOption(context, resources, selected);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:phylum/app_shortcuts.dart';
|
||||
import 'package:phylum/integrations/download_manager.dart';
|
||||
@@ -8,7 +10,6 @@ import 'package:phylum/libphylum/actions/bookmark_remove_action.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/ui/common/responsive_dialog.dart';
|
||||
import 'package:phylum/ui/explorer/resource_icon_extension.dart';
|
||||
import 'package:phylum/ui/explorer/resource_info_view.dart';
|
||||
import 'package:phylum/util/dialogs.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -16,8 +17,8 @@ import 'package:provider/provider.dart';
|
||||
enum MenuOption {
|
||||
details(Icons.info_outline, 'Details', isSingle),
|
||||
download(Icons.download, 'Download', isFilesOnly),
|
||||
bookmarkAdd(Icons.bookmark_add_outlined, 'Add To Bookmarks', all),
|
||||
bookmarkRemove(Icons.bookmark_remove_outlined, 'Remove From Bookmarks', all),
|
||||
bookmarkAdd(Icons.bookmark_add_outlined, 'Add To Bookmarks', notAllBookmarked),
|
||||
bookmarkRemove(Icons.bookmark_remove, 'Remove From Bookmarks', allBookmarked),
|
||||
rename(Icons.drive_file_rename_outline, 'Rename', isSingle),
|
||||
move(Icons.drive_file_move_outlined, 'Move To', all),
|
||||
copy(Icons.copy, 'Copy To', all),
|
||||
@@ -33,79 +34,10 @@ enum MenuOption {
|
||||
|
||||
final IconData icon;
|
||||
final String text;
|
||||
final bool Function(PhylumAccount account, Iterable<Resource> resources) filter;
|
||||
final FutureOr<bool> Function(PhylumAccount account, Iterable<Resource> resources) filter;
|
||||
}
|
||||
|
||||
const resourceOptionsGroups = [
|
||||
[
|
||||
MenuOption.download,
|
||||
MenuOption.rename,
|
||||
MenuOption.move,
|
||||
MenuOption.copy,
|
||||
MenuOption.delete,
|
||||
],
|
||||
[
|
||||
MenuOption.bookmarkAdd,
|
||||
MenuOption.bookmarkRemove,
|
||||
MenuOption.permissions,
|
||||
MenuOption.publinks,
|
||||
],
|
||||
[
|
||||
MenuOption.details,
|
||||
],
|
||||
];
|
||||
|
||||
final explorerOptionsGroups = [
|
||||
[
|
||||
MenuOption.newFolder,
|
||||
MenuOption.uploadFiles,
|
||||
MenuOption.uploadFolders,
|
||||
],
|
||||
[
|
||||
MenuOption.bookmarkAdd,
|
||||
MenuOption.bookmarkRemove,
|
||||
MenuOption.permissions,
|
||||
MenuOption.publinks,
|
||||
],
|
||||
[
|
||||
MenuOption.details,
|
||||
]
|
||||
];
|
||||
|
||||
bool isSingle(PhylumAccount account, Iterable<Resource> resources) => resources.length == 1;
|
||||
|
||||
bool none(PhylumAccount account, Iterable<Resource> resources) => false;
|
||||
|
||||
bool all(PhylumAccount accout, Iterable<Resource> resources) => true;
|
||||
|
||||
bool isFilesOnly(PhylumAccount account, Iterable<Resource> resources) => resources.every((r) => !r.dir);
|
||||
|
||||
class ResourceOptionsButton extends StatelessWidget {
|
||||
final Resource r;
|
||||
|
||||
const ResourceOptionsButton({super.key, required this.r});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final resources = [r];
|
||||
return MediaQuery.sizeOf(context).width < 800
|
||||
? IconButton(icon: Icon(Icons.adaptive.more), onPressed: () => showResourceOptions(context, resources))
|
||||
: PopupMenuButton(
|
||||
itemBuilder: (context) => buildChildPopupMenuItems(context, resources).toList(growable: false),
|
||||
onSelected: (o) => handleResourceOption(context, resources, o),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showResourceOptions(BuildContext context, Iterable<Resource> resources) async {
|
||||
final option = await showModalBottomSheet(context: context, builder: (context) => ResourceOptionsList(resources: resources));
|
||||
if (!context.mounted || option == null) {
|
||||
return;
|
||||
}
|
||||
handleResourceOption(context, resources, option);
|
||||
}
|
||||
|
||||
void handleResourceOption(BuildContext context, Iterable<Resource> resources, MenuOption option) async {
|
||||
void handleOption(BuildContext context, Iterable<Resource> resources, MenuOption option) async {
|
||||
final account = context.read<PhylumAccount>();
|
||||
switch (option) {
|
||||
case MenuOption.rename:
|
||||
@@ -172,73 +104,15 @@ void handleResourceOption(BuildContext context, Iterable<Resource> resources, Me
|
||||
}
|
||||
}
|
||||
|
||||
class ResourceOptionsList extends StatelessWidget {
|
||||
final Iterable<Resource> resources;
|
||||
bool isSingle(PhylumAccount account, Iterable<Resource> resources) => resources.length == 1;
|
||||
|
||||
const ResourceOptionsList({super.key, required this.resources});
|
||||
bool none(PhylumAccount account, Iterable<Resource> resources) => false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
DecoratedBox(
|
||||
position: DecorationPosition.foreground,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: Divider.createBorderSide(context),
|
||||
),
|
||||
),
|
||||
child: resources.length == 1
|
||||
? ListTile(
|
||||
leading: resources.first.getIcon(),
|
||||
title: Text(
|
||||
resources.first.name,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
),
|
||||
)
|
||||
: ListTile(
|
||||
title: Text('${resources.length} items'),
|
||||
),
|
||||
),
|
||||
..._buildItems(context, resourceOptionsGroups, resources)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
bool all(PhylumAccount accout, Iterable<Resource> resources) => true;
|
||||
|
||||
Iterable<PopupMenuEntry> _buildItems(BuildContext context, Iterable<Iterable<MenuOption>> options, Iterable<Resource> resources) {
|
||||
final account = context.read<PhylumAccount>();
|
||||
return options
|
||||
.map((group) => group.map((o) => PopupMenuItem(
|
||||
value: o,
|
||||
enabled: o.filter(account, resources),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
leading: Icon(o.icon),
|
||||
title: Text(o.text),
|
||||
),
|
||||
)))
|
||||
.fold(
|
||||
<PopupMenuEntry>[],
|
||||
(acc, e) {
|
||||
if (e.isEmpty) {
|
||||
return acc;
|
||||
}
|
||||
if (acc.isEmpty) {
|
||||
return e;
|
||||
}
|
||||
return [...acc, PopupMenuDivider(), ...e];
|
||||
},
|
||||
);
|
||||
}
|
||||
bool isFilesOnly(PhylumAccount account, Iterable<Resource> resources) => resources.every((r) => !r.dir);
|
||||
|
||||
Iterable<PopupMenuEntry> buildChildPopupMenuItems(BuildContext context, Iterable<Resource> resources) {
|
||||
return _buildItems(context, resourceOptionsGroups, resources);
|
||||
}
|
||||
Future<bool> notAllBookmarked(PhylumAccount account, Iterable<Resource> resources) => allBookmarked(account, resources).then((b) => !b);
|
||||
|
||||
Iterable<PopupMenuEntry> buildExplorerPopupMenuItems(BuildContext context, Iterable<Resource> resources) {
|
||||
return _buildItems(context, explorerOptionsGroups, resources);
|
||||
}
|
||||
Future<bool> allBookmarked(PhylumAccount account, Iterable<Resource> resources) =>
|
||||
account.bookmarkRepository.bookmarksCount(resources.map((r) => r.id)).getSingle().then((c) => c == resources.length);
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
|
||||
import 'menu_option.dart';
|
||||
|
||||
typedef MenuOptionGroups = Iterable<Iterable<MenuOption>>;
|
||||
|
||||
const resourceOptions = [
|
||||
[
|
||||
MenuOption.download,
|
||||
MenuOption.rename,
|
||||
MenuOption.move,
|
||||
MenuOption.copy,
|
||||
MenuOption.delete,
|
||||
],
|
||||
[
|
||||
MenuOption.bookmarkAdd,
|
||||
MenuOption.bookmarkRemove,
|
||||
MenuOption.permissions,
|
||||
MenuOption.publinks,
|
||||
],
|
||||
[
|
||||
MenuOption.details,
|
||||
],
|
||||
];
|
||||
|
||||
final parentFolderOptions = [
|
||||
[
|
||||
MenuOption.newFolder,
|
||||
MenuOption.uploadFiles,
|
||||
MenuOption.uploadFolders,
|
||||
],
|
||||
[
|
||||
MenuOption.bookmarkAdd,
|
||||
MenuOption.bookmarkRemove,
|
||||
MenuOption.permissions,
|
||||
MenuOption.publinks,
|
||||
],
|
||||
[
|
||||
MenuOption.details,
|
||||
]
|
||||
];
|
||||
|
||||
Future<Iterable<PopupMenuEntry<MenuOption>>> buildPopupMenuItems(
|
||||
PhylumAccount account,
|
||||
MenuOptionGroups options,
|
||||
Iterable<Resource> resources, [
|
||||
bool hideDisabled = true,
|
||||
]) async {
|
||||
final availableOptions = await Future.wait(
|
||||
options.map(
|
||||
(group) async => Future.wait(
|
||||
group.map((o) async {
|
||||
final enabled = await o.filter(account, resources);
|
||||
return hideDisabled && !enabled
|
||||
? null
|
||||
: PopupMenuItem(
|
||||
value: o,
|
||||
enabled: enabled,
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
leading: Icon(o.icon),
|
||||
title: Text(o.text),
|
||||
),
|
||||
);
|
||||
}),
|
||||
).then((items) => items.whereType<PopupMenuEntry<MenuOption>>()),
|
||||
),
|
||||
);
|
||||
|
||||
final m = availableOptions.fold(
|
||||
<PopupMenuEntry<MenuOption>>[],
|
||||
(acc, e) {
|
||||
if (e.isEmpty) {
|
||||
return acc;
|
||||
}
|
||||
if (acc.isEmpty) {
|
||||
return e.toList(growable: false);
|
||||
}
|
||||
return [...acc, PopupMenuDivider(), ...e];
|
||||
},
|
||||
);
|
||||
return m;
|
||||
}
|
||||
Reference in New Issue
Block a user