V5 Push
Here's a list of changes/features: https://github.com/RetroMusicPlayer/RetroMusicPlayer/releases/tag/v5.0 Internal Changes: 1) Migrated to ViewBinding 2) Migrated to Glide V4 3) Migrated to kotlin version of Material Dialogs
This commit is contained in:
parent
fc42767031
commit
bce6dbfa27
421 changed files with 13285 additions and 5757 deletions
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.auto;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Beesham Sarendranauth (Beesham)
|
||||
*/
|
||||
public class AutoMediaIDHelper {
|
||||
|
||||
// Media IDs used on browseable items of MediaBrowser
|
||||
public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY_ROOT__";
|
||||
public static final String MEDIA_ID_ROOT = "__ROOT__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__"; // TODO
|
||||
public static final String MEDIA_ID_MUSICS_BY_HISTORY = "__BY_HISTORY__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_TOP_TRACKS = "__BY_TOP_TRACKS__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_SUGGESTIONS = "__BY_SUGGESTIONS__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_PLAYLIST = "__BY_PLAYLIST__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_ALBUM = "__BY_ALBUM__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_ARTIST = "__BY_ARTIST__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_ALBUM_ARTIST = "__BY_ALBUM_ARTIST__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_GENRE = "__BY_GENRE__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_SHUFFLE = "__BY_SHUFFLE__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_QUEUE = "__BY_QUEUE__";
|
||||
|
||||
private static final String CATEGORY_SEPARATOR = "__/__";
|
||||
private static final String LEAF_SEPARATOR = "__|__";
|
||||
|
||||
/**
|
||||
* Create a String value that represents a playable or a browsable media.
|
||||
* <p/>
|
||||
* Encode the media browseable categories, if any, and the unique music ID, if any,
|
||||
* into a single String mediaID.
|
||||
* <p/>
|
||||
* MediaIDs are of the form <categoryType>__/__<categoryValue>__|__<musicUniqueId>, to make it
|
||||
* easy to find the category (like genre) that a music was selected from, so we
|
||||
* can correctly build the playing queue. This is specially useful when
|
||||
* one music can appear in more than one list, like "by genre -> genre_1"
|
||||
* and "by artist -> artist_1".
|
||||
*
|
||||
* @param mediaID Unique ID for playable items, or null for browseable items.
|
||||
* @param categories Hierarchy of categories representing this item's browsing parents.
|
||||
* @return A hierarchy-aware media ID.
|
||||
*/
|
||||
public static String createMediaID(String mediaID, String... categories) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (categories != null) {
|
||||
for (int i = 0; i < categories.length; i++) {
|
||||
if (!isValidCategory(categories[i])) {
|
||||
throw new IllegalArgumentException("Invalid category: " + categories[i]);
|
||||
}
|
||||
sb.append(categories[i]);
|
||||
if (i < categories.length - 1) {
|
||||
sb.append(CATEGORY_SEPARATOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mediaID != null) {
|
||||
sb.append(LEAF_SEPARATOR).append(mediaID);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String extractCategory(@NonNull String mediaID) {
|
||||
int pos = mediaID.indexOf(LEAF_SEPARATOR);
|
||||
if (pos >= 0) {
|
||||
return mediaID.substring(0, pos);
|
||||
}
|
||||
return mediaID;
|
||||
}
|
||||
|
||||
public static String extractMusicID(@NonNull String mediaID) {
|
||||
int pos = mediaID.indexOf(LEAF_SEPARATOR);
|
||||
if (pos >= 0) {
|
||||
return mediaID.substring(pos + LEAF_SEPARATOR.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isBrowseable(@NonNull String mediaID) {
|
||||
return !mediaID.contains(LEAF_SEPARATOR);
|
||||
}
|
||||
|
||||
private static boolean isValidCategory(String category) {
|
||||
return category == null ||
|
||||
(!category.contains(CATEGORY_SEPARATOR) && !category.contains(LEAF_SEPARATOR));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
package code.name.monkey.retromusic.auto
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.support.v4.media.MediaBrowserCompat
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.model.CategoryInfo
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.repository.*
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Created by Beesham Sarendranauth (Beesham)
|
||||
*/
|
||||
class AutoMusicProvider(
|
||||
val mContext: Context,
|
||||
private val songsRepository: SongRepository,
|
||||
private val albumsRepository: AlbumRepository,
|
||||
private val artistsRepository: ArtistRepository,
|
||||
private val genresRepository: GenreRepository,
|
||||
private val playlistsRepository: PlaylistRepository,
|
||||
private val topPlayedRepository: TopPlayedRepository
|
||||
) {
|
||||
private var mMusicService: WeakReference<MusicService>? = null
|
||||
|
||||
fun setMusicService(service: MusicService) {
|
||||
mMusicService = WeakReference(service)
|
||||
}
|
||||
|
||||
fun getChildren(mediaId: String?, resources: Resources): List<MediaBrowserCompat.MediaItem> {
|
||||
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
|
||||
when (mediaId) {
|
||||
AutoMediaIDHelper.MEDIA_ID_ROOT -> {
|
||||
mediaItems.addAll(getRootChildren(resources))
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST -> for (playlist in playlistsRepository.playlists()) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST, playlist.id)
|
||||
.icon(R.drawable.ic_playlist_play)
|
||||
.title(playlist.name)
|
||||
.subTitle(playlist.getInfoString(mContext))
|
||||
.asPlayable()
|
||||
.build()
|
||||
)
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> for (album in albumsRepository.albums()) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.path(mediaId, album.id)
|
||||
.title(album.title)
|
||||
.subTitle(album.albumArtist ?: album.artistName)
|
||||
.icon(MusicUtil.getMediaStoreAlbumCoverUri(album.id))
|
||||
.asPlayable()
|
||||
.build()
|
||||
)
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST -> for (artist in artistsRepository.artists()) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
.path(mediaId, artist.id)
|
||||
.title(artist.name)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST -> for (artist in artistsRepository.albumArtists()) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
// we just pass album id here as we don't have album artist id's
|
||||
.path(mediaId, artist.safeGetFirstAlbum().id)
|
||||
.title(artist.name)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE -> for (genre in genresRepository.genres()) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
.path(mediaId, genre.id)
|
||||
.title(genre.name)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE ->
|
||||
mMusicService?.get()?.playingQueue
|
||||
?.let {
|
||||
for (song in it) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
.path(mediaId, song.id)
|
||||
.title(song.title)
|
||||
.subTitle(song.artistName)
|
||||
.icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
getPlaylistChildren(mediaId, mediaItems)
|
||||
}
|
||||
}
|
||||
return mediaItems
|
||||
}
|
||||
|
||||
private fun getPlaylistChildren(
|
||||
mediaId: String?,
|
||||
mediaItems: MutableList<MediaBrowserCompat.MediaItem>
|
||||
) {
|
||||
val songs = when (mediaId) {
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS -> {
|
||||
topPlayedRepository.topTracks()
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY -> {
|
||||
topPlayedRepository.recentlyPlayedTracks()
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS -> {
|
||||
topPlayedRepository.notRecentlyPlayedTracks().take(8)
|
||||
}
|
||||
else -> {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
songs.forEach { song ->
|
||||
mediaItems.add(
|
||||
getPlayableSong(mediaId, song)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRootChildren(resources: Resources): List<MediaBrowserCompat.MediaItem> {
|
||||
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
|
||||
val libraryCategories = PreferenceUtil.libraryCategory
|
||||
libraryCategories.forEach {
|
||||
if (it.visible) {
|
||||
when (it.category) {
|
||||
CategoryInfo.Category.Albums -> {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM)
|
||||
.gridLayout(true)
|
||||
.icon(R.drawable.ic_album)
|
||||
.title(resources.getString(R.string.albums)).build()
|
||||
)
|
||||
}
|
||||
CategoryInfo.Category.Artists -> {
|
||||
if (PreferenceUtil.albumArtistsOnly) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST)
|
||||
.icon(R.drawable.ic_album_artist)
|
||||
.title(resources.getString(R.string.album_artist)).build()
|
||||
)
|
||||
} else {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST)
|
||||
.icon(R.drawable.ic_artist)
|
||||
.title(resources.getString(R.string.artists)).build()
|
||||
)
|
||||
}
|
||||
}
|
||||
CategoryInfo.Category.Genres -> {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE)
|
||||
.icon(R.drawable.ic_guitar)
|
||||
.title(resources.getString(R.string.genres)).build()
|
||||
)
|
||||
}
|
||||
CategoryInfo.Category.Playlists -> {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST)
|
||||
.icon(R.drawable.ic_playlist_play)
|
||||
.title(resources.getString(R.string.playlists)).build()
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SHUFFLE)
|
||||
.icon(R.drawable.ic_shuffle)
|
||||
.title(resources.getString(R.string.action_shuffle_all))
|
||||
.subTitle(MusicUtil.getPlaylistInfoString(mContext, songsRepository.songs()))
|
||||
.build()
|
||||
)
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE)
|
||||
.icon(R.drawable.ic_queue_music)
|
||||
.title(resources.getString(R.string.queue))
|
||||
.subTitle(MusicUtil.getPlaylistInfoString(mContext, MusicPlayerRemote.playingQueue))
|
||||
.asBrowsable().build()
|
||||
)
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS)
|
||||
.icon(R.drawable.ic_trending_up)
|
||||
.title(resources.getString(R.string.my_top_tracks))
|
||||
.subTitle(
|
||||
MusicUtil.getPlaylistInfoString(
|
||||
mContext,
|
||||
topPlayedRepository.topTracks()
|
||||
)
|
||||
)
|
||||
.asBrowsable().build()
|
||||
)
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS)
|
||||
.icon(R.drawable.ic_face)
|
||||
.title(resources.getString(R.string.suggestion_songs))
|
||||
.subTitle(
|
||||
MusicUtil.getPlaylistInfoString(
|
||||
mContext,
|
||||
topPlayedRepository.notRecentlyPlayedTracks().takeIf {
|
||||
it.size > 9
|
||||
} ?: emptyList()
|
||||
)
|
||||
)
|
||||
.asBrowsable().build()
|
||||
)
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY)
|
||||
.icon(R.drawable.ic_history)
|
||||
.title(resources.getString(R.string.history))
|
||||
.subTitle(
|
||||
MusicUtil.getPlaylistInfoString(
|
||||
mContext,
|
||||
topPlayedRepository.recentlyPlayedTracks()
|
||||
)
|
||||
)
|
||||
.asBrowsable().build()
|
||||
)
|
||||
return mediaItems
|
||||
}
|
||||
|
||||
private fun getPlayableSong(mediaId: String?, song: Song): MediaBrowserCompat.MediaItem {
|
||||
return AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
.path(mediaId, song.id)
|
||||
.title(song.title)
|
||||
.subTitle(song.artistName)
|
||||
.icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId))
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package code.name.monkey.retromusic.auto
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.support.v4.media.MediaBrowserCompat
|
||||
import android.support.v4.media.MediaDescriptionCompat
|
||||
import code.name.monkey.retromusic.util.ImageUtil
|
||||
|
||||
|
||||
internal object AutoMediaItem {
|
||||
fun with(context: Context): Builder {
|
||||
return Builder(context)
|
||||
}
|
||||
|
||||
internal class Builder(val mContext: Context) {
|
||||
private var mBuilder: MediaDescriptionCompat.Builder?
|
||||
private var mFlags = 0
|
||||
fun path(fullPath: String): Builder {
|
||||
mBuilder?.setMediaId(fullPath)
|
||||
return this
|
||||
}
|
||||
|
||||
fun path(path: String?, id: Long): Builder {
|
||||
return path(AutoMediaIDHelper.createMediaID(id.toString(), path))
|
||||
}
|
||||
|
||||
fun title(title: String): Builder {
|
||||
mBuilder?.setTitle(title)
|
||||
return this
|
||||
}
|
||||
|
||||
fun subTitle(subTitle: String): Builder {
|
||||
mBuilder?.setSubtitle(subTitle)
|
||||
return this
|
||||
}
|
||||
|
||||
fun icon(uri: Uri?): Builder {
|
||||
mBuilder?.setIconUri(uri)
|
||||
return this
|
||||
}
|
||||
|
||||
fun icon(iconDrawableId: Int): Builder {
|
||||
mBuilder?.setIconBitmap(
|
||||
ImageUtil.createBitmap(
|
||||
ImageUtil.getVectorDrawable(
|
||||
mContext.resources,
|
||||
iconDrawableId,
|
||||
mContext.theme
|
||||
)
|
||||
)
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun gridLayout(isGrid: Boolean): Builder {
|
||||
|
||||
val hints = Bundle()
|
||||
hints.putBoolean(CONTENT_STYLE_SUPPORTED, true)
|
||||
hints.putInt(
|
||||
CONTENT_STYLE_BROWSABLE_HINT,
|
||||
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE
|
||||
)
|
||||
hints.putInt(
|
||||
CONTENT_STYLE_PLAYABLE_HINT,
|
||||
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE
|
||||
)
|
||||
mBuilder?.setExtras(hints)
|
||||
return this
|
||||
}
|
||||
|
||||
fun asBrowsable(): Builder {
|
||||
mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
return this
|
||||
}
|
||||
|
||||
fun asPlayable(): Builder {
|
||||
mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): MediaBrowserCompat.MediaItem {
|
||||
val result = MediaBrowserCompat.MediaItem(mBuilder!!.build(), mFlags)
|
||||
mBuilder = null
|
||||
mFlags = 0
|
||||
return result
|
||||
}
|
||||
|
||||
init {
|
||||
mBuilder = MediaDescriptionCompat.Builder()
|
||||
}
|
||||
companion object{
|
||||
// Hints - see https://developer.android.com/training/cars/media#default-content-style
|
||||
const val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED"
|
||||
const val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"
|
||||
const val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"
|
||||
const val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1
|
||||
const val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue