Add 'genres' page/function to Android Auto (#505)
* Add 'genres' page/function to Android Auto * Add 'genres' string to multilingual files that use aa_tab_titles and aa_tab_values * Updated USAGE.md * Add preference to shuffle songs on the 'genre' page * Updated USAGE.md
This commit is contained in:
1
USAGE.md
1
USAGE.md
@@ -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.
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
11
app/src/main/res/drawable/ic_aa_genres.xml
Normal file
11
app/src/main/res/drawable/ic_aa_genres.xml
Normal 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>
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user