Skip to content

Commit

Permalink
feat: add support for "Album Artist" (#1024)
Browse files Browse the repository at this point in the history
Fixes #339
  • Loading branch information
Feichtmeier authored Nov 16, 2024
1 parent 8dbaa98 commit 284fd66
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 49 deletions.
7 changes: 7 additions & 0 deletions lib/local_audio/local_audio_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class LocalAudioModel extends SafeChangeNotifier {

List<Audio>? get audios => _service.audios;
List<String>? get allArtists => _service.allArtists;
List<String>? get allAlbumArtists => _service.allAlbumArtists;
List<String>? get allGenres => _service.allGenres;
List<String>? get allAlbums => _service.allAlbums;

Expand All @@ -56,6 +57,12 @@ class LocalAudioModel extends SafeChangeNotifier {
]) =>
_service.findTitlesOfArtist(artist, audioFilter);

List<Audio>? findTitlesOfAlbumArtists(
String artist, [
AudioFilter audioFilter = AudioFilter.album,
]) =>
_service.findTitlesOfAlbumArtists(artist, audioFilter);

List<String>? findArtistsOfGenre(String genre) =>
_service.findArtistsOfGenre(genre);

Expand Down
68 changes: 59 additions & 9 deletions lib/local_audio/local_audio_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@ import '../extensions/media_file_x.dart';
import '../settings/settings_service.dart';
import 'local_cover_service.dart';

typedef LocalSearchResult = ({
List<Audio>? titles,
List<String>? artists,
List<String>? albums,
List<String>? genres,
List<String>? playlists,
});
class LocalSearchResult {
const LocalSearchResult({
required this.titles,
required this.artists,
required this.albumArtists,
required this.albums,
required this.genres,
required this.playlists,
});

final List<Audio>? titles;
final List<String>? artists;
final List<String>? albumArtists;
final List<String>? albums;
final List<String>? genres;
final List<String>? playlists;
}

class LocalAudioService {
final SettingsService? _settingsService;
Expand Down Expand Up @@ -63,6 +73,22 @@ class LocalAudioService {
_allArtists?.sort();
}

List<String>? _allAlbumArtists;
List<String>? get allAlbumArtists => _allAlbumArtists;
void _findAllAlbumArtists() {
if (_audios == null) return;
final albumArtists = <String>[];
for (var a in audios!) {
final albumArtist = a.albumArtist;
if (albumArtist?.isNotEmpty == true &&
!albumArtists.contains(albumArtist)) {
albumArtists.add(albumArtist!);
}
}
_allAlbumArtists = albumArtists;
_allAlbumArtists?.sort();
}

List<String>? _allGenres;
List<String>? get allGenres => _allGenres;
void _findAllGenres() {
Expand Down Expand Up @@ -144,6 +170,25 @@ class LocalAudioService {
return artistList != null ? List.from(artistList) : null;
}

List<Audio>? findTitlesOfAlbumArtists(
String albumArtist, [
AudioFilter audioFilter = AudioFilter.album,
]) {
final album = audios?.where(
(a) => a.albumArtist != null && a.albumArtist == albumArtist,
);

var albumArtistList = album?.toList();
if (albumArtistList != null) {
sortListByAudioFilter(
audioFilter: audioFilter,
audios: albumArtistList,
);
albumArtistList = splitByDiscs(albumArtistList).toList();
}
return albumArtistList != null ? List.from(albumArtistList) : null;
}

List<String>? findArtistsOfGenre(String genre) {
if (audios == null) return null;
final artistsOfGenre = <String>[];
Expand Down Expand Up @@ -190,9 +235,10 @@ class LocalAudioService {
LocalSearchResult? search(String? query) {
if (query == null) return null;
if (query.isEmpty) {
return (
return const LocalSearchResult(
titles: [],
artists: [],
albumArtists: [],
albums: [],
genres: [],
playlists: [],
Expand Down Expand Up @@ -227,7 +273,7 @@ class LocalAudioService {
}
}

return (
return LocalSearchResult(
titles: audios
?.where(
(audio) =>
Expand All @@ -241,6 +287,9 @@ class LocalAudioService {
artists: allArtists
?.where((a) => a.toLowerCase().contains(query.toLowerCase()))
.toList(),
albumArtists: allAlbumArtists
?.where((a) => a.toLowerCase().contains(query.toLowerCase()))
.toList(),
playlists: [],
);
}
Expand All @@ -259,6 +308,7 @@ class LocalAudioService {
_failedImports = result.failedImports;
_sortAllTitles();
_findAllArtists();
_findAllAlbumArtists();
findAllAlbums();
_findAllGenres();
_audiosController.add(true);
Expand Down
73 changes: 73 additions & 0 deletions lib/local_audio/view/artists_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,76 @@ class ArtistsView extends StatelessWidget {
);
}
}

class AlbumArtistsView extends StatelessWidget {
const AlbumArtistsView({
super.key,
this.albumArtists,
this.noResultMessage,
this.noResultIcon,
});

final List<String>? albumArtists;
final Widget? noResultMessage, noResultIcon;

@override
Widget build(BuildContext context) {
if (albumArtists == null) {
return const SliverFillRemainingProgress();
}

if (albumArtists!.isEmpty) {
return SliverFillNoSearchResultPage(
icon: noResultIcon,
message: noResultMessage,
);
}
final model = di<LocalAudioModel>();

return SliverGrid.builder(
itemCount: albumArtists!.length,
gridDelegate: kDiskGridDelegate,
itemBuilder: (context, index) {
final albumArtistNames = albumArtists!.elementAt(index);
final albumArtistsAudios =
model.findTitlesOfAlbumArtists(albumArtistNames);

final text = albumArtists!.elementAt(index);

return YaruSelectableContainer(
selected: false,
onTap: () {
final artist = albumArtistsAudios?.firstOrNull?.artist;
if (artist == null) {
showSnackBar(
context: context,
content: Text(context.l10n.unknown),
);
} else {
di<LibraryModel>().push(
builder: (_) => ArtistPage(artistAudios: albumArtistsAudios),
pageId: artist,
);
}
},
borderRadius: BorderRadius.circular(300),
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: double.infinity,
height: double.infinity,
child: ArtistRoundImageContainer(
artistAudios: albumArtistsAudios,
height: audioCardDimension,
width: audioCardDimension,
),
),
ArtistVignette(text: text),
],
),
);
},
);
}
}
59 changes: 32 additions & 27 deletions lib/local_audio/view/local_audio_body.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,47 @@ class LocalAudioBody extends StatelessWidget {
required this.playlists,
this.noResultMessage,
this.noResultIcon,
required this.albumArtists,
});

final LocalAudioView localAudioView;
final List<Audio>? titles;
final List<String>? artists;
final List<String>? albumArtists;
final List<String>? albums;
final List<String>? genres;
final List<String>? playlists;
final Widget? noResultMessage, noResultIcon;

@override
Widget build(BuildContext context) {
return switch (localAudioView) {
LocalAudioView.titles => TitlesView(
audios: titles,
noResultMessage: noResultMessage,
noResultIcon: noResultIcon,
),
LocalAudioView.artists => ArtistsView(
artists: artists,
noResultMessage: noResultMessage,
noResultIcon: noResultIcon,
),
LocalAudioView.albums => AlbumsView(
albums: albums,
noResultMessage: noResultMessage,
noResultIcon: noResultIcon,
),
LocalAudioView.genres => GenresView(
genres: genres,
noResultMessage: noResultMessage,
noResultIcon: noResultIcon,
),
LocalAudioView.playlists => PlaylistsView(
playlists: playlists ?? [],
)
};
}
Widget build(BuildContext context) => switch (localAudioView) {
LocalAudioView.titles => TitlesView(
audios: titles,
noResultMessage: noResultMessage,
noResultIcon: noResultIcon,
),
LocalAudioView.artists => ArtistsView(
artists: artists,
noResultMessage: noResultMessage,
noResultIcon: noResultIcon,
),
LocalAudioView.albumArtists => AlbumArtistsView(
albumArtists: albumArtists,
noResultMessage: noResultMessage,
noResultIcon: noResultIcon,
),
LocalAudioView.albums => AlbumsView(
albums: albums,
noResultMessage: noResultMessage,
noResultIcon: noResultIcon,
),
LocalAudioView.genres => GenresView(
genres: genres,
noResultMessage: noResultMessage,
noResultIcon: noResultIcon,
),
LocalAudioView.playlists => PlaylistsView(
playlists: playlists ?? [],
)
};
}
4 changes: 4 additions & 0 deletions lib/local_audio/view/local_audio_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class _LocalAudioPageState extends State<LocalAudioPage> {
Widget build(BuildContext context) {
final audios = watchPropertyValue((LocalAudioModel m) => m.audios);
final allArtists = watchPropertyValue((LocalAudioModel m) => m.allArtists);
final allAlbumArtists =
watchPropertyValue((LocalAudioModel m) => m.allAlbumArtists);

final allAlbums = watchPropertyValue((LocalAudioModel m) => m.allAlbums);
final allGenres = watchPropertyValue((LocalAudioModel m) => m.allGenres);
final playlists =
Expand Down Expand Up @@ -97,6 +100,7 @@ class _LocalAudioPageState extends State<LocalAudioPage> {
titles: audios,
albums: allAlbums,
artists: allArtists,
albumArtists: allAlbumArtists,
genres: allGenres,
playlists: playlists,
noResultIcon: const AnimatedEmoji(AnimatedEmojis.bird),
Expand Down
18 changes: 9 additions & 9 deletions lib/local_audio/view/local_audio_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import '../../l10n/l10n.dart';
enum LocalAudioView {
titles,
artists,
albumArtists,
albums,
genres,
playlists;

String localize(AppLocalizations l10n) {
return switch (this) {
titles => l10n.titles,
artists => l10n.artists,
albums => l10n.albums,
genres => l10n.genres,
playlists => l10n.playlists,
};
}
String localize(AppLocalizations l10n) => switch (this) {
titles => l10n.titles,
artists => l10n.artists,
albumArtists => l10n.albumArtists,
albums => l10n.albums,
genres => l10n.genres,
playlists => l10n.playlists,
};
}
11 changes: 7 additions & 4 deletions lib/search/search_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,19 @@ class SearchModel extends SafeChangeNotifier {
Future<LocalSearchResult?> localSearch(String? query) async {
await Future.delayed(const Duration(microseconds: 1));
final search = _localAudioService.search(_searchQuery);
return (
albums: search?.albums,
return LocalSearchResult(
titles: search?.titles,
genres: search?.genres,
artists: search?.artists,
albumArtists: search?.albumArtists,
albums: search?.albums,
genres: search?.genres,
playlists: (query != null && query.isNotEmpty)
? _libraryService.playlists.keys
.where(
(e) => e.toLowerCase().contains(query.toLowerCase()),
)
.toList()
: null
: null,
);
}

Expand Down Expand Up @@ -311,6 +312,8 @@ class SearchModel extends SafeChangeNotifier {
setSearchType(SearchType.localAlbum);
} else if (localSearchResult?.artists?.isNotEmpty == true) {
setSearchType(SearchType.localArtist);
} else if (localSearchResult?.albumArtists?.isNotEmpty == true) {
setSearchType(SearchType.localAlbumArtist);
} else if (localSearchResult?.genres?.isNotEmpty == true) {
setSearchType(SearchType.localGenreName);
} else if (localSearchResult?.playlists?.isNotEmpty == true) {
Expand Down
2 changes: 2 additions & 0 deletions lib/search/search_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import '../l10n/l10n.dart';
enum SearchType {
localTitle,
localArtist,
localAlbumArtist,
localAlbum,
localGenreName,
localPlaylists,
Expand All @@ -16,6 +17,7 @@ enum SearchType {
String localize(AppLocalizations l10n) => switch (this) {
localTitle => l10n.titles,
localArtist => l10n.artists,
localAlbumArtist => l10n.albumArtists,
localAlbum => l10n.albums,
localGenreName => l10n.genres,
localPlaylists => l10n.playlists,
Expand Down
Loading

0 comments on commit 284fd66

Please sign in to comment.