mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-28 23:08:59 +02:00
fix(ui): hero continuity for motion (Apple Music) headers
Albums/artists with a motion banner have no square cover, so the Hero had no destination: the open flight ended nowhere and there was no return flight. The Hero now rides the full-bleed banner, flying the still cover image as the shuttle so the video doesn't restart mid-air.
This commit is contained in:
@@ -379,7 +379,7 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen>
|
||||
final cacheWidth = coverCacheWidthForViewport(context);
|
||||
final headerBgUrl =
|
||||
_headerImageUrl ?? widget.headerImageUrl ?? widget.coverUrl;
|
||||
final Widget headerBgImage = headerBgUrl != null
|
||||
Widget headerBgImage() => headerBgUrl != null
|
||||
? CachedNetworkImage(
|
||||
imageUrl: highResCoverUrl(headerBgUrl) ?? headerBgUrl,
|
||||
fit: BoxFit.cover,
|
||||
@@ -397,15 +397,27 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen>
|
||||
),
|
||||
);
|
||||
|
||||
Widget background = hasMotion
|
||||
? MotionHeaderBanner(videoUrl: motionUrl, fallback: headerBgImage())
|
||||
: headerBgImage();
|
||||
// With a motion banner there is no square cover, so the Hero rides the
|
||||
// full-bleed background instead; the shuttle flies the still image (a
|
||||
// shuttle-built banner would restart the video mid-flight).
|
||||
if (hasMotion && widget.heroTag != null) {
|
||||
background = Hero(
|
||||
tag: widget.heroTag!,
|
||||
flightShuttleBuilder: (_, _, _, _, _) => headerBgImage(),
|
||||
child: background,
|
||||
);
|
||||
}
|
||||
|
||||
return AlbumDetailHeader(
|
||||
title: widget.albumName,
|
||||
expandedHeight: expandedHeight,
|
||||
showTitleInAppBar: showTitleInAppBar,
|
||||
backgroundColor: pageBackgroundColor,
|
||||
heroTag: widget.heroTag,
|
||||
background: hasMotion
|
||||
? MotionHeaderBanner(videoUrl: motionUrl, fallback: headerBgImage)
|
||||
: headerBgImage,
|
||||
background: background,
|
||||
blurAndScrimBackground: showSquareCover,
|
||||
coverBuilder: showSquareCover
|
||||
? (context, coverSize) => coverThumbUrl != null
|
||||
|
||||
@@ -121,8 +121,18 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen>
|
||||
int? _monthlyListeners;
|
||||
String? _error;
|
||||
|
||||
Widget _heroWrap(Widget child) =>
|
||||
widget.heroTag != null ? Hero(tag: widget.heroTag!, child: child) : child;
|
||||
/// [shuttle], when given, is flown instead of re-instantiating [child]
|
||||
/// (needed for motion banners, which would restart their video mid-flight).
|
||||
Widget _heroWrap(Widget child, {Widget Function()? shuttle}) =>
|
||||
widget.heroTag != null
|
||||
? Hero(
|
||||
tag: widget.heroTag!,
|
||||
flightShuttleBuilder: shuttle == null
|
||||
? null
|
||||
: (_, _, _, _, _) => shuttle(),
|
||||
child: child,
|
||||
)
|
||||
: child;
|
||||
|
||||
bool _showTitleInAppBar = false;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
@@ -1170,34 +1180,42 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen>
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
if (hasMotionBanner)
|
||||
MotionHeaderBanner(
|
||||
videoUrl: headerVideoUrl,
|
||||
fallback: hasValidImage
|
||||
? CachedCoverImage(
|
||||
imageUrl: imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
memCacheWidth: 800,
|
||||
placeholder: (context, url) => Container(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
Builder(
|
||||
builder: (context) {
|
||||
Widget fallbackImage() => hasValidImage
|
||||
? CachedCoverImage(
|
||||
imageUrl: imageUrl!,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
memCacheWidth: 800,
|
||||
placeholder: (context, url) => Container(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: Icon(
|
||||
Icons.person,
|
||||
size: 80,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: Icon(
|
||||
Icons.person,
|
||||
size: 80,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
child: Icon(
|
||||
Icons.person,
|
||||
size: 80,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
return _heroWrap(
|
||||
MotionHeaderBanner(
|
||||
videoUrl: headerVideoUrl!,
|
||||
fallback: fallbackImage(),
|
||||
),
|
||||
shuttle: fallbackImage,
|
||||
);
|
||||
},
|
||||
)
|
||||
else if (hasValidImage)
|
||||
_heroWrap(
|
||||
|
||||
Reference in New Issue
Block a user