#1174 custom placeholder handling for collection-viewer hero
This commit is contained in:
parent
cc3b4f661b
commit
f4e5018b78
7 changed files with 67 additions and 3 deletions
|
@ -32,6 +32,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
|
|||
import 'package:aves/widgets/common/providers/durations_provider.dart';
|
||||
import 'package:aves/widgets/common/providers/highlight_info_provider.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/providers/viewer_entry_provider.dart';
|
||||
import 'package:aves/widgets/home_page.dart';
|
||||
import 'package:aves/widgets/navigation/tv_page_transitions.dart';
|
||||
import 'package:aves/widgets/navigation/tv_rail.dart';
|
||||
|
@ -224,6 +225,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
|||
Provider<TvRailController>.value(value: _tvRailController),
|
||||
DurationsProvider(),
|
||||
HighlightInfoProvider(),
|
||||
ViewerEntryProvider(),
|
||||
],
|
||||
child: NotificationListener<PopExitNotification>(
|
||||
onNotification: (notification) {
|
||||
|
|
|
@ -40,6 +40,7 @@ import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
|
|||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/common/identity/scroll_thumb.dart';
|
||||
import 'package:aves/widgets/common/providers/tile_extent_controller_provider.dart';
|
||||
import 'package:aves/widgets/common/providers/viewer_entry_provider.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/decorated.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/image.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/notifications.dart';
|
||||
|
@ -49,6 +50,7 @@ import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
|||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
@ -116,6 +118,12 @@ class _CollectionGridContentState extends State<_CollectionGridContent> {
|
|||
final ValueNotifier<bool> _isScrollingNotifier = ValueNotifier(false);
|
||||
final ValueNotifier<AppMode> _selectingAppModeNotifier = ValueNotifier(AppMode.pickFilteredMediaInternal);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => context.read<ViewerEntryNotifier>().value = null);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusedItemNotifier.dispose();
|
||||
|
@ -238,9 +246,12 @@ class _CollectionGridContentState extends State<_CollectionGridContent> {
|
|||
);
|
||||
}
|
||||
|
||||
void _goToViewer(CollectionLens collection, AvesEntry entry) {
|
||||
Future<void> _goToViewer(CollectionLens collection, AvesEntry entry) async {
|
||||
// track viewer entry for dynamic hero placeholder
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => context.read<ViewerEntryNotifier>().value = entry);
|
||||
|
||||
final selection = context.read<Selection<AvesEntry>>();
|
||||
Navigator.maybeOf(context)?.push(
|
||||
await Navigator.maybeOf(context)?.push(
|
||||
TransparentMaterialPageRoute(
|
||||
settings: const RouteSettings(name: EntryViewerPage.routeName),
|
||||
pageBuilder: (context, a, sa) {
|
||||
|
@ -266,6 +277,14 @@ class _CollectionGridContentState extends State<_CollectionGridContent> {
|
|||
},
|
||||
),
|
||||
);
|
||||
|
||||
// reset track viewer entry
|
||||
final animate = context.read<Settings>().animate;
|
||||
if (animate) {
|
||||
// TODO TLAD fix timing when transition is incomplete, e.g. when going back while going to the viewer
|
||||
await Future.delayed(ADurations.pageTransitionExact * timeDilation);
|
||||
}
|
||||
context.read<ViewerEntryNotifier>().value = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/services/intent_service.dart';
|
|||
import 'package:aves/widgets/collection/grid/list_details.dart';
|
||||
import 'package:aves/widgets/collection/grid/list_details_theme.dart';
|
||||
import 'package:aves/widgets/common/grid/scaling.dart';
|
||||
import 'package:aves/widgets/common/providers/viewer_entry_provider.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/decorated.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/notifications.dart';
|
||||
import 'package:aves/widgets/viewer/hero.dart';
|
||||
|
@ -124,5 +125,20 @@ class Tile extends StatelessWidget {
|
|||
selectable: selectable,
|
||||
highlightable: highlightable,
|
||||
heroTagger: heroTagger,
|
||||
// do not use a hero placeholder but hide the thumbnail matching the viewer entry,
|
||||
// so that it can hero out on an entry and come back with a hero to a different entry
|
||||
heroPlaceholderBuilder: (context, heroSize, child) => child,
|
||||
imageDecorator: (context, child) {
|
||||
return Selector<ViewerEntryNotifier, bool>(
|
||||
selector: (context, v) => v.value == entry,
|
||||
builder: (context, isViewerEntry, child) {
|
||||
return Visibility.maintain(
|
||||
visible: !isViewerEntry,
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
17
lib/widgets/common/providers/viewer_entry_provider.dart
Normal file
17
lib/widgets/common/providers/viewer_entry_provider.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ViewerEntryProvider extends ListenableProvider<ViewerEntryNotifier> {
|
||||
ViewerEntryProvider({
|
||||
super.key,
|
||||
super.child,
|
||||
}) : super(
|
||||
create: (context) => ViewerEntryNotifier(null),
|
||||
dispose: (context, value) => value.dispose(),
|
||||
);
|
||||
}
|
||||
|
||||
class ViewerEntryNotifier extends ValueNotifier<AvesEntry?> {
|
||||
ViewerEntryNotifier(super.value);
|
||||
}
|
|
@ -13,6 +13,8 @@ class DecoratedThumbnail extends StatelessWidget {
|
|||
final ValueNotifier<bool>? cancellableNotifier;
|
||||
final bool isMosaic, selectable, highlightable;
|
||||
final Object? Function()? heroTagger;
|
||||
final HeroPlaceholderBuilder? heroPlaceholderBuilder;
|
||||
final TransitionBuilder? imageDecorator;
|
||||
|
||||
static Color borderColor(BuildContext context) => Theme.of(context).dividerColor;
|
||||
|
||||
|
@ -27,6 +29,8 @@ class DecoratedThumbnail extends StatelessWidget {
|
|||
this.selectable = true,
|
||||
this.highlightable = true,
|
||||
this.heroTagger,
|
||||
this.heroPlaceholderBuilder,
|
||||
this.imageDecorator,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -50,12 +54,13 @@ class DecoratedThumbnail extends StatelessWidget {
|
|||
isMosaic: isMosaic,
|
||||
cancellableNotifier: cancellableNotifier,
|
||||
heroTag: heroTagger?.call(),
|
||||
heroPlaceholderBuilder: heroPlaceholderBuilder,
|
||||
);
|
||||
|
||||
child = Stack(
|
||||
fit: StackFit.passthrough,
|
||||
children: [
|
||||
child,
|
||||
imageDecorator?.call(context, child) ?? child,
|
||||
ThumbnailEntryOverlay(entry: entry),
|
||||
if (selectable) ...[
|
||||
GridItemSelectionOverlay<AvesEntry>(
|
||||
|
|
|
@ -25,6 +25,7 @@ class ThumbnailImage extends StatefulWidget {
|
|||
final bool showLoadingBackground;
|
||||
final ValueNotifier<bool>? cancellableNotifier;
|
||||
final Object? heroTag;
|
||||
final HeroPlaceholderBuilder? heroPlaceholderBuilder;
|
||||
|
||||
const ThumbnailImage({
|
||||
super.key,
|
||||
|
@ -37,6 +38,7 @@ class ThumbnailImage extends StatefulWidget {
|
|||
this.showLoadingBackground = true,
|
||||
this.cancellableNotifier,
|
||||
this.heroTag,
|
||||
this.heroPlaceholderBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -283,6 +285,7 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
|
|||
}
|
||||
return child;
|
||||
},
|
||||
placeholderBuilder: widget.heroPlaceholderBuilder,
|
||||
transitionOnUserGestures: true,
|
||||
child: image,
|
||||
);
|
||||
|
|
|
@ -18,6 +18,7 @@ import 'package:aves/widgets/aves_app.dart';
|
|||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/basic/insets.dart';
|
||||
import 'package:aves/widgets/common/providers/viewer_entry_provider.dart';
|
||||
import 'package:aves/widgets/viewer/action/video_action_delegate.dart';
|
||||
import 'package:aves/widgets/viewer/controls/controller.dart';
|
||||
import 'package:aves/widgets/viewer/controls/notifications.dart';
|
||||
|
@ -900,6 +901,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
|||
predicate: (v) => v < 1,
|
||||
animate: false,
|
||||
);
|
||||
context.read<ViewerEntryNotifier>().value = entry;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue