Make Queue nav destination

Fix a heisenleak by making QueueFragment a navigation destination instead of being directly instantiated.
This commit is contained in:
OxygenCobalt 2020-11-07 09:29:49 -07:00
parent c664d22a43
commit bc7950e7af
8 changed files with 59 additions and 11 deletions

View file

@ -15,7 +15,6 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackBinding import org.oxycblt.auxio.databinding.FragmentPlaybackBinding
import org.oxycblt.auxio.playback.queue.QueueFragment
import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.theme.accent import org.oxycblt.auxio.theme.accent
import org.oxycblt.auxio.theme.disable import org.oxycblt.auxio.theme.disable
@ -71,7 +70,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
setOnMenuItemClickListener { setOnMenuItemClickListener {
if (it.itemId == R.id.action_queue) { if (it.itemId == R.id.action_queue) {
QueueFragment().show(parentFragmentManager, "TAG_QUEUE") findNavController().navigate(PlaybackFragmentDirections.actionShowQueue())
} }
true true
@ -186,6 +185,14 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
queueMenuItem.isEnabled = true queueMenuItem.isEnabled = true
queueMenuItem.icon = iconQueueActive queueMenuItem.icon = iconQueueActive
} }
// If someone edits the queue to make it have no songs left, then disable the
// skip next button.
if (playbackModel.index.value!! == it.size) {
binding.playbackSkipNext.disable(requireContext())
} else {
binding.playbackSkipNext.enable(requireContext())
}
} }
Log.d(this::class.simpleName, "Fragment Created.") Log.d(this::class.simpleName, "Fragment Created.")

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
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.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.toDuration import org.oxycblt.auxio.music.toDuration
@ -22,6 +23,9 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
private val mSong = MutableLiveData<Song?>() private val mSong = MutableLiveData<Song?>()
val song: LiveData<Song?> get() = mSong val song: LiveData<Song?> get() = mSong
private val mParent = MutableLiveData<BaseModel?>()
val parent: LiveData<BaseModel?> get() = mParent
private val mPosition = MutableLiveData(0L) private val mPosition = MutableLiveData(0L)
val position: LiveData<Long> get() = mPosition val position: LiveData<Long> get() = mPosition
@ -32,6 +36,9 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
private val mIndex = MutableLiveData(0) private val mIndex = MutableLiveData(0)
val index: LiveData<Int> get() = mIndex val index: LiveData<Int> get() = mIndex
private val mMode = MutableLiveData(PlaybackMode.ALL_SONGS)
val mode: LiveData<PlaybackMode> get() = mMode
// States // States
private val mIsPlaying = MutableLiveData(false) private val mIsPlaying = MutableLiveData(false)
val isPlaying: LiveData<Boolean> get() = mIsPlaying val isPlaying: LiveData<Boolean> get() = mIsPlaying
@ -194,6 +201,10 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
mSong.value = song mSong.value = song
} }
override fun onParentUpdate(parent: BaseModel?) {
mParent.value = parent
}
override fun onPositionUpdate(position: Long) { override fun onPositionUpdate(position: Long) {
if (!mIsSeeking.value!!) { if (!mIsSeeking.value!!) {
mPosition.value = position / 1000 mPosition.value = position / 1000
@ -208,6 +219,10 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
mIndex.value = index mIndex.value = index
} }
override fun onModeUpdate(mode: PlaybackMode) {
mMode.value = mode
}
override fun onPlayingUpdate(isPlaying: Boolean) { override fun onPlayingUpdate(isPlaying: Boolean) {
mIsPlaying.value = isPlaying mIsPlaying.value = isPlaying
} }

View file

@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -12,6 +13,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.theme.accent import org.oxycblt.auxio.theme.accent
import org.oxycblt.auxio.theme.applyDivider import org.oxycblt.auxio.theme.applyDivider
import org.oxycblt.auxio.theme.toColor import org.oxycblt.auxio.theme.toColor
@ -28,9 +30,7 @@ class QueueFragment : BottomSheetDialogFragment() {
): View? { ): View? {
val binding = FragmentQueueBinding.inflate(inflater) val binding = FragmentQueueBinding.inflate(inflater)
val helper = ItemTouchHelper( val helper = ItemTouchHelper(QueueDragCallback(playbackModel))
QueueDragCallback(playbackModel)
)
val queueAdapter = QueueAdapter(helper) val queueAdapter = QueueAdapter(helper)
// --- UI SETUP --- // --- UI SETUP ---
@ -47,9 +47,19 @@ class QueueFragment : BottomSheetDialogFragment() {
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
playbackModel.mode.observe(viewLifecycleOwner) {
if (it == PlaybackMode.ALL_SONGS) {
binding.queueHeader.setText(R.string.label_next_songs)
} else {
binding.queueHeader.text = getString(
R.string.format_next_from, playbackModel.parent.value!!.name
)
}
}
playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) {
if (it.isEmpty()) { if (it.isEmpty()) {
dismiss() findNavController().navigateUp()
return@observe return@observe
} }

View file

@ -29,6 +29,10 @@ class PlaybackStateManager private constructor() {
callbacks.forEach { it.onPositionUpdate(value) } callbacks.forEach { it.onPositionUpdate(value) }
} }
private var mParent: BaseModel? = null private var mParent: BaseModel? = null
set(value) {
field = value
callbacks.forEach { it.onParentUpdate(value) }
}
// Queue // Queue
private var mQueue = mutableListOf<Song>() private var mQueue = mutableListOf<Song>()
@ -47,6 +51,7 @@ class PlaybackStateManager private constructor() {
callbacks.forEach { it.onModeUpdate(value) } callbacks.forEach { it.onModeUpdate(value) }
} }
// Status
private var mIsPlaying = false private var mIsPlaying = false
set(value) { set(value) {
field = value field = value
@ -131,6 +136,7 @@ class PlaybackStateManager private constructor() {
} }
fun playParentModel(baseModel: BaseModel, shuffled: Boolean) { fun playParentModel(baseModel: BaseModel, shuffled: Boolean) {
// This should never occur.
if (baseModel is Song || baseModel is Header) { if (baseModel is Song || baseModel is Header) {
Log.e( Log.e(
this::class.simpleName, this::class.simpleName,
@ -295,8 +301,7 @@ class PlaybackStateManager private constructor() {
// If specified, make the current song the first member of the queue. // If specified, make the current song the first member of the queue.
if (keepSong) { if (keepSong) {
mSong?.let { mSong?.let {
mQueue.remove(it) moveQueueItems(mQueue.indexOf(it), 0)
mQueue.add(0, it)
} }
} else { } else {
// Otherwise, just start from the zeroth position in the queue. // Otherwise, just start from the zeroth position in the queue.
@ -400,6 +405,7 @@ class PlaybackStateManager private constructor() {
interface Callback { interface Callback {
fun onSongUpdate(song: Song?) {} fun onSongUpdate(song: Song?) {}
fun onParentUpdate(parent: BaseModel?) {}
fun onPositionUpdate(position: Long) {} fun onPositionUpdate(position: Long) {}
fun onQueueUpdate(queue: MutableList<Song>) {} fun onQueueUpdate(queue: MutableList<Song>) {}
fun onModeUpdate(mode: PlaybackMode) {} fun onModeUpdate(mode: PlaybackMode) {}

View file

@ -2,7 +2,6 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -115,5 +115,14 @@
android:id="@+id/playback_fragment" android:id="@+id/playback_fragment"
android:name="org.oxycblt.auxio.playback.PlaybackFragment" android:name="org.oxycblt.auxio.playback.PlaybackFragment"
android:label="PlaybackFragment" android:label="PlaybackFragment"
tools:layout="@layout/fragment_playback" /> tools:layout="@layout/fragment_playback" >
<action
android:id="@+id/action_show_queue"
app:destination="@id/queue_fragment" />
</fragment>
<dialog
android:id="@+id/queue_fragment"
android:name="org.oxycblt.auxio.playback.queue.QueueFragment"
android:label="QueueFragment"
tools:layout="@layout/fragment_queue"/>
</navigation> </navigation>

View file

@ -27,6 +27,7 @@
<string name="label_play">Play</string> <string name="label_play">Play</string>
<string name="label_queue">Queue</string> <string name="label_queue">Queue</string>
<string name="label_queue_add">Add to queue</string> <string name="label_queue_add">Add to queue</string>
<string name="label_next_songs">Next from: All Songs</string>
<string name="label_notification_playback">Music Playback</string> <string name="label_notification_playback">Music Playback</string>
<string name="label_service_playback">The music playback service for Auxio.</string> <string name="label_service_playback">The music playback service for Auxio.</string>
@ -62,6 +63,7 @@
<string name="format_info">%1$s / %2$s</string> <string name="format_info">%1$s / %2$s</string>
<string name="format_double_info">%1$s / %2$s / %3$s</string> <string name="format_double_info">%1$s / %2$s / %3$s</string>
<string name="format_double_counts">%1$s, %2$s</string> <string name="format_double_counts">%1$s, %2$s</string>
<string name="format_next_from">Next From: %s</string>
<plurals name="format_song_count"> <plurals name="format_song_count">
<item quantity="one">%s Song</item> <item quantity="one">%s Song</item>

View file

@ -42,7 +42,7 @@
</style> </style>
<!-- <!--
Hack to get QueueFragment to not overlap the Status Bar or Navigation Bar Fix to get QueueFragment to not overlap the Status Bar or Navigation Bar
https://stackoverflow.com/a/57790787/14143986 https://stackoverflow.com/a/57790787/14143986
--> -->
<style name="Theme.BottomSheetFix" parent="@style/Theme.Design.BottomSheetDialog"> <style name="Theme.BottomSheetFix" parent="@style/Theme.Design.BottomSheetDialog">