#225 bin: restore action in snack bar following move
This commit is contained in:
parent
edce0cbcca
commit
8e0b1b495e
11 changed files with 197 additions and 104 deletions
|
@ -62,19 +62,19 @@ class CollectionLens with ChangeNotifier {
|
||||||
break;
|
break;
|
||||||
case MoveType.move:
|
case MoveType.move:
|
||||||
case MoveType.fromBin:
|
case MoveType.fromBin:
|
||||||
_refresh();
|
refresh();
|
||||||
break;
|
break;
|
||||||
case MoveType.toBin:
|
case MoveType.toBin:
|
||||||
_onEntryRemoved(e.entries);
|
_onEntryRemoved(e.entries);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
_subscriptions.add(sourceEvents.on<EntryRefreshedEvent>().listen((e) => _refresh()));
|
_subscriptions.add(sourceEvents.on<EntryRefreshedEvent>().listen((e) => refresh()));
|
||||||
_subscriptions.add(sourceEvents.on<FilterVisibilityChangedEvent>().listen((e) => _refresh()));
|
_subscriptions.add(sourceEvents.on<FilterVisibilityChangedEvent>().listen((e) => refresh()));
|
||||||
_subscriptions.add(sourceEvents.on<CatalogMetadataChangedEvent>().listen((e) => _refresh()));
|
_subscriptions.add(sourceEvents.on<CatalogMetadataChangedEvent>().listen((e) => refresh()));
|
||||||
_subscriptions.add(sourceEvents.on<AddressMetadataChangedEvent>().listen((e) {
|
_subscriptions.add(sourceEvents.on<AddressMetadataChangedEvent>().listen((e) {
|
||||||
if (this.filters.any((filter) => filter is LocationFilter)) {
|
if (this.filters.any((filter) => filter is LocationFilter)) {
|
||||||
_refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
favourites.addListener(_onFavouritesChanged);
|
favourites.addListener(_onFavouritesChanged);
|
||||||
|
@ -85,7 +85,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
Settings.collectionGroupFactorKey,
|
Settings.collectionGroupFactorKey,
|
||||||
].contains(event.key))
|
].contains(event.key))
|
||||||
.listen((_) => _onSettingsChanged()));
|
.listen((_) => _onSettingsChanged()));
|
||||||
_refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -171,7 +171,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFilterChanged() {
|
void _onFilterChanged() {
|
||||||
_refresh();
|
refresh();
|
||||||
filterChangeNotifier.notifyListeners();
|
filterChangeNotifier.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +259,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
|
|
||||||
// metadata change should also trigger a full refresh
|
// metadata change should also trigger a full refresh
|
||||||
// as dates impact sorting and sectioning
|
// as dates impact sorting and sectioning
|
||||||
void _refresh() {
|
void refresh() {
|
||||||
_applyFilters();
|
_applyFilters();
|
||||||
_applySort();
|
_applySort();
|
||||||
_applySection();
|
_applySection();
|
||||||
|
@ -267,7 +267,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
|
|
||||||
void _onFavouritesChanged() {
|
void _onFavouritesChanged() {
|
||||||
if (filters.any((filter) => filter is FavouriteFilter)) {
|
if (filters.any((filter) => filter is FavouriteFilter)) {
|
||||||
_refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onEntryAdded(Set<AvesEntry>? entries) {
|
void _onEntryAdded(Set<AvesEntry>? entries) {
|
||||||
_refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onEntryRemoved(Set<AvesEntry> entries) {
|
void _onEntryRemoved(Set<AvesEntry> entries) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||||
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
||||||
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -36,6 +37,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required MoveType moveType,
|
required MoveType moveType,
|
||||||
required Set<AvesEntry> entries,
|
required Set<AvesEntry> entries,
|
||||||
|
bool hideShowAction = false,
|
||||||
VoidCallback? onSuccess,
|
VoidCallback? onSuccess,
|
||||||
}) async {
|
}) async {
|
||||||
final todoCount = entries.length;
|
final todoCount = entries.length;
|
||||||
|
@ -128,8 +130,9 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
itemCount: todoCount,
|
itemCount: todoCount,
|
||||||
onCancel: () => mediaFileService.cancelFileOp(opId),
|
onCancel: () => mediaFileService.cancelFileOp(opId),
|
||||||
onDone: (processed) async {
|
onDone: (processed) async {
|
||||||
final successOps = processed.where((e) => e.success).toSet();
|
final successOps = processed.where((v) => v.success).toSet();
|
||||||
final movedOps = successOps.where((e) => !e.skipped).toSet();
|
final movedOps = successOps.where((v) => !v.skipped).toSet();
|
||||||
|
final movedEntries = movedOps.map((v) => v.uri).map((uri) => entries.firstWhereOrNull((entry) => entry.uri == uri)).whereNotNull().toSet();
|
||||||
await source.updateAfterMove(
|
await source.updateAfterMove(
|
||||||
todoEntries: entries,
|
todoEntries: entries,
|
||||||
moveType: moveType,
|
moveType: moveType,
|
||||||
|
@ -152,51 +155,34 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
final appMode = context.read<ValueNotifier<AppMode>?>()?.value;
|
final appMode = context.read<ValueNotifier<AppMode>?>()?.value;
|
||||||
|
|
||||||
SnackBarAction? action;
|
SnackBarAction? action;
|
||||||
if (count > 0 && appMode == AppMode.main && !toBin) {
|
if (count > 0 && appMode == AppMode.main) {
|
||||||
action = SnackBarAction(
|
if (toBin) {
|
||||||
label: l10n.showButtonLabel,
|
if (movedEntries.isNotEmpty) {
|
||||||
onPressed: () async {
|
action = SnackBarAction(
|
||||||
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();
|
// TODO TLAD [l10n] key for "RESTORE"
|
||||||
bool highlightTest(AvesEntry entry) => newUris.contains(entry.uri);
|
label: l10n.entryActionRestore.toUpperCase(),
|
||||||
|
onPressed: () => move(
|
||||||
final collection = context.read<CollectionLens?>();
|
|
||||||
if (collection == null || collection.filters.any((f) => f is AlbumFilter || f is TrashFilter)) {
|
|
||||||
final targetFilters = collection?.filters.where((f) => f != TrashFilter.instance).toSet() ?? {};
|
|
||||||
// we could simply add the filter to the current collection
|
|
||||||
// but navigating makes the change less jarring
|
|
||||||
if (destinationAlbums.length == 1) {
|
|
||||||
final destinationAlbum = destinationAlbums.single;
|
|
||||||
targetFilters.removeWhere((f) => f is AlbumFilter);
|
|
||||||
targetFilters.add(AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum)));
|
|
||||||
}
|
|
||||||
unawaited(Navigator.pushAndRemoveUntil(
|
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
moveType: MoveType.fromBin,
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
entries: movedEntries,
|
||||||
builder: (context) => CollectionPage(
|
hideShowAction: true,
|
||||||
source: source,
|
),
|
||||||
filters: targetFilters,
|
);
|
||||||
highlightTest: highlightTest,
|
}
|
||||||
),
|
} else if (!hideShowAction) {
|
||||||
),
|
action = SnackBarAction(
|
||||||
(route) => false,
|
label: l10n.showButtonLabel,
|
||||||
));
|
onPressed: () => _showMovedItems(context, destinationAlbums, movedOps),
|
||||||
} else {
|
);
|
||||||
// track in current page, without navigation
|
}
|
||||||
await Future.delayed(Durations.highlightScrollInitDelay);
|
|
||||||
final targetEntry = collection.sortedEntries.firstWhereOrNull(highlightTest);
|
|
||||||
if (targetEntry != null) {
|
|
||||||
context.read<HighlightInfo>().trackItem(targetEntry, highlightItem: targetEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
showFeedback(
|
showFeedback(
|
||||||
context,
|
context,
|
||||||
copy ? l10n.collectionCopySuccessFeedback(count) : l10n.collectionMoveSuccessFeedback(count),
|
copy ? l10n.collectionCopySuccessFeedback(count) : l10n.collectionMoveSuccessFeedback(count),
|
||||||
action,
|
action,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
EntryMovedNotification(moveType, movedEntries).dispatch(context);
|
||||||
onSuccess?.call();
|
onSuccess?.call();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -280,6 +266,47 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _showMovedItems(
|
||||||
|
BuildContext context,
|
||||||
|
Set<String> destinationAlbums,
|
||||||
|
Set<MoveOpEvent> movedOps,
|
||||||
|
) async {
|
||||||
|
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();
|
||||||
|
bool highlightTest(AvesEntry entry) => newUris.contains(entry.uri);
|
||||||
|
|
||||||
|
final collection = context.read<CollectionLens?>();
|
||||||
|
if (collection == null || collection.filters.any((f) => f is AlbumFilter || f is TrashFilter)) {
|
||||||
|
final source = context.read<CollectionSource>();
|
||||||
|
final targetFilters = collection?.filters.where((f) => f != TrashFilter.instance).toSet() ?? {};
|
||||||
|
// we could simply add the filter to the current collection
|
||||||
|
// but navigating makes the change less jarring
|
||||||
|
if (destinationAlbums.length == 1) {
|
||||||
|
final destinationAlbum = destinationAlbums.single;
|
||||||
|
targetFilters.removeWhere((f) => f is AlbumFilter);
|
||||||
|
targetFilters.add(AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum)));
|
||||||
|
}
|
||||||
|
unawaited(Navigator.pushAndRemoveUntil(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
|
builder: (context) => CollectionPage(
|
||||||
|
source: source,
|
||||||
|
filters: targetFilters,
|
||||||
|
highlightTest: highlightTest,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(route) => false,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
// track in current page, without navigation
|
||||||
|
await Future.delayed(Durations.highlightScrollInitDelay);
|
||||||
|
final targetEntry = collection.sortedEntries.firstWhereOrNull(highlightTest);
|
||||||
|
if (targetEntry != null) {
|
||||||
|
context.read<HighlightInfo>().trackItem(targetEntry, highlightItem: targetEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MoveUndatedConfirmationDialogDelegate extends ConfirmationDialogDelegate {
|
class MoveUndatedConfirmationDialogDelegate extends ConfirmationDialogDelegate {
|
||||||
|
|
|
@ -50,20 +50,34 @@ mixin FeedbackMixin {
|
||||||
// and space under the snack bar `margin` does not receive gestures
|
// and space under the snack bar `margin` does not receive gestures
|
||||||
// (because it is used by the `Dismissible` wrapping the snack bar)
|
// (because it is used by the `Dismissible` wrapping the snack bar)
|
||||||
// so we use `showOverlayNotification` instead
|
// so we use `showOverlayNotification` instead
|
||||||
showOverlayNotification(
|
OverlaySupportEntry? notificationOverlayEntry;
|
||||||
|
notificationOverlayEntry = showOverlayNotification(
|
||||||
(context) => SafeArea(
|
(context) => SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: margin,
|
padding: margin,
|
||||||
child: SnackBar(
|
child: SnackBar(
|
||||||
content: snackBarContent,
|
content: snackBarContent,
|
||||||
animation: const AlwaysStoppedAnimation<double>(1),
|
animation: const AlwaysStoppedAnimation<double>(1),
|
||||||
action: action,
|
action: action != null
|
||||||
|
? SnackBarAction(
|
||||||
|
label: action.label,
|
||||||
|
onPressed: () {
|
||||||
|
// the regular snack bar dismiss behavior is confused
|
||||||
|
// because it expects a `Scaffold` in context,
|
||||||
|
// so we manually dimiss the overlay entry
|
||||||
|
notificationOverlayEntry?.dismiss();
|
||||||
|
action.onPressed();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
duration: duration,
|
duration: duration,
|
||||||
dismissDirection: DismissDirection.horizontal,
|
dismissDirection: DismissDirection.horizontal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
duration: duration,
|
duration: duration,
|
||||||
|
// reuse the same key to dismiss previous snack bar when a new one is shown
|
||||||
|
key: const Key('snack'),
|
||||||
position: NotificationPosition.bottom,
|
position: NotificationPosition.bottom,
|
||||||
context: context,
|
context: context,
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,7 +15,7 @@ import 'package:aves/widgets/common/map/compass.dart';
|
||||||
import 'package:aves/widgets/common/map/theme.dart';
|
import 'package:aves/widgets/common/map/theme.dart';
|
||||||
import 'package:aves/widgets/common/map/zoomed_bounds.dart';
|
import 'package:aves/widgets/common/map/zoomed_bounds.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||||
import 'package:aves/widgets/viewer/info/notifications.dart';
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
|
@ -26,7 +26,7 @@ import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
import 'package:aves/widgets/common/thumbnail/scroller.dart';
|
import 'package:aves/widgets/common/thumbnail/scroller.dart';
|
||||||
import 'package:aves/widgets/map/map_info_row.dart';
|
import 'package:aves/widgets/map/map_info_row.dart';
|
||||||
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
||||||
import 'package:aves/widgets/viewer/info/notifications.dart';
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
|
@ -31,7 +31,7 @@ import 'package:aves/widgets/filter_grids/album_pick.dart';
|
||||||
import 'package:aves/widgets/viewer/action/printer.dart';
|
import 'package:aves/widgets/viewer/action/printer.dart';
|
||||||
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
|
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
|
||||||
import 'package:aves/widgets/viewer/debug/debug_page.dart';
|
import 'package:aves/widgets/viewer/debug/debug_page.dart';
|
||||||
import 'package:aves/widgets/viewer/info/notifications.dart';
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/notifications.dart';
|
import 'package:aves/widgets/viewer/overlay/notifications.dart';
|
||||||
import 'package:aves/widgets/viewer/source_viewer_page.dart';
|
import 'package:aves/widgets/viewer/source_viewer_page.dart';
|
||||||
import 'package:aves/widgets/viewer/video/conductor.dart';
|
import 'package:aves/widgets/viewer/video/conductor.dart';
|
||||||
|
@ -205,7 +205,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
if (source.initState != SourceInitializationState.none) {
|
if (source.initState != SourceInitializationState.none) {
|
||||||
await source.removeEntries({entry.uri}, includeTrash: true);
|
await source.removeEntries({entry.uri}, includeTrash: true);
|
||||||
}
|
}
|
||||||
EntryRemovedNotification(entry).dispatch(context);
|
EntryDeletedNotification({entry}).dispatch(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,20 +299,11 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _move(BuildContext context, {required MoveType moveType}) async {
|
Future<void> _move(BuildContext context, {required MoveType moveType}) => move(
|
||||||
await move(
|
context,
|
||||||
context,
|
moveType: moveType,
|
||||||
moveType: moveType,
|
entries: {entry},
|
||||||
entries: {entry},
|
);
|
||||||
onSuccess: {
|
|
||||||
MoveType.move,
|
|
||||||
MoveType.toBin,
|
|
||||||
MoveType.fromBin,
|
|
||||||
}.contains(moveType)
|
|
||||||
? () => EntryRemovedNotification(entry).dispatch(context)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _rename(BuildContext context) async {
|
Future<void> _rename(BuildContext context) async {
|
||||||
final newName = await showDialog<String>(
|
final newName = await showDialog<String>(
|
||||||
|
|
|
@ -9,7 +9,7 @@ import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/common/magnifier/pan/scroll_physics.dart';
|
import 'package:aves/widgets/common/magnifier/pan/scroll_physics.dart';
|
||||||
import 'package:aves/widgets/viewer/entry_horizontal_pager.dart';
|
import 'package:aves/widgets/viewer/entry_horizontal_pager.dart';
|
||||||
import 'package:aves/widgets/viewer/info/info_page.dart';
|
import 'package:aves/widgets/viewer/info/info_page.dart';
|
||||||
import 'package:aves/widgets/viewer/info/notifications.dart';
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:screen_brightness/screen_brightness.dart';
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
import 'package:aves/app_mode.dart';
|
||||||
|
import 'package:aves/model/actions/move_type.dart';
|
||||||
import 'package:aves/model/device.dart';
|
import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/model/filters/trash.dart';
|
||||||
import 'package:aves/model/highlight.dart';
|
import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
@ -16,9 +18,9 @@ import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
import 'package:aves/widgets/viewer/entry_vertical_pager.dart';
|
import 'package:aves/widgets/viewer/entry_vertical_pager.dart';
|
||||||
import 'package:aves/widgets/viewer/hero.dart';
|
import 'package:aves/widgets/viewer/hero.dart';
|
||||||
import 'package:aves/widgets/viewer/info/notifications.dart';
|
|
||||||
import 'package:aves/widgets/viewer/multipage/conductor.dart';
|
import 'package:aves/widgets/viewer/multipage/conductor.dart';
|
||||||
import 'package:aves/widgets/viewer/multipage/controller.dart';
|
import 'package:aves/widgets/viewer/multipage/controller.dart';
|
||||||
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/bottom.dart';
|
import 'package:aves/widgets/viewer/overlay/bottom.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/notifications.dart';
|
import 'package:aves/widgets/viewer/overlay/notifications.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/panorama.dart';
|
import 'package:aves/widgets/viewer/overlay/panorama.dart';
|
||||||
|
@ -195,8 +197,33 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
|
||||||
onNotification: (dynamic notification) {
|
onNotification: (dynamic notification) {
|
||||||
if (notification is FilterSelectedNotification) {
|
if (notification is FilterSelectedNotification) {
|
||||||
_goToCollection(notification.filter);
|
_goToCollection(notification.filter);
|
||||||
} else if (notification is EntryRemovedNotification) {
|
} else if (notification is EntryDeletedNotification) {
|
||||||
_onEntryRemoved(context, notification.entry);
|
_onEntryRemoved(context, notification.entries);
|
||||||
|
} else if (notification is EntryMovedNotification) {
|
||||||
|
// only add or remove entries following user actions,
|
||||||
|
// instead of applying all collection source changes
|
||||||
|
final isBin = collection?.filters.contains(TrashFilter.instance) ?? false;
|
||||||
|
final entries = notification.entries;
|
||||||
|
switch (notification.moveType) {
|
||||||
|
case MoveType.move:
|
||||||
|
_onEntryRemoved(context, entries);
|
||||||
|
break;
|
||||||
|
case MoveType.toBin:
|
||||||
|
if (!isBin) {
|
||||||
|
_onEntryRemoved(context, entries);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MoveType.fromBin:
|
||||||
|
if (isBin) {
|
||||||
|
_onEntryRemoved(context, entries);
|
||||||
|
} else {
|
||||||
|
_onEntryRestored(entries);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MoveType.copy:
|
||||||
|
case MoveType.export:
|
||||||
|
break;
|
||||||
|
}
|
||||||
} else if (notification is ToggleOverlayNotification) {
|
} else if (notification is ToggleOverlayNotification) {
|
||||||
_overlayVisible.value = notification.visible ?? !_overlayVisible.value;
|
_overlayVisible.value = notification.visible ?? !_overlayVisible.value;
|
||||||
} else if (notification is ShowInfoNotification) {
|
} else if (notification is ShowInfoNotification) {
|
||||||
|
@ -457,12 +484,28 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
|
||||||
_updateEntry();
|
_updateEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onEntryRemoved(BuildContext context, AvesEntry entry) {
|
void _onEntryRestored(Set<AvesEntry> restoredEntries) {
|
||||||
// deleted or moved to another album
|
if (restoredEntries.isEmpty) return;
|
||||||
|
|
||||||
|
final _collection = collection;
|
||||||
|
if (_collection != null) {
|
||||||
|
_collection.refresh();
|
||||||
|
final index = _collection.sortedEntries.indexOf(restoredEntries.first);
|
||||||
|
if (index != -1) {
|
||||||
|
_onHorizontalPageChanged(index);
|
||||||
|
}
|
||||||
|
_onCollectionChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleted or moved to another album
|
||||||
|
void _onEntryRemoved(BuildContext context, Set<AvesEntry> removedEntries) {
|
||||||
|
if (removedEntries.isEmpty) return;
|
||||||
|
|
||||||
if (hasCollection) {
|
if (hasCollection) {
|
||||||
final entries = collection!.sortedEntries;
|
final collectionEntries = collection!.sortedEntries;
|
||||||
entries.remove(entry);
|
removedEntries.forEach(collectionEntries.remove);
|
||||||
if (entries.isNotEmpty) {
|
if (collectionEntries.isNotEmpty) {
|
||||||
_onCollectionChange();
|
_onCollectionChange();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ import 'package:aves/widgets/viewer/info/basic_section.dart';
|
||||||
import 'package:aves/widgets/viewer/info/info_app_bar.dart';
|
import 'package:aves/widgets/viewer/info/info_app_bar.dart';
|
||||||
import 'package:aves/widgets/viewer/info/location_section.dart';
|
import 'package:aves/widgets/viewer/info/location_section.dart';
|
||||||
import 'package:aves/widgets/viewer/info/metadata/metadata_section.dart';
|
import 'package:aves/widgets/viewer/info/metadata/metadata_section.dart';
|
||||||
import 'package:aves/widgets/viewer/info/notifications.dart';
|
|
||||||
import 'package:aves/widgets/viewer/multipage/conductor.dart';
|
import 'package:aves/widgets/viewer/multipage/conductor.dart';
|
||||||
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:aves/widgets/viewer/page_entry_builder.dart';
|
import 'package:aves/widgets/viewer/page_entry_builder.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import 'package:aves/model/entry.dart';
|
|
||||||
import 'package:aves/model/filters/filters.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
@immutable
|
|
||||||
class ShowImageNotification extends Notification {}
|
|
||||||
|
|
||||||
@immutable
|
|
||||||
class ShowInfoNotification extends Notification {}
|
|
||||||
|
|
||||||
@immutable
|
|
||||||
class FilterSelectedNotification extends Notification {
|
|
||||||
final CollectionFilter filter;
|
|
||||||
|
|
||||||
const FilterSelectedNotification(this.filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleted or moved to another album
|
|
||||||
@immutable
|
|
||||||
class EntryRemovedNotification extends Notification {
|
|
||||||
final AvesEntry entry;
|
|
||||||
|
|
||||||
const EntryRemovedNotification(this.entry);
|
|
||||||
}
|
|
42
lib/widgets/viewer/notifications.dart
Normal file
42
lib/widgets/viewer/notifications.dart
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:aves/model/actions/move_type.dart';
|
||||||
|
import 'package:aves/model/entry.dart';
|
||||||
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class ShowImageNotification extends Notification {}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class ShowInfoNotification extends Notification {}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class FilterSelectedNotification extends Notification with EquatableMixin {
|
||||||
|
final CollectionFilter filter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [filter];
|
||||||
|
|
||||||
|
const FilterSelectedNotification(this.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class EntryDeletedNotification extends Notification with EquatableMixin {
|
||||||
|
final Set<AvesEntry> entries;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [entries];
|
||||||
|
|
||||||
|
const EntryDeletedNotification(this.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class EntryMovedNotification extends Notification with EquatableMixin {
|
||||||
|
final MoveType moveType;
|
||||||
|
final Set<AvesEntry> entries;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [moveType, entries];
|
||||||
|
|
||||||
|
const EntryMovedNotification(this.moveType, this.entries);
|
||||||
|
}
|
Loading…
Reference in a new issue