From b9e2bd63604f30c763e019743fa88b36b06e069f Mon Sep 17 00:00:00 2001 From: DJDoubleD <34967020+DJDoubleD@users.noreply.github.com> Date: Sun, 10 Nov 2024 01:35:02 +0100 Subject: [PATCH] LibraryAlbums optimizations - Created dedicated AlbumList widget to load & render online and offline albums independantly - Add extra null safety & error handling - Apply album sorting also to offline album list --- lib/ui/library.dart | 295 +++++++++++++++++++++++++------------------- 1 file changed, 167 insertions(+), 128 deletions(-) diff --git a/lib/ui/library.dart b/lib/ui/library.dart index 4d5b7e1..bbed623 100644 --- a/lib/ui/library.dart +++ b/lib/ui/library.dart @@ -262,8 +262,8 @@ class LibraryTracks extends StatefulWidget { } class _LibraryTracksState extends State { - bool _loading = false; - bool _loadingTracks = false; + bool _isLoading = false; + bool _isLoadingTracks = false; final ScrollController _scrollController = ScrollController(); List tracks = []; List allTracks = []; @@ -327,7 +327,7 @@ class _LibraryTracksState extends State { await Connectivity().checkConnectivity(); if (connectivity.isNotEmpty && !connectivity.contains(ConnectivityResult.none)) { - if (mounted) setState(() => _loading = true); + if (mounted) setState(() => _isLoading = true); int pos = tracks.length; if (tracks.isEmpty) { @@ -343,7 +343,7 @@ class _LibraryTracksState extends State { } //Error loading if (favPlaylist == null) { - if (mounted) setState(() => _loading = false); + if (mounted) setState(() => _isLoading = false); return; } //Update @@ -352,15 +352,15 @@ class _LibraryTracksState extends State { trackCount = favPlaylist!.trackCount; if (tracks.isEmpty) tracks = favPlaylist.tracks!; _makeFavorite(); - _loading = false; + _isLoading = false; }); } return; } //Load another page of tracks from deezer - if (_loadingTracks) return; - _loadingTracks = true; + if (_isLoadingTracks) return; + _isLoadingTracks = true; List? t; try { @@ -380,8 +380,8 @@ class _LibraryTracksState extends State { setState(() { tracks.addAll(t!); _makeFavorite(); - _loading = false; - _loadingTracks = false; + _isLoading = false; + _isLoadingTracks = false; }); } } @@ -577,7 +577,7 @@ class _LibraryTracksState extends State { }, ); }), - if (_loading) + if (_isLoading) const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -629,59 +629,16 @@ class LibraryAlbums extends StatefulWidget { } class _LibraryAlbumsState extends State { - List? _albums; Sorting _sort = Sorting(sourceType: SortSourceTypes.ALBUMS); final ScrollController _scrollController = ScrollController(); - List get _sorted { - List albums = List.from(_albums ?? []); - if (albums.isNotEmpty) { - albums.sort((a, b) => a.favoriteDate!.compareTo(b.favoriteDate!)); - switch (_sort.type) { - case SortType.DEFAULT: - break; - case SortType.ALPHABETIC: - albums.sort((a, b) => - a.title!.toLowerCase().compareTo(b.title!.toLowerCase())); - break; - case SortType.ARTIST: - albums.sort((a, b) => a.artists![0].name! - .toLowerCase() - .compareTo(b.artists![0].name!.toLowerCase())); - break; - case SortType.RELEASE_DATE: - albums.sort((a, b) => DateTime.parse(a.releaseDate!) - .compareTo(DateTime.parse(b.releaseDate!))); - break; - default: - break; - } - } - //Reverse - if (_sort.reverse) return albums.reversed.toList(); - return albums; + Future> _loadOnlineAlbums() async { + if (settings.offlineMode) return []; + return await deezerAPI.getAlbums(); } - Future _load() async { - if (settings.offlineMode) return; - try { - List albums = await deezerAPI.getAlbums(); - if (mounted) setState(() => _albums = albums); - } catch (e) { - Logger.root.severe('Error loading albums: $e', StackTrace); - } - } - - @override - void initState() { - _load(); - //Load sorting - int? index = Sorting.index(SortSourceTypes.ALBUMS); - if (index != null) { - _sort = cache.sorts[index]; - } - - super.initState(); + Future> _loadOfflineAlbums() async { + return await downloadManager.getOfflineAlbums(); } Future _reverse() async { @@ -696,6 +653,16 @@ class _LibraryAlbumsState extends State { await cache.save(); } + @override + void initState() { + super.initState(); + //Load sorting + int? index = Sorting.index(SortSourceTypes.ALBUMS); + if (index != null) { + _sort = cache.sorts[index]; + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -755,83 +722,155 @@ class _LibraryAlbumsState extends State { child: ListView( controller: _scrollController, children: [ - Container( - height: 8.0, + Container(height: 8.0), + AlbumList( + loadAlbums: _loadOnlineAlbums, + sort: _sort, + ), + AlbumList( + loadAlbums: _loadOfflineAlbums, + sort: _sort, + offline: true, ), - if (!settings.offlineMode && _albums == null) - const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [CircularProgressIndicator()], - ), - if (_albums != null) - ...List.generate(_albums?.length ?? 0, (int i) { - Album a = _sorted[i]; - return AlbumTile( - a, - onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => AlbumDetails(a))); - }, - onHold: () async { - MenuSheet m = MenuSheet(); - m.defaultAlbumMenu(a, context: context, onRemove: () { - setState(() => _albums?.remove(a)); - }); - }, - ); - }), - FutureBuilder( - future: downloadManager.getOfflineAlbums(), - builder: (context, snapshot) { - if (snapshot.hasError || - !snapshot.hasData || - snapshot.data!.isEmpty) { - return const SizedBox( - height: 0, - width: 0, - ); - } - - List albums = snapshot.data!; - return Column( - children: [ - const FreezerDivider(), - Text( - 'Offline albums'.i18n, - textAlign: TextAlign.center, - style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 24.0), - ), - ...List.generate(albums.length, (i) { - Album a = albums[i]; - return AlbumTile( - a, - onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => AlbumDetails(a))); - }, - onHold: () async { - MenuSheet m = MenuSheet(); - m.defaultAlbumMenu(a, context: context, - onRemove: () { - setState(() { - albums.remove(a); - _albums?.remove(a); - }); - }); - }, - ); - }) - ], - ); - }, - ) ], ), )); } } +class AlbumList extends StatefulWidget { + final Future> Function() loadAlbums; + final Sorting sort; + final bool offline; + + const AlbumList({ + super.key, + required this.loadAlbums, + required this.sort, + this.offline = false, + }); + + @override + _AlbumListState createState() => _AlbumListState(); +} + +class _AlbumListState extends State { + List _albums = []; + bool _isLoading = true; + + @override + void initState() { + super.initState(); + _loadAlbums(); + } + + Future _loadAlbums() async { + setState(() => _isLoading = true); + try { + _albums = await widget.loadAlbums(); + } catch (e) { + Logger.root.severe('Error loading albums: $e', StackTrace.current); + } finally { + if (mounted) setState(() => _isLoading = false); + } + } + + List get _sortedAlbums => _sortAlbums(_albums); + + List _sortAlbums(List albums) { + List sortedAlbums = List.from(albums); + if (sortedAlbums.isNotEmpty) { + sortedAlbums + .sort((a, b) => _compareDates(a.favoriteDate, b.favoriteDate)); + + switch (widget.sort.type) { + case SortType.DEFAULT: + break; + case SortType.ALPHABETIC: + sortedAlbums.sort((a, b) => (a.title ?? '') + .toLowerCase() + .compareTo((b.title ?? '').toLowerCase())); + break; + case SortType.ARTIST: + sortedAlbums.sort((a, b) => (a.artists?.firstOrNull?.name ?? '') + .toLowerCase() + .compareTo((b.artists?.firstOrNull?.name ?? '').toLowerCase())); + break; + case SortType.RELEASE_DATE: + sortedAlbums + .sort((a, b) => _compareDates(a.releaseDate, b.releaseDate)); + break; + default: + break; + } + } + return widget.sort.reverse ? sortedAlbums.reversed.toList() : sortedAlbums; + } + + int _compareDates(String? dateA, String? dateB) { + if (dateA == null && dateB == null) return 0; + if (dateA == null) return 1; + if (dateB == null) return -1; + return DateTime.parse(dateA).compareTo(DateTime.parse(dateB)); + } + + @override + void didUpdateWidget(covariant AlbumList oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.sort != widget.sort) { + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + if (_isLoading) { + return const Padding( + padding: EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [CircularProgressIndicator()], + ), + ); + } + return Column( + children: [ + if (widget.offline && _albums.isNotEmpty) ...[ + const FreezerDivider(), + Text( + 'Offline albums'.i18n, + textAlign: TextAlign.center, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24.0), + ), + ], + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: _sortedAlbums.length, + itemBuilder: (context, index) { + Album album = _sortedAlbums[index]; + return AlbumTile( + album, + onTap: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => AlbumDetails(album))); + }, + onHold: () { + MenuSheet m = MenuSheet(); + m.defaultAlbumMenu(album, context: context, onRemove: () { + setState(() { + _albums.remove(album); + }); + }); + }, + ); + }, + ), + ], + ); + } +} + class LibraryArtists extends StatefulWidget { const LibraryArtists({super.key});