selection: delete

This commit is contained in:
Thibault Deckers 2020-04-24 13:05:03 +09:00
parent 1751b7b3d7
commit a08c5a3369
8 changed files with 319 additions and 210 deletions

View file

@ -31,7 +31,7 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
groupFactor = groupFactor ?? GroupFactor.month, groupFactor = groupFactor ?? GroupFactor.month,
sortFactor = sortFactor ?? SortFactor.date { sortFactor = sortFactor ?? SortFactor.date {
_subscriptions.add(source.eventBus.on<EntryAddedEvent>().listen((e) => onEntryAdded())); _subscriptions.add(source.eventBus.on<EntryAddedEvent>().listen((e) => onEntryAdded()));
_subscriptions.add(source.eventBus.on<EntryRemovedEvent>().listen((e) => onEntryRemoved(e.entry))); _subscriptions.add(source.eventBus.on<EntryRemovedEvent>().listen((e) => onEntryRemoved(e.entries)));
_subscriptions.add(source.eventBus.on<CatalogMetadataChangedEvent>().listen((e) => onMetadataChanged())); _subscriptions.add(source.eventBus.on<CatalogMetadataChangedEvent>().listen((e) => onMetadataChanged()));
onEntryAdded(); onEntryAdded();
} }
@ -66,6 +66,7 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
int get entryCount => _filteredEntries.length; int get entryCount => _filteredEntries.length;
// sorted as displayed to the user, i.e. sorted then grouped, not an absolute order on all entries
List<ImageEntry> _sortedEntries; List<ImageEntry> _sortedEntries;
List<ImageEntry> get sortedEntries { List<ImageEntry> get sortedEntries {
@ -180,11 +181,12 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
_applyGroup(); _applyGroup();
} }
void onEntryRemoved(ImageEntry entry) { void onEntryRemoved(Iterable<ImageEntry> entries) {
// do not apply sort/group as section order change would surprise the user while browsing // do not apply sort/group as section order change would surprise the user while browsing
_filteredEntries.remove(entry); _filteredEntries.removeWhere(entries.contains);
_sortedEntries?.remove(entry); _sortedEntries?.removeWhere(entries.contains);
sections.forEach((key, entries) => entries.remove(entry)); sections.forEach((key, sectionEntries) => sectionEntries.removeWhere(entries.contains));
selection.removeAll(entries);
notifyListeners(); notifyListeners();
} }
@ -223,14 +225,14 @@ mixin CollectionSelectionMixin on CollectionActivityMixin {
Set<ImageEntry> get selection => _selection; Set<ImageEntry> get selection => _selection;
bool isSelected(List<ImageEntry> entries) => entries.every(selection.contains); bool isSelected(Iterable<ImageEntry> entries) => entries.every(selection.contains);
void addToSelection(List<ImageEntry> entries) { void addToSelection(Iterable<ImageEntry> entries) {
_selection.addAll(entries); _selection.addAll(entries);
selectionChangeNotifier.notifyListeners(); selectionChangeNotifier.notifyListeners();
} }
void removeFromSelection(List<ImageEntry> entries) { void removeFromSelection(Iterable<ImageEntry> entries) {
_selection.removeAll(entries); _selection.removeAll(entries);
selectionChangeNotifier.notifyListeners(); selectionChangeNotifier.notifyListeners();
} }

View file

@ -138,13 +138,9 @@ class CollectionSource {
eventBus.fire(const EntryAddedEvent()); eventBus.fire(const EntryAddedEvent());
} }
Future<bool> delete(ImageEntry entry) async { void removeEntries(Iterable<ImageEntry> entries) async {
final success = await entry.delete(); _rawEntries.remove(entries);
if (success) { eventBus.fire(EntryRemovedEvent(entries));
_rawEntries.remove(entry);
eventBus.fire(EntryRemovedEvent(entry));
}
return success;
} }
String getUniqueAlbumName(String album) { String getUniqueAlbumName(String album) {
@ -170,7 +166,7 @@ class EntryAddedEvent {
} }
class EntryRemovedEvent { class EntryRemovedEvent {
final ImageEntry entry; final Iterable<ImageEntry> entries;
const EntryRemovedEvent(this.entry); const EntryRemovedEvent(this.entries);
} }

View file

@ -1,19 +1,24 @@
import 'dart:async';
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/model/settings.dart'; import 'package:aves/model/settings.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/image_file_service.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/album/filter_bar.dart'; import 'package:aves/widgets/album/filter_bar.dart';
import 'package:aves/widgets/album/search/search_delegate.dart'; import 'package:aves/widgets/album/search/search_delegate.dart';
import 'package:aves/widgets/common/entry_actions.dart';
import 'package:aves/widgets/common/menu_row.dart'; import 'package:aves/widgets/common/menu_row.dart';
import 'package:aves/widgets/fullscreen/fullscreen_actions.dart';
import 'package:aves/widgets/stats/stats.dart'; import 'package:aves/widgets/stats/stats.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flushbar/flushbar.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:outline_material_icons/outline_material_icons.dart'; import 'package:outline_material_icons/outline_material_icons.dart';
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
class CollectionAppBar extends StatefulWidget { class CollectionAppBar extends StatefulWidget {
final ValueNotifier<double> appBarHeightNotifier; final ValueNotifier<double> appBarHeightNotifier;
@ -150,17 +155,16 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
onPressed: _goToSearch, onPressed: _goToSearch,
), ),
if (collection.isSelecting) if (collection.isSelecting)
AnimatedBuilder( ...EntryActions.selection.map((action) => AnimatedBuilder(
animation: collection.selectionChangeNotifier, animation: collection.selectionChangeNotifier,
builder: (context, child) { builder: (context, child) {
const action = FullscreenAction.share;
return IconButton( return IconButton(
icon: Icon(action.getIcon()), icon: Icon(action.getIcon()),
onPressed: collection.selection.isEmpty ? null : _shareSelection, onPressed: collection.selection.isEmpty ? null : () => _onSelectionActionSelected(action),
tooltip: action.getText(), tooltip: action.getText(),
); );
}, },
), )),
Builder( Builder(
builder: (context) => PopupMenuButton<CollectionAction>( builder: (context) => PopupMenuButton<CollectionAction>(
itemBuilder: (context) => [ itemBuilder: (context) => [
@ -177,7 +181,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
), ),
], ],
], ],
onSelected: _onActionSelected, onSelected: _onCollectionActionSelected,
), ),
), ),
]; ];
@ -221,7 +225,20 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
: []; : [];
} }
void _onActionSelected(CollectionAction action) async { void _onActivityChange() {
if (collection.isSelecting) {
_browseToSelectAnimation.forward();
} else {
_browseToSelectAnimation.reverse();
_searchFieldController.clear();
}
}
void _updateHeight() {
widget.appBarHeightNotifier.value = kToolbarHeight + (hasFilters ? FilterBar.preferredHeight : 0);
}
void _onCollectionActionSelected(CollectionAction action) async {
// wait for the popup menu to hide before proceeding with the action // wait for the popup menu to hide before proceeding with the action
await Future.delayed(Constants.popupMenuTransitionDuration); await Future.delayed(Constants.popupMenuTransitionDuration);
switch (action) { switch (action) {
@ -277,22 +294,108 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
); );
} }
void _onSelectionActionSelected(EntryAction action) {
switch (action) {
case EntryAction.share:
_shareSelection();
break;
case EntryAction.delete:
_deleteSelection();
break;
default:
break;
}
}
void _shareSelection() { void _shareSelection() {
final urisByMimeType = groupBy<ImageEntry, String>(collection.selection, (e) => e.mimeType).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList())); final urisByMimeType = groupBy<ImageEntry, String>(collection.selection, (e) => e.mimeType).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList()));
AndroidAppService.share(urisByMimeType); AndroidAppService.share(urisByMimeType);
} }
void _onActivityChange() { void _deleteSelection() {
if (collection.isSelecting) { final selection = collection.selection.toList();
_browseToSelectAnimation.forward(); _showOpReport(
} else { selection: selection,
_browseToSelectAnimation.reverse(); opStream: ImageFileService.delete(selection),
_searchFieldController.clear(); onDone: (processed) {
final deletedUris = processed.where((e) => e.success).map((e) => e.uri);
final deletedCount = deletedUris.length;
final selectionCount = selection.length;
if (deletedCount < selectionCount) {
_showFeedback(context, 'Failed to delete ${selectionCount - deletedCount} items');
} }
if (deletedCount > 0) {
collection.source.removeEntries(selection.where((e) => deletedUris.contains(e.uri)));
}
collection.browse();
},
);
} }
void _updateHeight() { // selection action report overlay
widget.appBarHeightNotifier.value = kToolbarHeight + (hasFilters ? FilterBar.preferredHeight : 0);
OverlayEntry _opReportOverlayEntry;
static const _overlayAnimationDuration = Duration(milliseconds: 300);
void _showOpReport({
@required List<ImageEntry> selection,
@required Stream<ImageOpEvent> opStream,
@required void Function(Set<ImageOpEvent> processed) onDone,
}) {
final processed = <ImageOpEvent>{};
_opReportOverlayEntry = OverlayEntry(
builder: (context) {
return StreamBuilder<ImageOpEvent>(
stream: opStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
processed.add(snapshot.data);
}
Widget child = const SizedBox.shrink();
if (snapshot.hasError || snapshot.connectionState == ConnectionState.done) {
_hideOpReportOverlay().then((_) => onDone(processed));
} else if (snapshot.connectionState == ConnectionState.active) {
final percent = processed.length.toDouble() / selection.length;
child = CircularPercentIndicator(
percent: percent,
lineWidth: 16,
radius: 160,
backgroundColor: Colors.white24,
progressColor: Theme.of(context).accentColor,
animation: true,
center: Text(NumberFormat.percentPattern().format(percent)),
animateFromLastPercent: true,
);
}
return AnimatedSwitcher(
duration: _overlayAnimationDuration,
child: child,
);
});
},
);
Overlay.of(context).insert(_opReportOverlayEntry);
}
Future<void> _hideOpReportOverlay() async {
await Future.delayed(_overlayAnimationDuration);
_opReportOverlayEntry.remove();
_opReportOverlayEntry = null;
}
void _showFeedback(BuildContext context, String message) {
Flushbar(
message: message,
margin: const EdgeInsets.all(8),
borderRadius: 8,
borderColor: Colors.white30,
borderWidth: 0.5,
duration: const Duration(seconds: 2),
flushbarPosition: FlushbarPosition.TOP,
animationDuration: const Duration(milliseconds: 600),
).show(context);
} }
} }

View file

@ -4,9 +4,9 @@ import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/image_file_service.dart'; import 'package:aves/services/image_file_service.dart';
import 'package:aves/widgets/common/entry_actions.dart';
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
import 'package:aves/widgets/fullscreen/debug.dart'; import 'package:aves/widgets/fullscreen/debug.dart';
import 'package:aves/widgets/fullscreen/fullscreen_actions.dart';
import 'package:flushbar/flushbar.dart'; import 'package:flushbar/flushbar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@ -15,58 +15,58 @@ import 'package:pdf/widgets.dart' as pdf;
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
import 'package:printing/printing.dart'; import 'package:printing/printing.dart';
class FullscreenActionDelegate { class EntryActionDelegate {
final CollectionLens collection; final CollectionLens collection;
final VoidCallback showInfo; final VoidCallback showInfo;
FullscreenActionDelegate({ EntryActionDelegate({
@required this.collection, @required this.collection,
@required this.showInfo, @required this.showInfo,
}); });
bool get hasCollection => collection != null; bool get hasCollection => collection != null;
void onActionSelected(BuildContext context, ImageEntry entry, FullscreenAction action) { void onActionSelected(BuildContext context, ImageEntry entry, EntryAction action) {
switch (action) { switch (action) {
case FullscreenAction.toggleFavourite: case EntryAction.toggleFavourite:
entry.toggleFavourite(); entry.toggleFavourite();
break; break;
case FullscreenAction.delete: case EntryAction.delete:
_showDeleteDialog(context, entry); _showDeleteDialog(context, entry);
break; break;
case FullscreenAction.edit: case EntryAction.edit:
AndroidAppService.edit(entry.uri, entry.mimeType); AndroidAppService.edit(entry.uri, entry.mimeType);
break; break;
case FullscreenAction.info: case EntryAction.info:
showInfo(); showInfo();
break; break;
case FullscreenAction.rename: case EntryAction.rename:
_showRenameDialog(context, entry); _showRenameDialog(context, entry);
break; break;
case FullscreenAction.open: case EntryAction.open:
AndroidAppService.open(entry.uri, entry.mimeTypeAnySubtype); AndroidAppService.open(entry.uri, entry.mimeTypeAnySubtype);
break; break;
case FullscreenAction.openMap: case EntryAction.openMap:
AndroidAppService.openMap(entry.geoUri); AndroidAppService.openMap(entry.geoUri);
break; break;
case FullscreenAction.print: case EntryAction.print:
_print(entry); _print(entry);
break; break;
case FullscreenAction.rotateCCW: case EntryAction.rotateCCW:
_rotate(context, entry, clockwise: false); _rotate(context, entry, clockwise: false);
break; break;
case FullscreenAction.rotateCW: case EntryAction.rotateCW:
_rotate(context, entry, clockwise: true); _rotate(context, entry, clockwise: true);
break; break;
case FullscreenAction.setAs: case EntryAction.setAs:
AndroidAppService.setAs(entry.uri, entry.mimeType); AndroidAppService.setAs(entry.uri, entry.mimeType);
break; break;
case FullscreenAction.share: case EntryAction.share:
AndroidAppService.share({ AndroidAppService.share({
entry.mimeType: [entry.uri] entry.mimeType: [entry.uri]
}); });
break; break;
case FullscreenAction.debug: case EntryAction.debug:
_goToDebug(context, entry); _goToDebug(context, entry);
break; break;
} }
@ -146,13 +146,16 @@ class FullscreenActionDelegate {
}, },
); );
if (confirmed == null || !confirmed) return; if (confirmed == null || !confirmed) return;
if (hasCollection) { if (!await entry.delete()) {
if (!await collection.source.delete(entry)) {
_showFeedback(context, 'Failed'); _showFeedback(context, 'Failed');
} else if (collection.sortedEntries.isEmpty) { } else if (hasCollection) {
// update collection
collection.source.removeEntries([entry]);
if (collection.sortedEntries.isEmpty) {
Navigator.pop(context); Navigator.pop(context);
} }
} else if (await entry.delete()) { } else {
// leave viewer
exit(0); exit(0);
} }
} }

View file

@ -0,0 +1,99 @@
import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart';
import 'package:outline_material_icons/outline_material_icons.dart';
enum EntryAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share, toggleFavourite, debug }
class EntryActions {
static const selection = [
EntryAction.share,
EntryAction.delete,
];
static const inApp = [
EntryAction.info,
EntryAction.toggleFavourite,
EntryAction.share,
EntryAction.delete,
EntryAction.rename,
EntryAction.rotateCCW,
EntryAction.rotateCW,
EntryAction.print,
];
static const externalApp = [
EntryAction.edit,
EntryAction.open,
EntryAction.setAs,
EntryAction.openMap,
];
}
extension ExtraEntryAction on EntryAction {
String getText() {
switch (this) {
// in app actions
case EntryAction.toggleFavourite:
// different data depending on toggle state
return null;
case EntryAction.delete:
return 'Delete';
case EntryAction.info:
return 'Info';
case EntryAction.rename:
return 'Rename';
case EntryAction.rotateCCW:
return 'Rotate left';
case EntryAction.rotateCW:
return 'Rotate right';
case EntryAction.print:
return 'Print';
case EntryAction.share:
return 'Share';
// external app actions
case EntryAction.edit:
return 'Edit with…';
case EntryAction.open:
return 'Open with…';
case EntryAction.setAs:
return 'Set as…';
case EntryAction.openMap:
return 'Show on map…';
case EntryAction.debug:
return 'Debug';
}
return null;
}
IconData getIcon() {
switch (this) {
// in app actions
case EntryAction.toggleFavourite:
// different data depending on toggle state
return null;
case EntryAction.delete:
return AIcons.delete;
case EntryAction.info:
return OMIcons.info;
case EntryAction.rename:
return OMIcons.title;
case EntryAction.rotateCCW:
return AIcons.rotateLeft;
case EntryAction.rotateCW:
return AIcons.rotateRight;
case EntryAction.print:
return AIcons.print;
case EntryAction.share:
return AIcons.share;
// external app actions
case EntryAction.edit:
case EntryAction.open:
case EntryAction.setAs:
case EntryAction.openMap:
return null;
case EntryAction.debug:
return OMIcons.whatshot;
}
return null;
}
}

View file

@ -1,94 +0,0 @@
import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart';
import 'package:outline_material_icons/outline_material_icons.dart';
enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share, toggleFavourite, debug }
class FullscreenActions {
static const inApp = [
FullscreenAction.info,
FullscreenAction.toggleFavourite,
FullscreenAction.share,
FullscreenAction.delete,
FullscreenAction.rename,
FullscreenAction.rotateCCW,
FullscreenAction.rotateCW,
FullscreenAction.print,
];
static const externalApp = [
FullscreenAction.edit,
FullscreenAction.open,
FullscreenAction.setAs,
FullscreenAction.openMap,
];
}
extension ExtraFullscreenAction on FullscreenAction {
String getText() {
switch (this) {
// in app actions
case FullscreenAction.toggleFavourite:
// different data depending on toggle state
return null;
case FullscreenAction.delete:
return 'Delete';
case FullscreenAction.info:
return 'Info';
case FullscreenAction.rename:
return 'Rename';
case FullscreenAction.rotateCCW:
return 'Rotate left';
case FullscreenAction.rotateCW:
return 'Rotate right';
case FullscreenAction.print:
return 'Print';
case FullscreenAction.share:
return 'Share';
// external app actions
case FullscreenAction.edit:
return 'Edit with…';
case FullscreenAction.open:
return 'Open with…';
case FullscreenAction.setAs:
return 'Set as…';
case FullscreenAction.openMap:
return 'Show on map…';
case FullscreenAction.debug:
return 'Debug';
}
return null;
}
IconData getIcon() {
switch (this) {
// in app actions
case FullscreenAction.toggleFavourite:
// different data depending on toggle state
return null;
case FullscreenAction.delete:
return AIcons.delete;
case FullscreenAction.info:
return OMIcons.info;
case FullscreenAction.rename:
return OMIcons.title;
case FullscreenAction.rotateCCW:
return AIcons.rotateLeft;
case FullscreenAction.rotateCW:
return AIcons.rotateRight;
case FullscreenAction.print:
return AIcons.print;
case FullscreenAction.share:
return AIcons.share;
// external app actions
case FullscreenAction.edit:
case FullscreenAction.open:
case FullscreenAction.setAs:
case FullscreenAction.openMap:
return null;
case FullscreenAction.debug:
return OMIcons.whatshot;
}
return null;
}
}

View file

@ -9,7 +9,7 @@ import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/album/collection_page.dart'; import 'package:aves/widgets/album/collection_page.dart';
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart'; import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
import 'package:aves/widgets/fullscreen/fullscreen_action_delegate.dart'; import 'package:aves/widgets/common/entry_action_delegate.dart';
import 'package:aves/widgets/fullscreen/image_page.dart'; import 'package:aves/widgets/fullscreen/image_page.dart';
import 'package:aves/widgets/fullscreen/info/info_page.dart'; import 'package:aves/widgets/fullscreen/info/info_page.dart';
import 'package:aves/widgets/fullscreen/overlay/bottom.dart'; import 'package:aves/widgets/fullscreen/overlay/bottom.dart';
@ -49,7 +49,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
Animation<double> _topOverlayScale, _bottomOverlayScale; Animation<double> _topOverlayScale, _bottomOverlayScale;
Animation<Offset> _bottomOverlayOffset; Animation<Offset> _bottomOverlayOffset;
EdgeInsets _frozenViewInsets, _frozenViewPadding; EdgeInsets _frozenViewInsets, _frozenViewPadding;
FullscreenActionDelegate _actionDelegate; EntryActionDelegate _actionDelegate;
final List<Tuple2<String, IjkMediaController>> _videoControllers = []; final List<Tuple2<String, IjkMediaController>> _videoControllers = [];
CollectionLens get collection => widget.collection; CollectionLens get collection => widget.collection;
@ -93,7 +93,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
curve: Curves.easeOutQuad, curve: Curves.easeOutQuad,
)); ));
_overlayVisible.addListener(_onOverlayVisibleChange); _overlayVisible.addListener(_onOverlayVisibleChange);
_actionDelegate = FullscreenActionDelegate( _actionDelegate = EntryActionDelegate(
collection: collection, collection: collection,
showInfo: () => _goToVerticalPage(infoPage), showInfo: () => _goToVerticalPage(infoPage),
); );

View file

@ -1,10 +1,10 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/common/entry_actions.dart';
import 'package:aves/widgets/common/fx/sweeper.dart'; import 'package:aves/widgets/common/fx/sweeper.dart';
import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/common/menu_row.dart'; import 'package:aves/widgets/common/menu_row.dart';
import 'package:aves/widgets/fullscreen/fullscreen_actions.dart';
import 'package:aves/widgets/fullscreen/overlay/common.dart'; import 'package:aves/widgets/fullscreen/overlay/common.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -15,7 +15,7 @@ class FullscreenTopOverlay extends StatelessWidget {
final int index; final int index;
final Animation<double> scale; final Animation<double> scale;
final EdgeInsets viewInsets, viewPadding; final EdgeInsets viewInsets, viewPadding;
final Function(FullscreenAction value) onActionSelected; final Function(EntryAction value) onActionSelected;
final bool canToggleFavourite; final bool canToggleFavourite;
ImageEntry get entry => entries[index]; ImageEntry get entry => entries[index];
@ -53,12 +53,12 @@ class FullscreenTopOverlay extends StatelessWidget {
final quickActionCount = min(targetCount, availableCount); final quickActionCount = min(targetCount, availableCount);
final quickActions = [ final quickActions = [
FullscreenAction.toggleFavourite, EntryAction.toggleFavourite,
FullscreenAction.share, EntryAction.share,
FullscreenAction.delete, EntryAction.delete,
].where(_canDo).take(quickActionCount); ].where(_canDo).take(quickActionCount);
final inAppActions = FullscreenActions.inApp.where((action) => !quickActions.contains(action)).where(_canDo); final inAppActions = EntryActions.inApp.where((action) => !quickActions.contains(action)).where(_canDo);
final externalAppActions = FullscreenActions.externalApp.where(_canDo); final externalAppActions = EntryActions.externalApp.where(_canDo);
return Row( return Row(
children: [ children: [
@ -70,14 +70,14 @@ class FullscreenTopOverlay extends StatelessWidget {
...quickActions.map(_buildOverlayButton), ...quickActions.map(_buildOverlayButton),
OverlayButton( OverlayButton(
scale: scale, scale: scale,
child: PopupMenuButton<FullscreenAction>( child: PopupMenuButton<EntryAction>(
itemBuilder: (context) => [ itemBuilder: (context) => [
...inAppActions.map(_buildPopupMenuItem), ...inAppActions.map(_buildPopupMenuItem),
const PopupMenuDivider(), const PopupMenuDivider(),
...externalAppActions.map(_buildPopupMenuItem), ...externalAppActions.map(_buildPopupMenuItem),
if (kDebugMode) ...[ if (kDebugMode) ...[
const PopupMenuDivider(), const PopupMenuDivider(),
_buildPopupMenuItem(FullscreenAction.debug), _buildPopupMenuItem(EntryAction.debug),
] ]
], ],
onSelected: onActionSelected, onSelected: onActionSelected,
@ -93,37 +93,37 @@ class FullscreenTopOverlay extends StatelessWidget {
); );
} }
bool _canDo(FullscreenAction action) { bool _canDo(EntryAction action) {
switch (action) { switch (action) {
case FullscreenAction.toggleFavourite: case EntryAction.toggleFavourite:
return canToggleFavourite; return canToggleFavourite;
case FullscreenAction.delete: case EntryAction.delete:
case FullscreenAction.rename: case EntryAction.rename:
return entry.canEdit; return entry.canEdit;
case FullscreenAction.rotateCCW: case EntryAction.rotateCCW:
case FullscreenAction.rotateCW: case EntryAction.rotateCW:
return entry.canRotate; return entry.canRotate;
case FullscreenAction.print: case EntryAction.print:
return entry.canPrint; return entry.canPrint;
case FullscreenAction.openMap: case EntryAction.openMap:
return entry.hasGps; return entry.hasGps;
case FullscreenAction.share: case EntryAction.share:
case FullscreenAction.info: case EntryAction.info:
case FullscreenAction.open: case EntryAction.open:
case FullscreenAction.edit: case EntryAction.edit:
case FullscreenAction.setAs: case EntryAction.setAs:
return true; return true;
case FullscreenAction.debug: case EntryAction.debug:
return kDebugMode; return kDebugMode;
} }
return false; return false;
} }
Widget _buildOverlayButton(FullscreenAction action) { Widget _buildOverlayButton(EntryAction action) {
Widget child; Widget child;
final onPressed = () => onActionSelected?.call(action); final onPressed = () => onActionSelected?.call(action);
switch (action) { switch (action) {
case FullscreenAction.toggleFavourite: case EntryAction.toggleFavourite:
child = ValueListenableBuilder<bool>( child = ValueListenableBuilder<bool>(
valueListenable: entry.isFavouriteNotifier, valueListenable: entry.isFavouriteNotifier,
builder: (context, isFavourite, child) => Stack( builder: (context, isFavourite, child) => Stack(
@ -142,24 +142,24 @@ class FullscreenTopOverlay extends StatelessWidget {
), ),
); );
break; break;
case FullscreenAction.info: case EntryAction.info:
case FullscreenAction.share: case EntryAction.share:
case FullscreenAction.delete: case EntryAction.delete:
case FullscreenAction.rename: case EntryAction.rename:
case FullscreenAction.rotateCCW: case EntryAction.rotateCCW:
case FullscreenAction.rotateCW: case EntryAction.rotateCW:
case FullscreenAction.print: case EntryAction.print:
child = IconButton( child = IconButton(
icon: Icon(action.getIcon()), icon: Icon(action.getIcon()),
onPressed: onPressed, onPressed: onPressed,
tooltip: action.getText(), tooltip: action.getText(),
); );
break; break;
case FullscreenAction.openMap: case EntryAction.openMap:
case FullscreenAction.open: case EntryAction.open:
case FullscreenAction.edit: case EntryAction.edit:
case FullscreenAction.setAs: case EntryAction.setAs:
case FullscreenAction.debug: case EntryAction.debug:
break; break;
} }
return child != null return child != null
@ -173,11 +173,11 @@ class FullscreenTopOverlay extends StatelessWidget {
: const SizedBox.shrink(); : const SizedBox.shrink();
} }
PopupMenuEntry<FullscreenAction> _buildPopupMenuItem(FullscreenAction action) { PopupMenuEntry<EntryAction> _buildPopupMenuItem(EntryAction action) {
Widget child; Widget child;
switch (action) { switch (action) {
// in app actions // in app actions
case FullscreenAction.toggleFavourite: case EntryAction.toggleFavourite:
child = entry.isFavouriteNotifier.value child = entry.isFavouriteNotifier.value
? const MenuRow( ? const MenuRow(
text: 'Remove from favourites', text: 'Remove from favourites',
@ -188,21 +188,21 @@ class FullscreenTopOverlay extends StatelessWidget {
icon: AIcons.favourite, icon: AIcons.favourite,
); );
break; break;
case FullscreenAction.info: case EntryAction.info:
case FullscreenAction.share: case EntryAction.share:
case FullscreenAction.delete: case EntryAction.delete:
case FullscreenAction.rename: case EntryAction.rename:
case FullscreenAction.rotateCCW: case EntryAction.rotateCCW:
case FullscreenAction.rotateCW: case EntryAction.rotateCW:
case FullscreenAction.print: case EntryAction.print:
case FullscreenAction.debug: case EntryAction.debug:
child = MenuRow(text: action.getText(), icon: action.getIcon()); child = MenuRow(text: action.getText(), icon: action.getIcon());
break; break;
// external app actions // external app actions
case FullscreenAction.edit: case EntryAction.edit:
case FullscreenAction.open: case EntryAction.open:
case FullscreenAction.setAs: case EntryAction.setAs:
case FullscreenAction.openMap: case EntryAction.openMap:
child = Text(action.getText()); child = Text(action.getText());
break; break;
} }