Add 'genres' page/function to Android Auto

This commit is contained in:
Jorilx
2026-03-15 15:40:05 +01:00
parent b6e75afe12
commit 7661d7aa4d
5 changed files with 125 additions and 2 deletions

View File

@@ -35,6 +35,7 @@ import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.subsonic.models.Genre;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
@@ -952,6 +953,94 @@ public class AutomotiveRepository {
thread.start();
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getGenres(String prefix) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getBrowsingClient()
.getGenres()
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getGenres() != null && response.body().getSubsonicResponse().getGenres().getGenres() != null) {
List<Genre> genres = response.body().getSubsonicResponse().getGenres().getGenres();
// Sort genres alphabetically by name
genres.sort((g1, g2) -> {
String name1 = g1.getGenre() != null ? g1.getGenre() : "";
String name2 = g2.getGenre() != null ? g2.getGenre() : "";
return name1.compareToIgnoreCase(name2);
});
List<MediaItem> mediaItems = new ArrayList<>();
for (Genre genre : genres) {
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(genre.getGenre())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(prefix + genre.getGenre())
.setMediaMetadata(mediaMetadata)
.setUri("")
.build();
mediaItems.add(mediaItem);
}
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getSongsByGenre(String genre, int count) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getSongsByGenre(genre, count, 0)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null && response.body().getSubsonicResponse().getSongsByGenre().getSongs() != null) {
List<com.cappielloantonio.tempo.subsonic.models.Child> songs = response.body().getSubsonicResponse().getSongsByGenre().getSongs();
setChildrenMetadata(songs);
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(songs);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
private static class GetMediaItemThreadSafe implements Runnable {
private final SessionMediaItemDao sessionMediaItemDao;
private final String id;

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="24dp">
<path
android:fillColor="@android:color/white"
android:pathData="M480,660Q555,660 607.5,607.5Q660,555 660,480Q660,405 607.5,352.5Q555,300 480,300Q405,300 352.5,352.5Q300,405 300,480Q300,555 352.5,607.5Q405,660 480,660ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880Z"/>
</vector>

View File

@@ -310,8 +310,9 @@
<!-- <item>For you</item> -->
<item>Star tracks</item>
<item>Star albums</item>
<item>Star artistes</item>
<item>Star artists</item>
<item>Random</item>
<item>Genres</item>
</string-array>
<string-array name="aa_tab_values">
<item>-1</item>
@@ -331,6 +332,7 @@
<item>13</item>
<item>14</item>
<item>15</item>
<item>16</item>
</string-array>
<!-- end Add by MFO -->

View File

@@ -11,6 +11,7 @@
<string name="aa_podcast">Podcast</string>
<string name="aa_radio">Radio</string>
<string name="aa_random">Random</string>
<string name="aa_genres">Genres</string>
<string name="aa_recent_albums">Recent</string>
<string name="aa_song_recently_played">Song played</string>
<string name="aa_starred_albums">★ Albums</string>

View File

@@ -50,6 +50,7 @@ object MediaBrowserTree {
private const val STARRED_ARTISTS_ID = "[starredArtistsID]"
private const val RANDOM_ID = "[randomID]"
private const val FOLDER_ID = "[folderID]"
private const val GENRES_ID = "[genresID]"
// System functions
private const val INDEX_ID = "[indexID]"
@@ -178,7 +179,8 @@ object MediaBrowserTree {
STARRED_TRACKS_ID,
STARRED_ALBUMS_ID,
STARRED_ARTISTS_ID,
RANDOM_ID
RANDOM_ID,
GENRES_ID
)
// Root level
@@ -419,6 +421,19 @@ object MediaBrowserTree {
)
)
treeNodes[GENRES_ID] =
MediaItemNode(
buildMediaItem(
gridView = albumView,
title = appContext.getString(R.string.aa_genres),
mediaId = GENRES_ID,
isPlayable = false,
isBrowsable = true,
imageUri = iconUri(R.drawable.ic_aa_genres),
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
val root = treeNodes[ROOT_ID]!!
val selectedIds = mutableSetOf<String>()
@@ -474,6 +489,7 @@ object MediaBrowserTree {
STARRED_ALBUMS_ID -> automotiveRepository.getStarredAlbums(id)
STARRED_ARTISTS_ID -> automotiveRepository.getStarredArtists(id)
RANDOM_ID -> automotiveRepository.getRandomSongs(100)
GENRES_ID -> automotiveRepository.getGenres(id)
else -> {
if (id.startsWith(LAST_PLAYED_ID)) {
@@ -512,6 +528,10 @@ object MediaBrowserTree {
return automotiveRepository.getArtistAlbum(STARRED_ALBUMS_ID,id.removePrefix(STARRED_ARTISTS_ID))
}
if (id.startsWith(GENRES_ID)) {
return automotiveRepository.getSongsByGenre(id.removePrefix(GENRES_ID), 100)
}
if (id.startsWith(PLAYLIST_ID)) {
return automotiveRepository.getPlaylistSongs(id.removePrefix(PLAYLIST_ID))
}