home: mirror tabs to mediasession browser
This commit is contained in:
parent
29d663f500
commit
3832c4e525
7 changed files with 175 additions and 124 deletions
|
@ -1,6 +1,6 @@
|
||||||
package org.oxycblt.auxio.home.list
|
package org.oxycblt.auxio.home
|
||||||
|
|
||||||
import org.oxycblt.auxio.home.HomeSettings
|
import org.oxycblt.auxio.home.tabs.Tab
|
||||||
import org.oxycblt.auxio.list.ListSettings
|
import org.oxycblt.auxio.list.ListSettings
|
||||||
import org.oxycblt.auxio.list.adapter.UpdateInstructions
|
import org.oxycblt.auxio.list.adapter.UpdateInstructions
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
|
@ -10,39 +10,46 @@ import org.oxycblt.auxio.music.MusicRepository
|
||||||
import org.oxycblt.auxio.music.MusicType
|
import org.oxycblt.auxio.music.MusicType
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.PlaybackSettings
|
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
interface HomeListGenerator {
|
interface HomeGenerator {
|
||||||
fun songs(): List<Song>
|
fun songs(): List<Song>
|
||||||
fun albums(): List<Album>
|
fun albums(): List<Album>
|
||||||
fun artists(): List<Artist>
|
fun artists(): List<Artist>
|
||||||
fun genres(): List<Genre>
|
fun genres(): List<Genre>
|
||||||
fun playlists(): List<Playlist>
|
fun playlists(): List<Playlist>
|
||||||
|
fun tabs(): List<MusicType>
|
||||||
fun release()
|
fun release()
|
||||||
|
|
||||||
interface Invalidator {
|
interface Invalidator {
|
||||||
fun invalidate(type: MusicType, instructions: UpdateInstructions)
|
fun invalidateMusic(type: MusicType, instructions: UpdateInstructions)
|
||||||
|
fun invalidateTabs()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(invalidator: Invalidator): HomeListGenerator
|
fun create(invalidator: Invalidator): HomeGenerator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HomeListGeneratorImpl(
|
private class HomeGeneratorImpl(
|
||||||
private val invalidator: HomeListGenerator.Invalidator,
|
private val invalidator: HomeGenerator.Invalidator,
|
||||||
private val homeSettings: HomeSettings,
|
private val homeSettings: HomeSettings,
|
||||||
private val listSettings: ListSettings,
|
private val listSettings: ListSettings,
|
||||||
private val musicRepository: MusicRepository,
|
private val musicRepository: MusicRepository,
|
||||||
) : HomeListGenerator, HomeSettings.Listener, ListSettings.Listener, MusicRepository.UpdateListener {
|
) : HomeGenerator, HomeSettings.Listener, ListSettings.Listener, MusicRepository.UpdateListener {
|
||||||
override fun songs() =
|
override fun songs() =
|
||||||
musicRepository.deviceLibrary?.let { listSettings.songSort.songs(it.songs) } ?: emptyList()
|
musicRepository.deviceLibrary?.let { listSettings.songSort.songs(it.songs) } ?: emptyList()
|
||||||
override fun albums() = musicRepository.deviceLibrary?.let { listSettings.albumSort.albums(it.albums) } ?: emptyList()
|
override fun albums() = musicRepository.deviceLibrary?.let { listSettings.albumSort.albums(it.albums) } ?: emptyList()
|
||||||
override fun artists() = musicRepository.deviceLibrary?.let { listSettings.artistSort.artists(it.artists) } ?: emptyList()
|
override fun artists() = musicRepository.deviceLibrary?.let { listSettings.artistSort.artists(it.artists) } ?: emptyList()
|
||||||
override fun genres() = musicRepository.deviceLibrary?.let { listSettings.genreSort.genres(it.genres) } ?: emptyList()
|
override fun genres() = musicRepository.deviceLibrary?.let { listSettings.genreSort.genres(it.genres) } ?: emptyList()
|
||||||
override fun playlists() = musicRepository.userLibrary?.let { listSettings.playlistSort.playlists(it.playlists) } ?: emptyList()
|
override fun playlists() = musicRepository.userLibrary?.let { listSettings.playlistSort.playlists(it.playlists) } ?: emptyList()
|
||||||
|
override fun tabs() =
|
||||||
|
homeSettings.homeTabs.filterIsInstance<Tab.Visible>().map { it.type }
|
||||||
|
|
||||||
|
|
||||||
|
override fun onTabsChanged() {
|
||||||
|
invalidator.invalidateTabs()
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
homeSettings.registerListener(this)
|
homeSettings.registerListener(this)
|
||||||
|
@ -51,9 +58,9 @@ private class HomeListGeneratorImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun release() {
|
override fun release() {
|
||||||
homeSettings.unregisterListener(this)
|
|
||||||
listSettings.unregisterListener(this)
|
|
||||||
musicRepository.removeUpdateListener(this)
|
musicRepository.removeUpdateListener(this)
|
||||||
|
listSettings.unregisterListener(this)
|
||||||
|
homeSettings.unregisterListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onHideCollaboratorsChanged() {
|
override fun onHideCollaboratorsChanged() {
|
||||||
|
@ -65,27 +72,27 @@ private class HomeListGeneratorImpl(
|
||||||
|
|
||||||
override fun onSongSortChanged() {
|
override fun onSongSortChanged() {
|
||||||
super.onSongSortChanged()
|
super.onSongSortChanged()
|
||||||
invalidator.invalidate(MusicType.SONGS, UpdateInstructions.Replace(0))
|
invalidator.invalidateMusic(MusicType.SONGS, UpdateInstructions.Replace(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAlbumSortChanged() {
|
override fun onAlbumSortChanged() {
|
||||||
super.onAlbumSortChanged()
|
super.onAlbumSortChanged()
|
||||||
invalidator.invalidate(MusicType.ALBUMS, UpdateInstructions.Replace(0))
|
invalidator.invalidateMusic(MusicType.ALBUMS, UpdateInstructions.Replace(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onArtistSortChanged() {
|
override fun onArtistSortChanged() {
|
||||||
super.onArtistSortChanged()
|
super.onArtistSortChanged()
|
||||||
invalidator.invalidate(MusicType.ARTISTS, UpdateInstructions.Replace(0))
|
invalidator.invalidateMusic(MusicType.ARTISTS, UpdateInstructions.Replace(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGenreSortChanged() {
|
override fun onGenreSortChanged() {
|
||||||
super.onGenreSortChanged()
|
super.onGenreSortChanged()
|
||||||
invalidator.invalidate(MusicType.GENRES, UpdateInstructions.Replace(0))
|
invalidator.invalidateMusic(MusicType.GENRES, UpdateInstructions.Replace(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaylistSortChanged() {
|
override fun onPlaylistSortChanged() {
|
||||||
super.onPlaylistSortChanged()
|
super.onPlaylistSortChanged()
|
||||||
invalidator.invalidate(MusicType.PLAYLISTS, UpdateInstructions.Replace(0))
|
invalidator.invalidateMusic(MusicType.PLAYLISTS, UpdateInstructions.Replace(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMusicChanges(changes: MusicRepository.Changes) {
|
override fun onMusicChanges(changes: MusicRepository.Changes) {
|
||||||
|
@ -94,16 +101,16 @@ private class HomeListGeneratorImpl(
|
||||||
logD("Refreshing library")
|
logD("Refreshing library")
|
||||||
// Get the each list of items in the library to use as our list data.
|
// Get the each list of items in the library to use as our list data.
|
||||||
// Applying the preferred sorting to them.
|
// Applying the preferred sorting to them.
|
||||||
invalidator.invalidate(MusicType.SONGS, UpdateInstructions.Diff)
|
invalidator.invalidateMusic(MusicType.SONGS, UpdateInstructions.Diff)
|
||||||
invalidator.invalidate(MusicType.ALBUMS, UpdateInstructions.Diff)
|
invalidator.invalidateMusic(MusicType.ALBUMS, UpdateInstructions.Diff)
|
||||||
invalidator.invalidate(MusicType.ARTISTS, UpdateInstructions.Diff)
|
invalidator.invalidateMusic(MusicType.ARTISTS, UpdateInstructions.Diff)
|
||||||
invalidator.invalidate(MusicType.GENRES, UpdateInstructions.Diff)
|
invalidator.invalidateMusic(MusicType.GENRES, UpdateInstructions.Diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
val userLibrary = musicRepository.userLibrary
|
val userLibrary = musicRepository.userLibrary
|
||||||
if (changes.userLibrary && userLibrary != null) {
|
if (changes.userLibrary && userLibrary != null) {
|
||||||
logD("Refreshing playlists")
|
logD("Refreshing playlists")
|
||||||
invalidator.invalidate(MusicType.PLAYLISTS, UpdateInstructions.Diff)
|
invalidator.invalidateMusic(MusicType.PLAYLISTS, UpdateInstructions.Diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,15 +23,14 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import org.oxycblt.auxio.home.list.HomeListGenerator
|
|
||||||
import org.oxycblt.auxio.home.tabs.Tab
|
import org.oxycblt.auxio.home.tabs.Tab
|
||||||
|
import org.oxycblt.auxio.home.tabs.TabListGenerator
|
||||||
import org.oxycblt.auxio.list.ListSettings
|
import org.oxycblt.auxio.list.ListSettings
|
||||||
import org.oxycblt.auxio.list.adapter.UpdateInstructions
|
import org.oxycblt.auxio.list.adapter.UpdateInstructions
|
||||||
import org.oxycblt.auxio.list.sort.Sort
|
import org.oxycblt.auxio.list.sort.Sort
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.MusicRepository
|
|
||||||
import org.oxycblt.auxio.music.MusicType
|
import org.oxycblt.auxio.music.MusicType
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
@ -50,12 +49,11 @@ import org.oxycblt.auxio.util.logD
|
||||||
class HomeViewModel
|
class HomeViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val homeSettings: HomeSettings,
|
|
||||||
private val listSettings: ListSettings,
|
private val listSettings: ListSettings,
|
||||||
private val playbackSettings: PlaybackSettings,
|
private val playbackSettings: PlaybackSettings,
|
||||||
homeGeneratorFactory: HomeListGenerator.Factory
|
homeGeneratorFactory: HomeGenerator.Factory
|
||||||
) : ViewModel(), HomeSettings.Listener, HomeListGenerator.Invalidator {
|
) : ViewModel(), HomeGenerator.Invalidator {
|
||||||
private val generator = homeGeneratorFactory.create(this)
|
private val homeGenerator = homeGeneratorFactory.create(this)
|
||||||
|
|
||||||
private val _songList = MutableStateFlow(listOf<Song>())
|
private val _songList = MutableStateFlow(listOf<Song>())
|
||||||
/** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */
|
/** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */
|
||||||
|
@ -138,7 +136,7 @@ constructor(
|
||||||
* A list of [MusicType] corresponding to the current [Tab] configuration, excluding invisible
|
* A list of [MusicType] corresponding to the current [Tab] configuration, excluding invisible
|
||||||
* [Tab]s.
|
* [Tab]s.
|
||||||
*/
|
*/
|
||||||
var currentTabTypes = makeTabTypes()
|
var currentTabTypes = homeGenerator.tabs()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private val _currentTabType = MutableStateFlow(currentTabTypes[0])
|
private val _currentTabType = MutableStateFlow(currentTabTypes[0])
|
||||||
|
@ -166,45 +164,38 @@ constructor(
|
||||||
val showOuter: Event<Outer>
|
val showOuter: Event<Outer>
|
||||||
get() = _showOuter
|
get() = _showOuter
|
||||||
|
|
||||||
init {
|
|
||||||
homeSettings.registerListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
homeSettings.unregisterListener(this)
|
homeGenerator.release()
|
||||||
generator.release()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate(type: MusicType, instructions: UpdateInstructions) {
|
override fun invalidateMusic(type: MusicType, instructions: UpdateInstructions) {
|
||||||
when (type) {
|
when (type) {
|
||||||
MusicType.SONGS -> {
|
MusicType.SONGS -> {
|
||||||
_songList.value = generator.songs()
|
_songList.value = homeGenerator.songs()
|
||||||
_songInstructions.put(instructions)
|
_songInstructions.put(instructions)
|
||||||
}
|
}
|
||||||
MusicType.ALBUMS -> {
|
MusicType.ALBUMS -> {
|
||||||
_albumList.value = generator.albums()
|
_albumList.value = homeGenerator.albums()
|
||||||
_albumInstructions.put(instructions)
|
_albumInstructions.put(instructions)
|
||||||
}
|
}
|
||||||
MusicType.ARTISTS -> {
|
MusicType.ARTISTS -> {
|
||||||
_artistList.value = generator.artists()
|
_artistList.value = homeGenerator.artists()
|
||||||
_artistInstructions.put(instructions)
|
_artistInstructions.put(instructions)
|
||||||
}
|
}
|
||||||
MusicType.GENRES -> {
|
MusicType.GENRES -> {
|
||||||
_genreList.value = generator.genres()
|
_genreList.value = homeGenerator.genres()
|
||||||
_genreInstructions.put(instructions)
|
_genreInstructions.put(instructions)
|
||||||
}
|
}
|
||||||
MusicType.PLAYLISTS -> {
|
MusicType.PLAYLISTS -> {
|
||||||
_playlistList.value = generator.playlists()
|
_playlistList.value = homeGenerator.playlists()
|
||||||
_playlistInstructions.put(instructions)
|
_playlistInstructions.put(instructions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTabsChanged() {
|
override fun invalidateTabs() {
|
||||||
// Tabs changed, update the current tabs and set up a re-create event.
|
currentTabTypes = homeGenerator.tabs()
|
||||||
currentTabTypes = makeTabTypes()
|
|
||||||
logD("Updating tabs: ${currentTabType.value}")
|
|
||||||
_shouldRecreate.put(Unit)
|
_shouldRecreate.put(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,15 +281,6 @@ constructor(
|
||||||
fun showAbout() {
|
fun showAbout() {
|
||||||
_showOuter.put(Outer.About)
|
_showOuter.put(Outer.About)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a list of [MusicType]s representing a simpler version of the [Tab] configuration.
|
|
||||||
*
|
|
||||||
* @return A list of the [MusicType]s for each visible [Tab] in the configuration, ordered in
|
|
||||||
* the same way as the configuration.
|
|
||||||
*/
|
|
||||||
private fun makeTabTypes() =
|
|
||||||
homeSettings.homeTabs.filterIsInstance<Tab.Visible>().map { it.type }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface Outer {
|
sealed interface Outer {
|
||||||
|
|
|
@ -37,40 +37,23 @@ class AdaptiveTabStrategy(context: Context, private val tabs: List<MusicType>) :
|
||||||
private val width = context.resources.configuration.smallestScreenWidthDp
|
private val width = context.resources.configuration.smallestScreenWidthDp
|
||||||
|
|
||||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||||
val icon: Int
|
val homeTab = tabs[position]
|
||||||
val string: Int
|
val icon = when (homeTab) {
|
||||||
|
MusicType.SONGS -> R.drawable.ic_song_24
|
||||||
when (tabs[position]) {
|
MusicType.ALBUMS -> R.drawable.ic_album_24
|
||||||
MusicType.SONGS -> {
|
MusicType.ARTISTS -> R.drawable.ic_artist_24
|
||||||
icon = R.drawable.ic_song_24
|
MusicType.GENRES -> R.drawable.ic_genre_24
|
||||||
string = R.string.lbl_songs
|
MusicType.PLAYLISTS -> R.drawable.ic_playlist_24
|
||||||
}
|
|
||||||
MusicType.ALBUMS -> {
|
|
||||||
icon = R.drawable.ic_album_24
|
|
||||||
string = R.string.lbl_albums
|
|
||||||
}
|
|
||||||
MusicType.ARTISTS -> {
|
|
||||||
icon = R.drawable.ic_artist_24
|
|
||||||
string = R.string.lbl_artists
|
|
||||||
}
|
|
||||||
MusicType.GENRES -> {
|
|
||||||
icon = R.drawable.ic_genre_24
|
|
||||||
string = R.string.lbl_genres
|
|
||||||
}
|
|
||||||
MusicType.PLAYLISTS -> {
|
|
||||||
icon = R.drawable.ic_playlist_24
|
|
||||||
string = R.string.lbl_playlists
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use expected sw* size thresholds when choosing a configuration.
|
// Use expected sw* size thresholds when choosing a configuration.
|
||||||
when {
|
when {
|
||||||
// On small screens, only display an icon.
|
// On small screens, only display an icon.
|
||||||
width < 370 -> tab.setIcon(icon).setContentDescription(string)
|
width < 370 -> tab.setIcon(icon).setContentDescription(homeTab.nameRes)
|
||||||
// On large screens, display an icon and text.
|
// On large screens, display an icon and text.
|
||||||
width < 600 -> tab.setText(string)
|
width < 600 -> tab.setText(homeTab.nameRes).setIcon(icon)
|
||||||
// On medium-size screens, display text.
|
// On medium-size screens, display text.
|
||||||
else -> tab.setIcon(icon).setText(string)
|
else -> tab.setIcon(icon).setText(homeTab.nameRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.oxycblt.auxio.music
|
package org.oxycblt.auxio.music
|
||||||
|
|
||||||
import org.oxycblt.auxio.IntegerTable
|
import org.oxycblt.auxio.IntegerTable
|
||||||
|
import org.oxycblt.auxio.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General configuration enum to control what kind of music is being worked with.
|
* General configuration enum to control what kind of music is being worked with.
|
||||||
|
@ -52,6 +53,16 @@ enum class MusicType {
|
||||||
PLAYLISTS -> IntegerTable.MUSIC_MODE_PLAYLISTS
|
PLAYLISTS -> IntegerTable.MUSIC_MODE_PLAYLISTS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val nameRes: Int
|
||||||
|
get() =
|
||||||
|
when (this) {
|
||||||
|
SONGS -> R.string.lbl_songs
|
||||||
|
ALBUMS -> R.string.lbl_albums
|
||||||
|
ARTISTS -> R.string.lbl_artists
|
||||||
|
GENRES -> R.string.lbl_genres
|
||||||
|
PLAYLISTS -> R.string.lbl_playlists
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Convert a [MusicType] integer representation into an instance.
|
* Convert a [MusicType] integer representation into an instance.
|
||||||
|
|
|
@ -44,8 +44,8 @@ import org.oxycblt.auxio.playback.formatDurationDs
|
||||||
import org.oxycblt.auxio.util.getPlural
|
import org.oxycblt.auxio.util.getPlural
|
||||||
|
|
||||||
sealed interface MediaSessionUID {
|
sealed interface MediaSessionUID {
|
||||||
data class CategoryItem(val category: Category) : MediaSessionUID {
|
data class Tab(val node: TabNode) : MediaSessionUID {
|
||||||
override fun toString() = "$ID_CATEGORY:${category.id}"
|
override fun toString() = "$ID_CATEGORY:${node.id}"
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SingleItem(val uid: Music.UID) : MediaSessionUID {
|
data class SingleItem(val uid: Music.UID) : MediaSessionUID {
|
||||||
|
@ -66,7 +66,7 @@ sealed interface MediaSessionUID {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return when (parts[0]) {
|
return when (parts[0]) {
|
||||||
ID_CATEGORY -> CategoryItem(Category.fromString(parts[1]) ?: return null)
|
ID_CATEGORY -> Tab(TabNode.fromString(parts[1]) ?: return null)
|
||||||
ID_ITEM -> {
|
ID_ITEM -> {
|
||||||
val uids = parts[1].split(">", limit = 2)
|
val uids = parts[1].split(">", limit = 2)
|
||||||
if (uids.size == 1) {
|
if (uids.size == 1) {
|
||||||
|
@ -148,12 +148,12 @@ private fun makeExtras(context: Context, vararg sugars: Sugar): Bundle {
|
||||||
return Bundle().apply { sugars.forEach { this.it(context) } }
|
return Bundle().apply { sugars.forEach { this.it(context) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Category.toMediaItem(context: Context): MediaItem {
|
fun TabNode.toMediaItem(context: Context): MediaItem {
|
||||||
val extras =
|
val extras =
|
||||||
makeExtras(
|
makeExtras(
|
||||||
context,
|
context,
|
||||||
style(MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM))
|
style(MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM))
|
||||||
val mediaSessionUID = MediaSessionUID.CategoryItem(this)
|
val mediaSessionUID = MediaSessionUID.Tab(this)
|
||||||
val description =
|
val description =
|
||||||
MediaDescriptionCompat.Builder()
|
MediaDescriptionCompat.Builder()
|
||||||
.setMediaId(mediaSessionUID.toString())
|
.setMediaId(mediaSessionUID.toString())
|
||||||
|
|
|
@ -23,7 +23,7 @@ import android.support.v4.media.MediaBrowserCompat.MediaItem
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.home.list.HomeListGenerator
|
import org.oxycblt.auxio.home.HomeGenerator
|
||||||
import org.oxycblt.auxio.list.ListSettings
|
import org.oxycblt.auxio.list.ListSettings
|
||||||
import org.oxycblt.auxio.list.adapter.UpdateInstructions
|
import org.oxycblt.auxio.list.adapter.UpdateInstructions
|
||||||
import org.oxycblt.auxio.list.sort.Sort
|
import org.oxycblt.auxio.list.sort.Sort
|
||||||
|
@ -35,8 +35,6 @@ import org.oxycblt.auxio.music.MusicRepository
|
||||||
import org.oxycblt.auxio.music.MusicType
|
import org.oxycblt.auxio.music.MusicType
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.device.DeviceLibrary
|
|
||||||
import org.oxycblt.auxio.music.user.UserLibrary
|
|
||||||
import org.oxycblt.auxio.search.SearchEngine
|
import org.oxycblt.auxio.search.SearchEngine
|
||||||
|
|
||||||
class MusicBrowser
|
class MusicBrowser
|
||||||
|
@ -46,13 +44,13 @@ constructor(
|
||||||
private val musicRepository: MusicRepository,
|
private val musicRepository: MusicRepository,
|
||||||
private val searchEngine: SearchEngine,
|
private val searchEngine: SearchEngine,
|
||||||
private val listSettings: ListSettings,
|
private val listSettings: ListSettings,
|
||||||
homeGeneratorFactory: HomeListGenerator.Factory
|
homeGeneratorFactory: HomeGenerator.Factory
|
||||||
) : MusicRepository.UpdateListener, HomeListGenerator.Invalidator {
|
) : MusicRepository.UpdateListener, HomeGenerator.Invalidator {
|
||||||
interface Invalidator {
|
interface Invalidator {
|
||||||
fun invalidateMusic(ids: Set<String>)
|
fun invalidateMusic(ids: Set<String>)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val generator = homeGeneratorFactory.create(this)
|
private val homeGenerator = homeGeneratorFactory.create(this)
|
||||||
private var invalidator: Invalidator? = null
|
private var invalidator: Invalidator? = null
|
||||||
|
|
||||||
fun attach(invalidator: Invalidator) {
|
fun attach(invalidator: Invalidator) {
|
||||||
|
@ -64,26 +62,24 @@ constructor(
|
||||||
musicRepository.removeUpdateListener(this)
|
musicRepository.removeUpdateListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate(type: MusicType, instructions: UpdateInstructions) {
|
override fun invalidateMusic(type: MusicType, instructions: UpdateInstructions) {
|
||||||
val category = when (type) {
|
val id = MediaSessionUID.Tab(TabNode.Home(type)).toString()
|
||||||
MusicType.SONGS -> Category.Songs
|
|
||||||
MusicType.ALBUMS -> Category.Albums
|
|
||||||
MusicType.ARTISTS -> Category.Artists
|
|
||||||
MusicType.GENRES -> Category.Genres
|
|
||||||
MusicType.PLAYLISTS -> Category.Playlists
|
|
||||||
}
|
|
||||||
val id = MediaSessionUID.CategoryItem(category).toString()
|
|
||||||
invalidator?.invalidateMusic(setOf(id))
|
invalidator?.invalidateMusic(setOf(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun invalidateTabs() {
|
||||||
|
for (i in 0..10) {
|
||||||
|
// TODO: Temporary bodge, move the amount parameter to a bundle extra
|
||||||
|
val rootId = MediaSessionUID.Tab(TabNode.Root(i)).toString()
|
||||||
|
val moreId = MediaSessionUID.Tab(TabNode.More(i)).toString()
|
||||||
|
invalidator?.invalidateMusic(setOf(rootId, moreId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onMusicChanges(changes: MusicRepository.Changes) {
|
override fun onMusicChanges(changes: MusicRepository.Changes) {
|
||||||
val deviceLibrary = musicRepository.deviceLibrary
|
val deviceLibrary = musicRepository.deviceLibrary
|
||||||
val invalidate = mutableSetOf<String>()
|
val invalidate = mutableSetOf<String>()
|
||||||
if (changes.deviceLibrary && deviceLibrary != null) {
|
if (changes.deviceLibrary && deviceLibrary != null) {
|
||||||
Category.DEVICE_MUSIC.forEach {
|
|
||||||
invalidate.add(MediaSessionUID.CategoryItem(it).toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceLibrary.albums.forEach {
|
deviceLibrary.albums.forEach {
|
||||||
val id = MediaSessionUID.SingleItem(it.uid).toString()
|
val id = MediaSessionUID.SingleItem(it.uid).toString()
|
||||||
invalidate.add(id)
|
invalidate.add(id)
|
||||||
|
@ -101,9 +97,6 @@ constructor(
|
||||||
}
|
}
|
||||||
val userLibrary = musicRepository.userLibrary
|
val userLibrary = musicRepository.userLibrary
|
||||||
if (changes.userLibrary && userLibrary != null) {
|
if (changes.userLibrary && userLibrary != null) {
|
||||||
Category.USER_MUSIC.forEach {
|
|
||||||
invalidate.add(MediaSessionUID.CategoryItem(it).toString())
|
|
||||||
}
|
|
||||||
userLibrary.playlists.forEach {
|
userLibrary.playlists.forEach {
|
||||||
val id = MediaSessionUID.SingleItem(it.uid).toString()
|
val id = MediaSessionUID.SingleItem(it.uid).toString()
|
||||||
invalidate.add(id)
|
invalidate.add(id)
|
||||||
|
@ -118,7 +111,7 @@ constructor(
|
||||||
fun getItem(mediaId: String): MediaItem? {
|
fun getItem(mediaId: String): MediaItem? {
|
||||||
val music =
|
val music =
|
||||||
when (val uid = MediaSessionUID.fromString(mediaId)) {
|
when (val uid = MediaSessionUID.fromString(mediaId)) {
|
||||||
is MediaSessionUID.CategoryItem -> return uid.category.toMediaItem(context)
|
is MediaSessionUID.Tab -> return uid.node.toMediaItem(context)
|
||||||
is MediaSessionUID.SingleItem ->
|
is MediaSessionUID.SingleItem ->
|
||||||
musicRepository.find(uid.uid)?.let { musicRepository.find(it.uid) }
|
musicRepository.find(uid.uid)?.let { musicRepository.find(it.uid) }
|
||||||
is MediaSessionUID.ChildItem ->
|
is MediaSessionUID.ChildItem ->
|
||||||
|
@ -186,8 +179,8 @@ constructor(
|
||||||
id: String
|
id: String
|
||||||
): List<MediaItem>? {
|
): List<MediaItem>? {
|
||||||
return when (val mediaSessionUID = MediaSessionUID.fromString(id)) {
|
return when (val mediaSessionUID = MediaSessionUID.fromString(id)) {
|
||||||
is MediaSessionUID.CategoryItem -> {
|
is MediaSessionUID.Tab -> {
|
||||||
getCategoryMediaItems(mediaSessionUID.category)
|
getCategoryMediaItems(mediaSessionUID.node)
|
||||||
}
|
}
|
||||||
is MediaSessionUID.SingleItem -> {
|
is MediaSessionUID.SingleItem -> {
|
||||||
getChildMediaItems(mediaSessionUID.uid)
|
getChildMediaItems(mediaSessionUID.uid)
|
||||||
|
@ -202,25 +195,30 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCategoryMediaItems(
|
private fun getCategoryMediaItems(
|
||||||
category: Category
|
node: TabNode
|
||||||
) =
|
) =
|
||||||
when (category) {
|
when (node) {
|
||||||
is Category.Root -> {
|
is TabNode.Root -> {
|
||||||
val base = Category.MUSIC.take(category.amount - 1)
|
val tabs = homeGenerator.tabs()
|
||||||
if (base.size < Category.MUSIC.size) {
|
val base = tabs.take(node.amount - 1).map { TabNode.Home(it) }
|
||||||
base + Category.More(Category.MUSIC.size - base.size)
|
if (base.size < tabs.size) {
|
||||||
|
base + TabNode.More(Category.MUSIC.size - base.size)
|
||||||
} else {
|
} else {
|
||||||
base
|
base
|
||||||
}
|
}
|
||||||
.map { it.toMediaItem(context) }
|
.map { it.toMediaItem(context) }
|
||||||
}
|
}
|
||||||
is Category.More ->
|
is TabNode.More ->
|
||||||
Category.MUSIC.takeLast(category.remainder).map { it.toMediaItem(context) }
|
homeGenerator.tabs().takeLast(node.remainder).map {
|
||||||
is Category.Songs -> generator.songs().map { it.toMediaItem(context) }
|
TabNode.Home(it).toMediaItem(context) }
|
||||||
is Category.Albums -> generator.albums().map { it.toMediaItem(context) }
|
is TabNode.Home ->
|
||||||
is Category.Artists -> generator.artists().map { it.toMediaItem(context) }
|
when (node.type) {
|
||||||
is Category.Genres -> generator.genres().map { it.toMediaItem(context) }
|
MusicType.SONGS -> homeGenerator.songs().map { it.toMediaItem(context, null) }
|
||||||
is Category.Playlists -> generator.playlists().map { it.toMediaItem(context) }
|
MusicType.ALBUMS -> homeGenerator.albums().map { it.toMediaItem(context) }
|
||||||
|
MusicType.ARTISTS -> homeGenerator.artists().map { it.toMediaItem(context) }
|
||||||
|
MusicType.GENRES -> homeGenerator.genres().map { it.toMediaItem(context) }
|
||||||
|
MusicType.PLAYLISTS -> homeGenerator.playlists().map { it.toMediaItem(context) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getChildMediaItems(uid: Music.UID): List<MediaItem>? {
|
private fun getChildMediaItems(uid: Music.UID): List<MediaItem>? {
|
||||||
|
|
70
app/src/main/java/org/oxycblt/auxio/music/service/TabNode.kt
Normal file
70
app/src/main/java/org/oxycblt/auxio/music/service/TabNode.kt
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package org.oxycblt.auxio.music.service
|
||||||
|
|
||||||
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.home.tabs.Tab
|
||||||
|
import org.oxycblt.auxio.music.MusicType
|
||||||
|
|
||||||
|
sealed class TabNode {
|
||||||
|
abstract val id: String
|
||||||
|
abstract val data: Int
|
||||||
|
abstract val nameRes: Int
|
||||||
|
abstract val bitmapRes: Int?
|
||||||
|
|
||||||
|
override fun toString() = "${id}/${data}"
|
||||||
|
|
||||||
|
data class Root(val amount: Int) : TabNode() {
|
||||||
|
override val id = ID
|
||||||
|
override val data = amount
|
||||||
|
override val nameRes = R.string.info_app_name
|
||||||
|
override val bitmapRes = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ID = "root"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class More(val remainder: Int) : TabNode() {
|
||||||
|
override val id = ID
|
||||||
|
override val data = remainder
|
||||||
|
override val nameRes = R.string.lbl_more
|
||||||
|
override val bitmapRes = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ID = "more"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Home(val type: MusicType) : TabNode() {
|
||||||
|
override val id = ID
|
||||||
|
override val data = type.intCode
|
||||||
|
override val bitmapRes: Int
|
||||||
|
get() = when (type) {
|
||||||
|
MusicType.SONGS -> R.drawable.ic_song_bitmap_24
|
||||||
|
MusicType.ALBUMS -> R.drawable.ic_album_bitmap_24
|
||||||
|
MusicType.ARTISTS -> R.drawable.ic_artist_bitmap_24
|
||||||
|
MusicType.GENRES -> R.drawable.ic_genre_bitmap_24
|
||||||
|
MusicType.PLAYLISTS -> R.drawable.ic_playlist_bitmap_24
|
||||||
|
}
|
||||||
|
override val nameRes = type.nameRes
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ID = "home"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(str: String): TabNode? {
|
||||||
|
val split = str.split("/", limit = 2)
|
||||||
|
if (split.size != 2) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val data = split[1].toIntOrNull() ?: return null
|
||||||
|
return when (split[0]) {
|
||||||
|
Root.ID -> Root(data)
|
||||||
|
More.ID -> More(data)
|
||||||
|
Home.ID -> Home(MusicType.fromIntCode(data) ?: return null)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue