#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/durations_provider.dart';
|
||||||
import 'package:aves/widgets/common/providers/highlight_info_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/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/home_page.dart';
|
||||||
import 'package:aves/widgets/navigation/tv_page_transitions.dart';
|
import 'package:aves/widgets/navigation/tv_page_transitions.dart';
|
||||||
import 'package:aves/widgets/navigation/tv_rail.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),
|
Provider<TvRailController>.value(value: _tvRailController),
|
||||||
DurationsProvider(),
|
DurationsProvider(),
|
||||||
HighlightInfoProvider(),
|
HighlightInfoProvider(),
|
||||||
|
ViewerEntryProvider(),
|
||||||
],
|
],
|
||||||
child: NotificationListener<PopExitNotification>(
|
child: NotificationListener<PopExitNotification>(
|
||||||
onNotification: (notification) {
|
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/empty.dart';
|
||||||
import 'package:aves/widgets/common/identity/scroll_thumb.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/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/decorated.dart';
|
||||||
import 'package:aves/widgets/common/thumbnail/image.dart';
|
import 'package:aves/widgets/common/thumbnail/image.dart';
|
||||||
import 'package:aves/widgets/common/thumbnail/notifications.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:aves_model/aves_model.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_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:permission_handler/permission_handler.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<bool> _isScrollingNotifier = ValueNotifier(false);
|
||||||
final ValueNotifier<AppMode> _selectingAppModeNotifier = ValueNotifier(AppMode.pickFilteredMediaInternal);
|
final ValueNotifier<AppMode> _selectingAppModeNotifier = ValueNotifier(AppMode.pickFilteredMediaInternal);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => context.read<ViewerEntryNotifier>().value = null);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_focusedItemNotifier.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>>();
|
final selection = context.read<Selection<AvesEntry>>();
|
||||||
Navigator.maybeOf(context)?.push(
|
await Navigator.maybeOf(context)?.push(
|
||||||
TransparentMaterialPageRoute(
|
TransparentMaterialPageRoute(
|
||||||
settings: const RouteSettings(name: EntryViewerPage.routeName),
|
settings: const RouteSettings(name: EntryViewerPage.routeName),
|
||||||
pageBuilder: (context, a, sa) {
|
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.dart';
|
||||||
import 'package:aves/widgets/collection/grid/list_details_theme.dart';
|
import 'package:aves/widgets/collection/grid/list_details_theme.dart';
|
||||||
import 'package:aves/widgets/common/grid/scaling.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/decorated.dart';
|
||||||
import 'package:aves/widgets/common/thumbnail/notifications.dart';
|
import 'package:aves/widgets/common/thumbnail/notifications.dart';
|
||||||
import 'package:aves/widgets/viewer/hero.dart';
|
import 'package:aves/widgets/viewer/hero.dart';
|
||||||
|
@ -124,5 +125,20 @@ class Tile extends StatelessWidget {
|
||||||
selectable: selectable,
|
selectable: selectable,
|
||||||
highlightable: highlightable,
|
highlightable: highlightable,
|
||||||
heroTagger: heroTagger,
|
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 ValueNotifier<bool>? cancellableNotifier;
|
||||||
final bool isMosaic, selectable, highlightable;
|
final bool isMosaic, selectable, highlightable;
|
||||||
final Object? Function()? heroTagger;
|
final Object? Function()? heroTagger;
|
||||||
|
final HeroPlaceholderBuilder? heroPlaceholderBuilder;
|
||||||
|
final TransitionBuilder? imageDecorator;
|
||||||
|
|
||||||
static Color borderColor(BuildContext context) => Theme.of(context).dividerColor;
|
static Color borderColor(BuildContext context) => Theme.of(context).dividerColor;
|
||||||
|
|
||||||
|
@ -27,6 +29,8 @@ class DecoratedThumbnail extends StatelessWidget {
|
||||||
this.selectable = true,
|
this.selectable = true,
|
||||||
this.highlightable = true,
|
this.highlightable = true,
|
||||||
this.heroTagger,
|
this.heroTagger,
|
||||||
|
this.heroPlaceholderBuilder,
|
||||||
|
this.imageDecorator,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -50,12 +54,13 @@ class DecoratedThumbnail extends StatelessWidget {
|
||||||
isMosaic: isMosaic,
|
isMosaic: isMosaic,
|
||||||
cancellableNotifier: cancellableNotifier,
|
cancellableNotifier: cancellableNotifier,
|
||||||
heroTag: heroTagger?.call(),
|
heroTag: heroTagger?.call(),
|
||||||
|
heroPlaceholderBuilder: heroPlaceholderBuilder,
|
||||||
);
|
);
|
||||||
|
|
||||||
child = Stack(
|
child = Stack(
|
||||||
fit: StackFit.passthrough,
|
fit: StackFit.passthrough,
|
||||||
children: [
|
children: [
|
||||||
child,
|
imageDecorator?.call(context, child) ?? child,
|
||||||
ThumbnailEntryOverlay(entry: entry),
|
ThumbnailEntryOverlay(entry: entry),
|
||||||
if (selectable) ...[
|
if (selectable) ...[
|
||||||
GridItemSelectionOverlay<AvesEntry>(
|
GridItemSelectionOverlay<AvesEntry>(
|
||||||
|
|
|
@ -25,6 +25,7 @@ class ThumbnailImage extends StatefulWidget {
|
||||||
final bool showLoadingBackground;
|
final bool showLoadingBackground;
|
||||||
final ValueNotifier<bool>? cancellableNotifier;
|
final ValueNotifier<bool>? cancellableNotifier;
|
||||||
final Object? heroTag;
|
final Object? heroTag;
|
||||||
|
final HeroPlaceholderBuilder? heroPlaceholderBuilder;
|
||||||
|
|
||||||
const ThumbnailImage({
|
const ThumbnailImage({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -37,6 +38,7 @@ class ThumbnailImage extends StatefulWidget {
|
||||||
this.showLoadingBackground = true,
|
this.showLoadingBackground = true,
|
||||||
this.cancellableNotifier,
|
this.cancellableNotifier,
|
||||||
this.heroTag,
|
this.heroTag,
|
||||||
|
this.heroPlaceholderBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -283,6 +285,7 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
|
||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
},
|
},
|
||||||
|
placeholderBuilder: widget.heroPlaceholderBuilder,
|
||||||
transitionOnUserGestures: true,
|
transitionOnUserGestures: true,
|
||||||
child: image,
|
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/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
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/common/providers/viewer_entry_provider.dart';
|
||||||
import 'package:aves/widgets/viewer/action/video_action_delegate.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/controller.dart';
|
||||||
import 'package:aves/widgets/viewer/controls/notifications.dart';
|
import 'package:aves/widgets/viewer/controls/notifications.dart';
|
||||||
|
@ -900,6 +901,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
predicate: (v) => v < 1,
|
predicate: (v) => v < 1,
|
||||||
animate: false,
|
animate: false,
|
||||||
);
|
);
|
||||||
|
context.read<ViewerEntryNotifier>().value = entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue