Merge branch 'aa-genres' into bleeding-edge

This commit is contained in:
eddyizm
2026-03-23 22:26:51 -07:00
12 changed files with 178 additions and 3 deletions

View File

@@ -203,6 +203,7 @@ The Android Auto interface can be configured by user to best suit their preferen
- Star albums
- Star artists
- Random : 100 random songs
- Genres : 500 songs of the chosen genre OR 100 random songs if "shuffle genre songs" is selected
If all tabs are set to "Do not display", then "Home" tab will be created with all functions inside.

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,116 @@ 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, boolean shuffle) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
Call<ApiResponse> call;
if (shuffle) {
call = App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getRandomSongs(count, null, null, genre);
} else {
call = App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getSongsByGenre(genre, count, 0);
}
call.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null) {
List<com.cappielloantonio.tempo.subsonic.models.Child> songs;
if (shuffle) {
songs = response.body().getSubsonicResponse().getRandomSongs() != null
? response.body().getSubsonicResponse().getRandomSongs().getSongs()
: null;
} else {
songs = response.body().getSubsonicResponse().getSongsByGenre() != null
? response.body().getSubsonicResponse().getSongsByGenre().getSongs()
: null;
}
if (songs != null) {
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));
}
} 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) {
return getSongsByGenre(genre, count, false);
}
private static class GetMediaItemThreadSafe implements Runnable {
private final SessionMediaItemDao sessionMediaItemDao;
private final String id;

View File

@@ -102,6 +102,7 @@ object Preferences {
private const val AA_SECOND_TAB = "androidauto_second_tab"
private const val AA_THIRD_TAB = "androidauto_third_tab"
private const val AA_FOURTH_TAB = "androidauto_fourth_tab"
private const val AA_SHUFFLE_GENRE_SONGS = "androidauto_shuffle_genre_songs"
@JvmStatic
fun getServer(): String? {
@@ -818,4 +819,14 @@ object Preferences {
return App.getInstance().preferences.getString(AA_FOURTH_TAB, "3")!!.toInt()
}
@JvmStatic
fun isAndroidAutoShuffleGenreSongsEnabled(): Boolean {
return App.getInstance().preferences.getBoolean(AA_SHUFFLE_GENRE_SONGS, false)
}
@JvmStatic
fun setAndroidAutoShuffleGenreSongsEnabled(enabled: Boolean) {
App.getInstance().preferences.edit().putBoolean(AA_SHUFFLE_GENRE_SONGS, enabled).apply()
}
}

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

@@ -288,6 +288,7 @@
<item>Albums favoris</item>
<item>Artistes favoris</item>
<item>Aléatoire</item>
<item>Genres</item>
</string-array>
<string-array name="aa_tab_values">
<item>-1</item>
@@ -307,6 +308,7 @@
<item>13</item>
<item>14</item>
<item>15</item>
<item>16</item>
</string-array>
<!-- end Add by MFO -->

View File

@@ -338,6 +338,8 @@
<string name="settings_androidauto_second_tab">Affichage du deuxième onglet</string>
<string name="settings_androidauto_third_tab">Affichage du troisième onglet</string>
<string name="settings_androidauto_fourth_tab">Affichage du quatrième onglet</string>
<string name="settings_androidauto_shuffle_genre_songs">Mélanger les chansons par genre</string>
<string name="settings_androidauto_shuffle_genre_songs_summary">Lire des chansons aléatoires lors de la sélection d\'un genre</string>
<string name="settings_audio_transcode_download_format">Format de transcodage</string>
<string name="settings_audio_transcode_download_priority_summary">Si activé, Tempus ne forcera pas le téléchargement de la piste avec les paramètres de transcodage ci-dessous.</string>
<string name="settings_audio_transcode_download_priority_title">Prioriser les paramètres du serveurs, utilisés pour le streaming, dans les téléchargements</string>

View File

@@ -297,6 +297,7 @@
<item>Избранные альбомы</item>
<item>Избранные артисты</item>
<item>Случайное</item>
<item>Жанры</item>
</string-array>
<string-array name="aa_tab_values">
<item>-1</item>
@@ -316,6 +317,7 @@
<item>13</item>
<item>14</item>
<item>15</item>
<item>16</item>
</string-array>
<!-- end Add by MFO -->

View File

@@ -402,6 +402,8 @@
<string name="settings_androidauto_second_tab">Second tab display</string>
<string name="settings_androidauto_third_tab">Third tab display</string>
<string name="settings_androidauto_fourth_tab">Fourth tab display</string>
<string name="settings_androidauto_shuffle_genre_songs">Перемешивать треки по жанру</string>
<string name="settings_androidauto_shuffle_genre_songs_summary">Воспроизводить случайные треки при выборе жанра</string>
<string name="settings_audio_quality">Показывать качество аудио</string>
<string name="settings_audio_quality_summary">Битрейт и формат аудио будут отображаться для каждого трека.</string>
<string name="settings_song_rating">Показывать рейтинг трека</string>

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>
@@ -402,6 +403,8 @@
<string name="settings_androidauto_second_tab">Second tab display</string>
<string name="settings_androidauto_third_tab">Third tab display</string>
<string name="settings_androidauto_fourth_tab">Fourth tab display</string>
<string name="settings_androidauto_shuffle_genre_songs">Shuffle genre songs</string>
<string name="settings_androidauto_shuffle_genre_songs_summary">Play random songs when selecting a genre</string>
<string name="settings_audio_quality">Show audio quality</string>
<string name="settings_audio_quality_summary">The bitrate and audio format will be shown for each audio track.</string>
<string name="settings_song_rating">Show song star rating</string>

View File

@@ -519,7 +519,12 @@
app:entryValues="@array/aa_tab_values"
app:key="androidauto_fourth_tab"
app:title="@string/settings_androidauto_fourth_tab"
app:useSimpleSummaryProvider="true" />
app:useSimpleSummaryProvider="true" />
<SwitchPreference
android:title="@string/settings_androidauto_shuffle_genre_songs"
android:defaultValue="false"
android:key="androidauto_shuffle_genre_songs" />
</PreferenceCategory>
<!-- end Add by MFO -->

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,13 @@ object MediaBrowserTree {
return automotiveRepository.getArtistAlbum(STARRED_ALBUMS_ID,id.removePrefix(STARRED_ARTISTS_ID))
}
if (id.startsWith(GENRES_ID)) {
val shuffle = Preferences.isAndroidAutoShuffleGenreSongsEnabled()
// If the user doesn't want random songs, it's likely it's for perusing them, so provide as many as possible
val count = if (shuffle) 100 else 500
return automotiveRepository.getSongsByGenre(id.removePrefix(GENRES_ID), count, shuffle)
}
if (id.startsWith(PLAYLIST_ID)) {
return automotiveRepository.getPlaylistSongs(id.removePrefix(PLAYLIST_ID))
}