Add playing song nav to PlaybackFragment
Add the ability to nav to the playing song/playing artist/playing album from PlaybackFragment.
This commit is contained in:
parent
6ce8c854a9
commit
206d8d1c1f
16 changed files with 173 additions and 37 deletions
|
@ -83,14 +83,14 @@ dependencies {
|
|||
// Material
|
||||
implementation 'com.google.android.material:material:1.3.0-alpha03'
|
||||
|
||||
// Lint
|
||||
ktlint "com.pinterest:ktlint:0.37.2"
|
||||
|
||||
// ExoPlayer
|
||||
def exoplayer_version = "2.12.1"
|
||||
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:$exoplayer_version"
|
||||
|
||||
// Lint
|
||||
ktlint "com.pinterest:ktlint:0.37.2"
|
||||
|
||||
// Memory Leak checking
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
|
||||
}
|
||||
|
|
|
@ -93,17 +93,30 @@ class MainFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
playbackModel.navToSong.observe(viewLifecycleOwner) {
|
||||
playbackModel.navToPlayingSong.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
if (binding.navBar.selectedItemId != R.id.library_fragment ||
|
||||
(
|
||||
navController!!.currentDestination?.id == R.id.album_detail_fragment &&
|
||||
detailModel.currentAlbum.value == null ||
|
||||
detailModel.currentAlbum.value?.id
|
||||
!= playbackModel.song.value!!.album.id
|
||||
) ||
|
||||
navController.currentDestination?.id == R.id.artist_detail_fragment ||
|
||||
navController.currentDestination?.id == R.id.genre_detail_fragment
|
||||
shouldGoToAlbum(navController!!)
|
||||
) {
|
||||
binding.navBar.selectedItemId = R.id.library_fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playbackModel.navToPlayingAlbum.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
if (binding.navBar.selectedItemId != R.id.library_fragment ||
|
||||
shouldGoToAlbum(navController!!)
|
||||
) {
|
||||
binding.navBar.selectedItemId = R.id.library_fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playbackModel.navToPlayingArtist.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
if (binding.navBar.selectedItemId != R.id.library_fragment ||
|
||||
shouldGoToArtist(navController!!)
|
||||
) {
|
||||
binding.navBar.selectedItemId = R.id.library_fragment
|
||||
}
|
||||
|
@ -117,6 +130,25 @@ class MainFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
// I have no idea what these things even do
|
||||
private fun shouldGoToAlbum(controller: NavController): Boolean {
|
||||
return (
|
||||
controller.currentDestination!!.id == R.id.album_detail_fragment &&
|
||||
detailModel.currentAlbum.value?.id != playbackModel.song.value!!.album.id
|
||||
) ||
|
||||
controller.currentDestination!!.id == R.id.artist_detail_fragment ||
|
||||
controller.currentDestination!!.id == R.id.genre_detail_fragment
|
||||
}
|
||||
|
||||
private fun shouldGoToArtist(controller: NavController): Boolean {
|
||||
return (
|
||||
controller.currentDestination!!.id == R.id.artist_detail_fragment &&
|
||||
detailModel.currentArtist.value?.id != playbackModel.song.value!!.album.artist.id
|
||||
) ||
|
||||
controller.currentDestination!!.id == R.id.album_detail_fragment ||
|
||||
controller.currentDestination!!.id == R.id.genre_detail_fragment
|
||||
}
|
||||
|
||||
private fun navigateWithItem(navController: NavController, item: MenuItem): Boolean {
|
||||
if (navController.currentDestination!!.id != item.itemId) {
|
||||
// Create custom NavOptions myself so that animations work
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
package org.oxycblt.auxio.database
|
||||
|
||||
/**
|
||||
* A database entity that stores a compressed variant of the current playback state.
|
||||
* @property id - The database key for this state
|
||||
* @property songId - The song that is currently playing
|
||||
* @property parentId - The parent that is being played from [-1 if none]
|
||||
* @property index - The current index in the queue.
|
||||
* @property mode - The integer form of the current [org.oxycblt.auxio.playback.state.PlaybackMode]
|
||||
* @property isShuffling - A bool for if the queue was shuffled
|
||||
* @property shuffleSeed - A long for the seed used to shuffle the queue [Used for quick-restore]
|
||||
* @property loopMode - The integer form of the current [org.oxycblt.auxio.playback.state.LoopMode]
|
||||
* @property inUserQueue - A bool for if the state was currently playing from the user queue.
|
||||
*/
|
||||
data class PlaybackState(
|
||||
val id: Long = 0L,
|
||||
val songId: Long = -1L,
|
||||
|
|
|
@ -188,6 +188,7 @@ class PlaybackStateDatabase(context: Context) :
|
|||
|
||||
var position = 0
|
||||
|
||||
// Try to write out the entirety of the queue, any failed inserts will be skipped.
|
||||
while (position < queue.size) {
|
||||
database.beginTransaction()
|
||||
var i = position
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
package org.oxycblt.auxio.database
|
||||
|
||||
/**
|
||||
* A database entity that stores a simplified representation of a song in a queue.
|
||||
* @property id The database entity's id
|
||||
* @property songId The song id for this queue item
|
||||
* @property albumId The album id for this queue item, used to make searching quicker
|
||||
* @property isUserQueue A bool for if this queue item is a user queue item or not
|
||||
*/
|
||||
data class QueueItem(
|
||||
var id: Long = 0L,
|
||||
val songId: Long = Long.MIN_VALUE,
|
||||
|
|
|
@ -122,10 +122,12 @@ class AlbumDetailFragment : DetailFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
playbackModel.navToSong.observe(viewLifecycleOwner) {
|
||||
playbackModel.navToPlayingSong.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
// Calculate where the item for the currently played song is, and navigate to there.
|
||||
val pos = detailModel.currentAlbum.value!!.songs.indexOf(playbackModel.song.value)
|
||||
val pos = detailModel.albumSortMode.value!!.getSortedSongList(
|
||||
detailModel.currentAlbum.value!!.songs
|
||||
).indexOf(playbackModel.song.value)
|
||||
|
||||
if (pos != -1) {
|
||||
binding.albumSongRecycler.post {
|
||||
|
@ -133,7 +135,7 @@ class AlbumDetailFragment : DetailFragment() {
|
|||
val y = binding.albumSongRecycler.y +
|
||||
binding.albumSongRecycler.getChildAt(pos).y
|
||||
|
||||
binding.nestedScroll.smoothScrollBy(0, y.toInt())
|
||||
binding.nestedScroll.smoothScrollTo(0, y.toInt())
|
||||
}
|
||||
|
||||
playbackModel.doneWithNavToPlayingSong()
|
||||
|
@ -141,6 +143,12 @@ class AlbumDetailFragment : DetailFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
playbackModel.navToPlayingAlbum.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
playbackModel.doneWithNavToPlayingAlbum()
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(this::class.simpleName, "Fragment created.")
|
||||
|
||||
return binding.root
|
||||
|
|
|
@ -108,6 +108,12 @@ class ArtistDetailFragment : DetailFragment() {
|
|||
)
|
||||
}
|
||||
|
||||
playbackModel.navToPlayingArtist.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
playbackModel.doneWithNavToPlayingArtist()
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(this::class.simpleName, "Fragment created.")
|
||||
|
||||
return binding.root
|
||||
|
|
|
@ -151,7 +151,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
|
|||
}
|
||||
}
|
||||
|
||||
playbackModel.navToSong.observe(viewLifecycleOwner) {
|
||||
playbackModel.navToPlayingSong.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
libraryModel.updateNavigationStatus(false)
|
||||
|
||||
|
@ -159,6 +159,22 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
|
|||
}
|
||||
}
|
||||
|
||||
playbackModel.navToPlayingAlbum.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
libraryModel.updateNavigationStatus(false)
|
||||
|
||||
navToItem(playbackModel.song.value!!.album)
|
||||
}
|
||||
}
|
||||
|
||||
playbackModel.navToPlayingArtist.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
libraryModel.updateNavigationStatus(false)
|
||||
|
||||
navToItem(playbackModel.song.value!!.album.artist)
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(this::class.simpleName, "Fragment created.")
|
||||
|
||||
return binding.root
|
||||
|
|
|
@ -69,6 +69,9 @@ data class Album(
|
|||
|
||||
/**
|
||||
* The data object for an artist. Inherits [BaseModel]
|
||||
* @property albums The list of all [Album]s in this artist
|
||||
* @property genres The list of all parent [Genre]s in this artist, sorted by relevance
|
||||
* @property songs The list of all [Song]s in this artist
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
data class Artist(
|
||||
|
@ -89,7 +92,8 @@ data class Artist(
|
|||
|
||||
/**
|
||||
* The data object for a genre. Inherits [BaseModel]
|
||||
* @property artists The list of all [Artist]s in this genre
|
||||
* @property artists The list of all [Artist]s in this genre.
|
||||
* @property albums The list of all [Album]s in this genre.
|
||||
* @property songs The list of all [Song]s in this genre.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
|
@ -117,6 +121,8 @@ data class Genre(
|
|||
|
||||
/**
|
||||
* A data object used solely for the "Header" UI element. Inherits [BaseModel].
|
||||
* @property isAction Value that marks whether this header should have an action attached to it.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
data class Header(
|
||||
override val id: Long = -1,
|
||||
|
|
|
@ -9,7 +9,7 @@ import org.oxycblt.auxio.music.processing.MusicSorter
|
|||
import org.oxycblt.auxio.recycler.ShowMode
|
||||
|
||||
/**
|
||||
* The main storage for music items. Use [MusicStore.from()] to get the instance.
|
||||
* The main storage for music items. Use [MusicStore.getInstance] to get the instance.
|
||||
*/
|
||||
class MusicStore private constructor() {
|
||||
private var mGenres = listOf<Genre>()
|
||||
|
|
|
@ -11,6 +11,7 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.MainFragmentDirections
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.FragmentCompactPlaybackBinding
|
||||
|
@ -54,11 +55,15 @@ class CompactPlaybackFragment : Fragment() {
|
|||
true
|
||||
}
|
||||
|
||||
// Enable the ability to force-save the state in debug builds, in order to check
|
||||
// for persistence issues without waiting for PlaybackService to be killed.
|
||||
if (BuildConfig.DEBUG) {
|
||||
binding.playbackControls.setOnLongClickListener {
|
||||
playbackModel.save(requireContext())
|
||||
getString(R.string.debug_state_saved).createToast(requireContext())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// --- VIEWMODEL SETUP ---
|
||||
|
||||
|
|
|
@ -187,6 +187,24 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
}
|
||||
}
|
||||
|
||||
playbackModel.navToPlayingSong.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
playbackModel.navToPlayingAlbum.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
playbackModel.navToPlayingArtist.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(this::class.simpleName, "Fragment Created.")
|
||||
|
||||
return binding.root
|
||||
|
|
|
@ -62,8 +62,14 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
private val mIsSeeking = MutableLiveData(false)
|
||||
val isSeeking: LiveData<Boolean> get() = mIsSeeking
|
||||
|
||||
private val mNavToSong = MutableLiveData(false)
|
||||
val navToSong: LiveData<Boolean> get() = mNavToSong
|
||||
private val mNavToPlayingSong = MutableLiveData(false)
|
||||
val navToPlayingSong: LiveData<Boolean> get() = mNavToPlayingSong
|
||||
|
||||
private val mNavToPlayingAlbum = MutableLiveData(false)
|
||||
val navToPlayingAlbum: LiveData<Boolean> get() = mNavToPlayingAlbum
|
||||
|
||||
private val mNavToPlayingArtist = MutableLiveData(false)
|
||||
val navToPlayingArtist: LiveData<Boolean> get() = mNavToPlayingArtist
|
||||
|
||||
private var mCanAnimate = false
|
||||
val canAnimate: Boolean get() = mCanAnimate
|
||||
|
@ -283,11 +289,27 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
}
|
||||
|
||||
fun navToPlayingSong() {
|
||||
mNavToSong.value = true
|
||||
mNavToPlayingSong.value = true
|
||||
}
|
||||
|
||||
fun doneWithNavToPlayingSong() {
|
||||
mNavToSong.value = false
|
||||
mNavToPlayingSong.value = false
|
||||
}
|
||||
|
||||
fun navToPlayingAlbum() {
|
||||
mNavToPlayingAlbum.value = true
|
||||
}
|
||||
|
||||
fun doneWithNavToPlayingAlbum() {
|
||||
mNavToPlayingAlbum.value = false
|
||||
}
|
||||
|
||||
fun navToPlayingArtist() {
|
||||
mNavToPlayingArtist.value = true
|
||||
}
|
||||
|
||||
fun doneWithNavToPlayingArtist() {
|
||||
mNavToPlayingArtist.value = false
|
||||
}
|
||||
|
||||
fun enableAnimation() {
|
||||
|
|
|
@ -22,7 +22,7 @@ import kotlin.random.Random
|
|||
* - If you want to use the playback state with the ExoPlayer instance or system-side things,
|
||||
* use [org.oxycblt.auxio.playback.PlaybackService].
|
||||
*
|
||||
* All instantiation should be done with [PlaybackStateManager.from()].
|
||||
* All instantiation should be done with [PlaybackStateManager.getInstance].
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class PlaybackStateManager private constructor() {
|
||||
|
|
|
@ -9,7 +9,7 @@ https://stackoverflow.com/a/61157571/14143986
|
|||
android:top="-2dp">
|
||||
<shape android:shape="rectangle">
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:width="0.5dp"
|
||||
android:color="@color/divider_color" />
|
||||
</shape>
|
||||
</item>
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
android:layout_marginStart="@dimen/margin_mid_large"
|
||||
android:layout_marginEnd="@dimen/margin_mid_large"
|
||||
android:text="@{song.name}"
|
||||
android:onClick="@{() -> playbackModel.navToPlayingSong()}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
app:layout_constraintBottom_toTopOf="@+id/playback_artist"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -83,6 +84,7 @@
|
|||
android:layout_marginStart="@dimen/margin_mid_large"
|
||||
android:layout_marginEnd="@dimen/margin_mid_large"
|
||||
android:text="@{song.album.artist.name}"
|
||||
android:onClick="@{() -> playbackModel.navToPlayingArtist()}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/playback_album"
|
||||
|
@ -100,6 +102,7 @@
|
|||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@{song.album.name}"
|
||||
android:onClick="@{() -> playbackModel.navToPlayingAlbum()}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/playback_seek_bar"
|
||||
|
|
Loading…
Reference in a new issue