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:
parent
09d4e107e0
commit
3bafc17d0c
9 changed files with 103 additions and 27 deletions
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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}"
|
||||
|
|
|
@ -41,4 +41,5 @@
|
|||
|
||||
<!-- Misc -->
|
||||
<dimen name="elevation_normal">4dp</dimen>
|
||||
<dimen name="offset_thumb">4dp</dimen>
|
||||
</resources>
|
|
@ -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"
|
||||
|
||||
|
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue