From bd6e2b4472751b88e2a02c76d6a2e2bcd1969f9a Mon Sep 17 00:00:00 2001 From: zarzet Date: Tue, 21 Jul 2026 07:35:03 +0700 Subject: [PATCH] fix(ui): restore plain loading skeleton on extension album screen The hero-era loading machinery survived the hero-flight revert: the loading scaffold rendered the real cover, _RouteSettled deferred the swap until the push transition ended, and SkeletonCrossfade faded between the two. The loaded header decodes the cover under a different mem-cache key (viewport-sized vs full-res), so opening an album from the artist screen flashed the cover: shown, blanked, shown again. Restore the pre-hero flow: shimmer skeleton while loading, hard cut to AlbumScreen so the cover appears exactly once. Removes the now-unused _AlbumLoadingScaffold and three imports that only existed for it. --- lib/screens/home_tab.dart | 3 - lib/screens/home_tab_widgets.dart | 116 ++++++------------------------ 2 files changed, 21 insertions(+), 98 deletions(-) diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index fe63eed8..c2fdd75d 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -17,9 +17,6 @@ import 'package:spotiflac_android/providers/local_library_provider.dart'; import 'package:spotiflac_android/screens/track_metadata_screen.dart'; import 'package:spotiflac_android/screens/album_screen.dart'; import 'package:spotiflac_android/screens/artist_screen.dart'; -import 'package:spotiflac_android/screens/collapsing_header_scroll_mixin.dart'; -import 'package:spotiflac_android/utils/cover_art_utils.dart'; -import 'package:spotiflac_android/widgets/album_detail_header.dart'; import 'package:spotiflac_android/services/csv_import_service.dart'; import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart'; import 'package:spotiflac_android/services/platform_bridge.dart'; diff --git a/lib/screens/home_tab_widgets.dart b/lib/screens/home_tab_widgets.dart index bb65b37e..f1af08b5 100644 --- a/lib/screens/home_tab_widgets.dart +++ b/lib/screens/home_tab_widgets.dart @@ -751,69 +751,6 @@ mixin _RouteSettled on State { } } -/// Loading state for [ExtensionAlbumScreen]: the real collection header -/// (title + cover already in their final slots) above a shimmering track -/// list, so arriving content barely shifts the page. -class _AlbumLoadingScaffold extends StatefulWidget { - final String title; - final String? coverUrl; - - const _AlbumLoadingScaffold({required this.title, required this.coverUrl}); - - @override - State<_AlbumLoadingScaffold> createState() => _AlbumLoadingScaffoldState(); -} - -class _AlbumLoadingScaffoldState extends State<_AlbumLoadingScaffold> - with CollapsingHeaderScrollMixin<_AlbumLoadingScaffold> { - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final url = widget.coverUrl; - Widget cover() => url != null && url.isNotEmpty - ? CachedCoverImage( - imageUrl: highResCoverUrl(url) ?? url, - fit: BoxFit.cover, - ) - : Container( - color: colorScheme.surfaceContainerHighest, - child: Icon( - Icons.album, - size: 48, - color: colorScheme.onSurfaceVariant, - ), - ); - - return Scaffold( - body: CustomScrollView( - controller: scrollController, - slivers: [ - AlbumDetailHeader( - title: widget.title, - expandedHeight: calculateExpandedHeight(context), - showTitleInAppBar: showTitleInAppBar, - background: cover(), - coverBuilder: (context, coverSize) => cover(), - // Placeholder rows sized like the loaded header's subtitle - // (16pt artist text ≈ 22px), meta (13pt row ≈ 18px), and 48px - // action row, so the cover doesn't shift when content lands. - subtitle: const ShimmerLoading( - child: SkeletonBox(width: 120, height: 22, borderRadius: 4), - ), - meta: const ShimmerLoading( - child: SkeletonBox(width: 150, height: 18, borderRadius: 6), - ), - actions: const ShimmerLoading( - child: SkeletonBox(width: 220, height: 48, borderRadius: 24), - ), - ), - const SliverToBoxAdapter(child: AlbumTrackListSkeleton(itemCount: 8)), - ], - ), - ); - } -} - /// Loading state for [ExtensionArtistScreen]: the real full-bleed header with /// the cover and artist name above the discography skeleton. class _ArtistLoadingScaffold extends StatelessWidget { @@ -952,8 +889,7 @@ class ExtensionAlbumScreen extends ConsumerStatefulWidget { _ExtensionAlbumScreenState(); } -class _ExtensionAlbumScreenState extends ConsumerState - with _RouteSettled { +class _ExtensionAlbumScreenState extends ConsumerState { List? _tracks; bool _isLoading = true; String? _error; @@ -1086,40 +1022,30 @@ class _ExtensionAlbumScreenState extends ConsumerState @override Widget build(BuildContext context) { - if (_error != null) { - return SkeletonCrossfade( - child: _LoadingOrErrorScaffold( - title: widget.albumName, - isLoading: false, - error: _error, - loadingBody: const SizedBox.shrink(), - onRetry: _fetchTracks, + if (_isLoading || _error != null) { + return _LoadingOrErrorScaffold( + title: widget.albumName, + isLoading: _isLoading, + error: _error, + loadingBody: const AlbumTrackListSkeleton( + itemCount: 10, + showCoverHeader: true, ), + onRetry: _fetchTracks, ); } - if (_isLoading || !routeSettled) { - return SkeletonCrossfade( - child: _AlbumLoadingScaffold( - title: widget.albumName, - coverUrl: widget.coverUrl, - ), - ); - } - - return SkeletonCrossfade( - child: AlbumScreen( - albumId: widget.albumId, - albumName: widget.albumName, - coverUrl: widget.coverUrl, - headerVideoUrl: _headerVideoUrl, - headerImageUrl: _headerImageUrl, - audioTraits: _audioTraits, - tracks: _tracks, - extensionId: widget.extensionId, - artistId: _artistId, - artistName: _artistName, - ), + return AlbumScreen( + albumId: widget.albumId, + albumName: widget.albumName, + coverUrl: widget.coverUrl, + headerVideoUrl: _headerVideoUrl, + headerImageUrl: _headerImageUrl, + audioTraits: _audioTraits, + tracks: _tracks, + extensionId: widget.extensionId, + artistId: _artistId, + artistName: _artistName, ); } }