Make Queue nav destination
Fix a heisenleak by making QueueFragment a navigation destination instead of being directly instantiated.
This commit is contained in:
parent
c664d22a43
commit
bc7950e7af
8 changed files with 59 additions and 11 deletions
|
@ -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.")
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue