Add durations to PlaybackFragment

Add the song duration & the current duration to PlaybackFragment, update the SeekBar in PlaybackFragment to reflect those durations.
This commit is contained in:
OxygenCobalt 2020-10-12 16:02:26 -06:00
parent 09d4e107e0
commit 3bafc17d0c
9 changed files with 103 additions and 27 deletions

View file

@ -48,11 +48,11 @@ dependencies {
// General
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.activity:activity:1.2.0-beta01'
implementation 'androidx.fragment:fragment:1.3.0-beta01'
implementation 'androidx.activity:activity-ktx:1.2.0-beta01'
implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01'
// Layout
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
// Lifecycle
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

View file

@ -14,6 +14,7 @@ sealed class BaseModel {
}
// Song
// TODO: Maybe move durations to a solely-millis system
data class Song(
override val id: Long = -1,
override var name: String,

View file

@ -3,9 +3,11 @@ package org.oxycblt.auxio.playback
import android.content.res.ColorStateList
import android.graphics.drawable.AnimatedVectorDrawable
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
@ -15,11 +17,11 @@ import org.oxycblt.auxio.databinding.FragmentPlaybackBinding
import org.oxycblt.auxio.theme.accent
import org.oxycblt.auxio.theme.toColor
class PlaybackFragment : Fragment() {
class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
private val playbackModel: PlaybackViewModel by activityViewModels()
// TODO: Implement media controls
// TODO: Implement nav to artists/albums
// TODO: Add a full playback fragment
// TODO: Possibly implement a trackbar with a spectrum shown as well.
override fun onCreateView(
inflater: LayoutInflater,
@ -31,6 +33,7 @@ class PlaybackFragment : Fragment() {
// Create accents & icons to use
val accentColor = ColorStateList.valueOf(accent.first.toColor(requireContext()))
val inactiveColor = ColorStateList.valueOf(R.color.control_color.toColor(requireContext()))
val normalTextColor = binding.playbackDurationCurrent.currentTextColor
val iconPauseToPlay = ContextCompat.getDrawable(
requireContext(), R.drawable.ic_pause_to_play
@ -50,6 +53,7 @@ class PlaybackFragment : Fragment() {
// Make marquee scroll work
binding.playbackSong.isSelected = true
binding.playbackSeekBar.setOnSeekBarChangeListener(this)
// --- VIEWMODEL SETUP --
@ -73,6 +77,39 @@ class PlaybackFragment : Fragment() {
}
}
playbackModel.isSeeking.observe(viewLifecycleOwner) {
// Highlight the current duration if the user is seeking, and revert it if not.
if (it) {
binding.playbackDurationCurrent.setTextColor(accentColor)
} else {
binding.playbackDurationCurrent.setTextColor(normalTextColor)
}
}
// Updates for the current duration TextView/Seekbar
playbackModel.formattedCurrentDuration.observe(viewLifecycleOwner) {
binding.playbackDurationCurrent.text = it
}
playbackModel.formattedSeekBarProgress.observe(viewLifecycleOwner) {
binding.playbackSeekBar.progress = it
}
Log.d(this::class.simpleName, "Fragment Created.")
return binding.root
}
// Seeking callbacks
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
playbackModel.updateCurrentDurationWithProgress(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
playbackModel.setSeekingStatus(true)
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
playbackModel.setSeekingStatus(false)
}
}

View file

@ -2,8 +2,10 @@ package org.oxycblt.auxio.playback
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.toDuration
// TODO: Implement media controls
// TODO: Add the playback service itself
@ -13,12 +15,23 @@ class PlaybackViewModel : ViewModel() {
private val mCurrentSong = MutableLiveData<Song>()
val currentSong: LiveData<Song> get() = mCurrentSong
private val mShouldOpenPlayback = MutableLiveData<Boolean>()
val shouldOpenPlayback: LiveData<Boolean> get() = mShouldOpenPlayback
private val mCurrentDuration = MutableLiveData(0L)
val currentDuration: LiveData<Long> get() = mCurrentDuration
private val mIsPlaying = MutableLiveData(false)
val isPlaying: LiveData<Boolean> get() = mIsPlaying
private val mIsSeeking = MutableLiveData(false)
val isSeeking: LiveData<Boolean> get() = mIsSeeking
val formattedCurrentDuration = Transformations.map(currentDuration) {
it.toDuration()
}
val formattedSeekBarProgress = Transformations.map(currentDuration) {
((it.toDouble() / mCurrentSong.value!!.seconds) * 100).toInt()
}
fun updateSong(song: Song) {
mCurrentSong.value = song
@ -27,16 +40,17 @@ class PlaybackViewModel : ViewModel() {
}
}
fun openPlayback() {
mShouldOpenPlayback.value = true
}
fun doneWithOpenPlayback() {
mShouldOpenPlayback.value = false
}
// Invert, not directly set the p
// Invert, not directly set the playing status
fun invertPlayingStatus() {
mIsPlaying.value = !mIsPlaying.value!!
}
fun setSeekingStatus(status: Boolean) {
mIsSeeking.value = status
}
fun updateCurrentDurationWithProgress(progress: Int) {
mCurrentDuration.value =
((progress.toDouble() / 100) * mCurrentSong.value!!.seconds).toLong()
}
}

View file

@ -16,6 +16,6 @@ https://stackoverflow.com/a/61157571/14143986
<item>
<ripple
android:color="@color/selection_color"
android:radius="@dimen/size_divider_ripple"></ripple>
android:radius="@dimen/size_divider_ripple" />
</item>
</layer-list>

View file

@ -41,9 +41,9 @@
android:layout_height="0dp"
android:layout_margin="@dimen/margin_mid_large"
android:contentDescription="@{@string/description_album_cover(song.name)}"
app:layout_constraintDimensionRatio="1:1"
app:coverArt="@{song}"
app:layout_constraintBottom_toTopOf="@+id/playback_song"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar"
@ -88,10 +88,10 @@
android:id="@+id/playback_album"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:layout_marginStart="@dimen/margin_mid_large"
android:layout_marginEnd="@dimen/margin_mid_large"
android:layout_marginBottom="@dimen/margin_medium"
android:layout_marginBottom="16dp"
android:ellipsize="end"
android:singleLine="true"
android:text="@{song.album.name}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
@ -105,23 +105,46 @@
android:id="@+id/playback_seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_medium"
android:paddingStart="@dimen/margin_mid_large"
android:paddingEnd="@dimen/margin_mid_large"
android:thumbOffset="@dimen/offset_thumb"
android:clickable="true"
android:focusable="true"
android:progressBackgroundTint="?android:attr/colorControlNormal"
android:progressTint="?android:attr/colorPrimary"
android:splitTrack="false"
android:thumbTint="?android:attr/colorPrimary"
app:layout_constraintBottom_toTopOf="@+id/playback_play_pause"
app:layout_constraintBottom_toTopOf="@+id/playback_duration_current"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:progress="70" />
<TextView
android:id="@+id/playback_duration_current"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_mid_large"
android:layout_marginBottom="@dimen/margin_medium"
app:layout_constraintBottom_toTopOf="@+id/playback_play_pause"
app:layout_constraintStart_toStartOf="parent"
tools:text="11:38" />
<TextView
android:id="@+id/playback_song_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/margin_mid_large"
android:layout_marginBottom="@dimen/margin_medium"
android:text="@{song.formattedDuration}"
app:layout_constraintBottom_toTopOf="@+id/playback_play_pause"
app:layout_constraintEnd_toEndOf="parent"
tools:text="16:16" />
<ImageButton
android:id="@+id/playback_play_pause"
android:layout_width="@dimen/size_play_pause"
android:layout_height="@dimen/size_play_pause"
android:layout_marginBottom="40dp"
android:layout_marginBottom="30dp"
android:background="@drawable/ui_circular_button"
android:backgroundTint="?android:attr/colorPrimary"
android:contentDescription="@{playbackModel.isPlaying ? @string/description_pause : @string/description_play}"

View file

@ -41,4 +41,5 @@
<!-- Misc -->
<dimen name="elevation_normal">4dp</dimen>
<dimen name="offset_thumb">4dp</dimen>
</resources>

View file

@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.4.0"
ext.kotlin_version = "1.4.10"
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.2'
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"

View file

@ -1,6 +1,6 @@
#Mon Aug 17 09:36:07 MDT 2020
#Mon Oct 12 13:43:13 MDT 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip