Add skipping

Add the ability to skip fowards/backwards.
This commit is contained in:
OxygenCobalt 2020-10-13 12:40:16 -06:00
parent d8f40bfd27
commit 3376b57f8e
13 changed files with 149 additions and 7 deletions

View file

@ -22,6 +22,7 @@ class AlbumDetailFragment : Fragment() {
private val args: AlbumDetailFragmentArgs by navArgs() private val args: AlbumDetailFragmentArgs by navArgs()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val musicModel: MusicViewModel by activityViewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -45,7 +46,7 @@ class AlbumDetailFragment : Fragment() {
} }
val songAdapter = DetailSongAdapter { val songAdapter = DetailSongAdapter {
playbackModel.updateSong(it) playbackModel.update(it, musicModel.songs.value!!)
} }
// --- UI SETUP --- // --- UI SETUP ---

View file

@ -190,7 +190,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
// If the item is a song [That was selected through search], then update the playback // If the item is a song [That was selected through search], then update the playback
// to that song instead of doing any navigation // to that song instead of doing any navigation
if (baseModel is Song) { if (baseModel is Song) {
playbackModel.updateSong(baseModel) playbackModel.update(baseModel, musicModel.songs.value!!)
return return
} }

View file

@ -67,6 +67,22 @@ data class Artist(
} }
return num return num
} }
val songs: MutableList<Song>
get() {
val songs = mutableListOf<Song>()
albums.forEach {
songs.addAll(it.songs)
}
return songs
}
val genreSongs: MutableList<Song>
get() {
val songs = mutableListOf<Song>()
genres.forEach {
songs.addAll(it.songs)
}
return songs
}
} }
// Genre // Genre
@ -93,6 +109,14 @@ data class Genre(
} }
return num return num
} }
val songs: MutableList<Song>
get() {
val songs = mutableListOf<Song>()
artists.forEach {
songs.addAll(it.songs)
}
return songs
}
} }
// Header [Used for search, nothing else] // Header [Used for search, nothing else]

View file

@ -15,6 +15,8 @@ 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.theme.accent import org.oxycblt.auxio.theme.accent
import org.oxycblt.auxio.theme.disable
import org.oxycblt.auxio.theme.enable
import org.oxycblt.auxio.theme.toColor import org.oxycblt.auxio.theme.toColor
class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
@ -33,8 +35,11 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
// Create accents & icons to use // Create accents & icons to use
val accentColor = ColorStateList.valueOf(accent.first.toColor(requireContext())) val accentColor = ColorStateList.valueOf(accent.first.toColor(requireContext()))
val inactiveColor = ColorStateList.valueOf(R.color.control_color.toColor(requireContext())) val controlColor = ColorStateList.valueOf(R.color.control_color.toColor(requireContext()))
val normalTextColor = binding.playbackDurationCurrent.currentTextColor val normalTextColor = binding.playbackDurationCurrent.currentTextColor
val disabledColor = ColorStateList.valueOf(
R.color.inactive_color.toColor(requireContext())
)
val iconPauseToPlay = ContextCompat.getDrawable( val iconPauseToPlay = ContextCompat.getDrawable(
requireContext(), R.drawable.ic_pause_to_play requireContext(), R.drawable.ic_pause_to_play
@ -60,10 +65,29 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
// --- VIEWMODEL SETUP -- // --- VIEWMODEL SETUP --
playbackModel.currentSong.observe(viewLifecycleOwner) { playbackModel.currentSong.observe(viewLifecycleOwner) {
Log.d(this::class.simpleName, "Updating song display to ${it.name}.")
binding.song = it binding.song = it
binding.playbackSeekBar.max = it.seconds.toInt() binding.playbackSeekBar.max = it.seconds.toInt()
} }
playbackModel.currentIndex.observe(viewLifecycleOwner) {
if (it > 0) {
binding.playbackSkipPrev.enable(requireContext())
} else {
binding.playbackSkipPrev.disable(requireContext())
}
Log.d(this::class.simpleName, it.toString())
if (it < playbackModel.queue.value!!.lastIndex) {
binding.playbackSkipNext.enable(requireContext())
} else {
Log.d(this::class.simpleName, "Fucking stupid retard.")
binding.playbackSkipNext.disable(requireContext())
}
}
playbackModel.isPlaying.observe(viewLifecycleOwner) { playbackModel.isPlaying.observe(viewLifecycleOwner) {
if (it) { if (it) {
// Animate the playing status and switch the button to the accent color // Animate the playing status and switch the button to the accent color
@ -76,7 +100,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
binding.playbackPlayPause.setImageDrawable(iconPlayToPause) binding.playbackPlayPause.setImageDrawable(iconPlayToPause)
iconPlayToPause.start() iconPlayToPause.start()
binding.playbackPlayPause.backgroundTintList = inactiveColor binding.playbackPlayPause.backgroundTintList = controlColor
} }
} }

View file

@ -15,6 +15,12 @@ class PlaybackViewModel : ViewModel() {
private val mCurrentSong = MutableLiveData<Song>() private val mCurrentSong = MutableLiveData<Song>()
val currentSong: LiveData<Song> get() = mCurrentSong val currentSong: LiveData<Song> get() = mCurrentSong
private val mCurrentIndex = MutableLiveData(0)
val currentIndex: LiveData<Int> get() = mCurrentIndex
private val mQueue = MutableLiveData(mutableListOf<Song>())
val queue: LiveData<MutableList<Song>> get() = mQueue
private val mCurrentDuration = MutableLiveData(0L) private val mCurrentDuration = MutableLiveData(0L)
val currentDuration: LiveData<Long> get() = mCurrentDuration val currentDuration: LiveData<Long> get() = mCurrentDuration
@ -33,7 +39,15 @@ class PlaybackViewModel : ViewModel() {
if (mCurrentSong.value != null) it.toInt() else 0 if (mCurrentSong.value != null) it.toInt() else 0
} }
fun updateSong(song: Song) { // Update the current song while changing the queue to All Songs.
fun update(song: Song, allSongs: List<Song>) {
updatePlayback(song)
mQueue.value = allSongs.toMutableList()
mCurrentIndex.value = allSongs.indexOf(song)
}
private fun updatePlayback(song: Song) {
mCurrentSong.value = song mCurrentSong.value = song
mCurrentDuration.value = 0 mCurrentDuration.value = 0
@ -56,4 +70,20 @@ class PlaybackViewModel : ViewModel() {
fun updateCurrentDurationWithProgress(progress: Int) { fun updateCurrentDurationWithProgress(progress: Int) {
mCurrentDuration.value = progress.toLong() mCurrentDuration.value = progress.toLong()
} }
fun skipNext() {
if (mCurrentIndex.value!! < mQueue.value!!.size) {
mCurrentIndex.value = mCurrentIndex.value!!.inc()
}
updatePlayback(mQueue.value!![mCurrentIndex.value!!])
}
fun skipPrev() {
if (mCurrentIndex.value!! > 0) {
mCurrentIndex.value = mCurrentIndex.value!!.dec()
}
updatePlayback(mQueue.value!![mCurrentIndex.value!!])
}
} }

View file

@ -33,7 +33,7 @@ class SongsFragment : Fragment() {
binding.songRecycler.apply { binding.songRecycler.apply {
adapter = SongAdapter(musicModel.songs.value!!) { adapter = SongAdapter(musicModel.songs.value!!) {
playbackModel.updateSong(it) playbackModel.update(it, musicModel.songs.value!!)
} }
applyDivider() applyDivider()
setHasFixedSize(true) setHasFixedSize(true)

View file

@ -103,6 +103,16 @@ fun ImageButton.disable(context: Context) {
isEnabled = false isEnabled = false
} }
fun ImageButton.enable(context: Context) {
if (!isEnabled) {
imageTintList = ColorStateList.valueOf(
R.color.control_color.toColor(context)
)
isEnabled = true
}
}
// Apply a custom vertical divider // Apply a custom vertical divider
fun RecyclerView.applyDivider() { fun RecyclerView.applyDivider() {
val div = DividerItemDecoration( val div = DividerItemDecoration(

View file

@ -7,5 +7,5 @@
android:tint="@color/control_color"> android:tint="@color/control_color">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z" /> android:pathData="M 5.88,7.06 12,13.166667 18.12,7.06 20,8.94 l -8,8 -8,-8 z" />
</vector> </vector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/control_color">
<path
android:fillColor="@android:color/white"
android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z" />
</vector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/control_color">
<path
android:fillColor="@android:color/white"
android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z" />
</vector>

View file

@ -19,6 +19,7 @@
android:id="@+id/playback_layout" android:id="@+id/playback_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="@color/background"> android:background="@color/background">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
@ -157,5 +158,33 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:src="@drawable/ic_play_to_pause" /> tools:src="@drawable/ic_play_to_pause" />
<ImageButton
android:id="@+id/playback_skip_next"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="@dimen/size_play_pause_compact"
android:layout_height="@dimen/size_play_pause_compact"
android:layout_marginStart="@dimen/margin_mid_large"
android:contentDescription="@string/description_skip_next"
android:background="@drawable/ui_unbounded_ripple"
android:src="@drawable/ic_skip_next"
android:onClick="@{() -> playbackModel.skipNext()}"
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
app:layout_constraintStart_toEndOf="@+id/playback_play_pause"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
<ImageButton
android:id="@+id/playback_skip_prev"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="@dimen/size_play_pause_compact"
android:layout_height="@dimen/size_play_pause_compact"
android:src="@drawable/ic_skip_prev"
android:contentDescription="@string/description_skip_prev"
android:background="@drawable/ui_unbounded_ripple"
android:layout_marginEnd="@dimen/margin_mid_large"
android:onClick="@{() -> playbackModel.skipPrev()}"
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -39,6 +39,8 @@
<string name="description_sort_alpha_up">Sort from Z to A</string> <string name="description_sort_alpha_up">Sort from Z to A</string>
<string name="description_play">Play</string> <string name="description_play">Play</string>
<string name="description_pause">Pause</string> <string name="description_pause">Pause</string>
<string name="description_skip_next">Skip to next song</string>
<string name="description_skip_prev">Skip to last song</string>
<!-- Placeholder Namespace | Placeholder values --> <!-- Placeholder Namespace | Placeholder values -->
<string name="placeholder_genre">Unknown Genre</string> <string name="placeholder_genre">Unknown Genre</string>