From 8f38ed6ee56dfa8750c8cf77fc7436088496d79d Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Mon, 21 Mar 2022 20:13:06 -0600 Subject: [PATCH] playback: rework view implementations Rework the playback views to follow the new idioms I want to use for Auxio. This change mostly consists of flattening away some of the custom views, removing databinding, and using a general viewbinding fragment that I hope to extend to the entire app. --- .../java/org/oxycblt/auxio/coil/CoilUtils.kt | 20 ++- .../detail/recycler/AlbumDetailAdapter.kt | 4 +- .../auxio/playback/PlaybackBarFragment.kt | 16 +-- .../auxio/playback/PlaybackPanelFragment.kt | 126 +++++++++++++----- .../oxycblt/auxio/playback/PlaybackSeekBar.kt | 112 ---------------- .../auxio/playback/PlaybackViewModel.kt | 8 +- .../org/oxycblt/auxio/ui/BottomSheetLayout.kt | 10 +- .../oxycblt/auxio/ui/ViewBindingFragment.kt | 59 ++++++++ .../layout-land/fragment_playback_panel.xml | 97 +++++++------- .../fragment_playback_panel.xml | 56 +++++--- .../fragment_playback_panel.xml | 67 +++++----- .../layout-sw640dp/fragment_playback_bar.xml | 13 -- .../fragment_playback_panel.xml | 51 +++++-- .../layout-w600dp/fragment_playback_bar.xml | 14 +- .../main/res/layout/fragment_playback_bar.xml | 13 -- .../res/layout/fragment_playback_panel.xml | 82 ++++++------ app/src/main/res/layout/view_seek_bar.xml | 4 +- app/src/main/res/values/dimens.xml | 2 + 18 files changed, 394 insertions(+), 360 deletions(-) delete mode 100644 app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/ui/ViewBindingFragment.kt diff --git a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt index 33aa4bda3..b995a994a 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt @@ -21,6 +21,7 @@ import android.content.Context import android.graphics.Bitmap import android.widget.ImageView import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.core.graphics.drawable.toBitmap import androidx.databinding.BindingAdapter import coil.dispose @@ -37,23 +38,28 @@ import org.oxycblt.auxio.music.Song // --- BINDING ADAPTERS --- -/** Bind the album art for a [song]. */ +/** Bind the album cover for a [song]. */ @BindingAdapter("albumArt") -fun ImageView.bindAlbumArt(song: Song?) = load(song, R.drawable.ic_album) +fun ImageView.bindAlbumCover(song: Song?) = + load(song, R.drawable.ic_album, R.string.desc_album_cover) -/** Bind the album art for an [album]. */ +/** Bind the album cover for an [album]. */ @BindingAdapter("albumArt") -fun ImageView.bindAlbumArt(album: Album?) = load(album, R.drawable.ic_album) +fun ImageView.bindAlbumCover(album: Album?) = + load(album, R.drawable.ic_album, R.string.desc_album_cover) /** Bind the image for an [artist] */ @BindingAdapter("artistImage") -fun ImageView.bindArtistImage(artist: Artist?) = load(artist, R.drawable.ic_artist) +fun ImageView.bindArtistImage(artist: Artist?) = + load(artist, R.drawable.ic_artist, R.string.desc_artist_image) /** Bind the image for a [genre] */ @BindingAdapter("genreImage") -fun ImageView.bindGenreImage(genre: Genre?) = load(genre, R.drawable.ic_genre) +fun ImageView.bindGenreImage(genre: Genre?) = + load(genre, R.drawable.ic_genre, R.string.desc_genre_image) -fun ImageView.load(music: T?, @DrawableRes error: Int) { +fun ImageView.load(music: T?, @DrawableRes error: Int, @StringRes desc: Int) { + contentDescription = context.getString(desc, music?.resolvedName) dispose() load(music) { error(error) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt index 851e433b7..1facb2423 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt @@ -24,7 +24,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R -import org.oxycblt.auxio.coil.bindAlbumArt +import org.oxycblt.auxio.coil.bindAlbumCover import org.oxycblt.auxio.databinding.ItemAlbumSongBinding import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.detail.DetailViewModel @@ -127,7 +127,7 @@ class AlbumDetailAdapter( override fun onBind(data: Album) { binding.detailCover.apply { - bindAlbumArt(data) + bindAlbumCover(data) contentDescription = context.getString(R.string.desc_album_cover, data.resolvedName) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt index f63a237b1..fae91f30d 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt @@ -28,8 +28,10 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import com.google.android.material.color.MaterialColors import org.oxycblt.auxio.R +import org.oxycblt.auxio.coil.bindAlbumCover import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding import org.oxycblt.auxio.detail.DetailViewModel +import org.oxycblt.auxio.music.bindSongInfo import org.oxycblt.auxio.ui.BottomSheetLayout import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.systemBarInsetsCompat @@ -99,27 +101,25 @@ class PlaybackBarFragment : Fragment() { // -- VIEWMODEL SETUP --- - binding.song = playbackModel.song.value playbackModel.song.observe(viewLifecycleOwner) { song -> if (song != null) { - binding.song = song - binding.executePendingBindings() + binding.playbackCover.bindAlbumCover(song) + binding.playbackSong.text = song.resolvedName + binding.playbackInfo.bindSongInfo(song) + binding.playbackProgressBar.max = song.seconds.toInt() } } binding.playbackPlayPause.isActivated = playbackModel.isPlaying.value!! playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying -> binding.playbackPlayPause.isActivated = isPlaying - binding.executePendingBindings() } - binding.playbackProgressBar.progress = playbackModel.position.value!!.toInt() - playbackModel.position.observe(viewLifecycleOwner) { position -> + binding.playbackProgressBar.progress = playbackModel.seconds.value!!.toInt() + playbackModel.seconds.observe(viewLifecycleOwner) { position -> binding.playbackProgressBar.progress = position.toInt() } - binding.executePendingBindings() - return binding.root } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index b19cff6ac..e96553d7f 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -20,19 +20,25 @@ package org.oxycblt.auxio.playback import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem -import android.view.View -import android.view.ViewGroup import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController +import com.google.android.material.color.MaterialColors +import com.google.android.material.slider.Slider +import kotlin.math.max import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R +import org.oxycblt.auxio.coil.bindAlbumCover import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding import org.oxycblt.auxio.detail.DetailViewModel +import org.oxycblt.auxio.music.toDuration import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.ui.BottomSheetLayout +import org.oxycblt.auxio.ui.ViewBindingFragment +import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.stateList import org.oxycblt.auxio.util.systemBarInsetsCompat /** @@ -42,28 +48,25 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat * * TODO: Handle RTL correctly in the playback buttons */ -class PlaybackPanelFragment : Fragment() { +class PlaybackPanelFragment : + ViewBindingFragment(), + Slider.OnChangeListener, + Slider.OnSliderTouchListener { private val playbackModel: PlaybackViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels() - private var lastBinding: FragmentPlaybackPanelBinding? = null - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, + override fun onCreateBinding(inflater: LayoutInflater): FragmentPlaybackPanelBinding { + return FragmentPlaybackPanelBinding.inflate(inflater) + } + + override fun onBindingCreated( + binding: FragmentPlaybackPanelBinding, savedInstanceState: Bundle? - ): View { - val binding = FragmentPlaybackPanelBinding.inflate(layoutInflater) + ) { val queueItem: MenuItem - // See onDestroyView for why we do this - lastBinding = binding - // --- UI SETUP --- - binding.lifecycleOwner = viewLifecycleOwner - binding.playbackModel = playbackModel - binding.detailModel = detailModel - binding.root.setOnApplyWindowInsetsListener { _, insets -> val bars = insets.systemBarInsetsCompat binding.root.updatePadding(top = bars.top, bottom = bars.bottom) @@ -85,20 +88,62 @@ class PlaybackPanelFragment : Fragment() { queueItem = menu.findItem(R.id.action_queue) } - // Make marquee of song title work - binding.playbackSong.isSelected = true - binding.playbackSeekBar.onConfirmListener = playbackModel::setPosition + binding.playbackSong.apply { + // Make marquee of the song title work + isSelected = true + setOnClickListener { playbackModel.song.value?.let { detailModel.navToItem(it) } } + } - // Abuse the play/pause FAB (see style definition for more info) - binding.playbackPlayPause.post { binding.playbackPlayPause.stateListAnimator = null } + binding.playbackArtist.setOnClickListener { + playbackModel.song.value?.let { detailModel.navToItem(it.album.artist) } + } + + binding.playbackAlbum.setOnClickListener { + playbackModel.song.value?.let { detailModel.navToItem(it.album) } + } + + binding.playbackSeekBar.apply { + addOnChangeListener(this@PlaybackPanelFragment) + addOnSliderTouchListener(this@PlaybackPanelFragment) + + // Composite a tint list based on the active/inactive colors + trackInactiveTintList = + MaterialColors.compositeARGBWithAlpha( + context.getAttrColorSafe(R.attr.colorSecondary), (255 * 0.2).toInt()) + .stateList + } + + binding.playbackLoop.setOnClickListener { playbackModel.incrementLoopStatus() } + + binding.playbackSkipPrev.setOnClickListener { playbackModel.skipPrev() } + + binding.playbackPlayPause.apply { + // Abuse the play/pause FAB (see style definition for more info) + post { binding.playbackPlayPause.stateListAnimator = null } + setOnClickListener { playbackModel.invertPlayingStatus() } + } + + binding.playbackSkipNext.setOnClickListener { playbackModel.skipNext() } + + binding.playbackShuffle.setOnClickListener { playbackModel.invertShuffleStatus() } // --- VIEWMODEL SETUP -- playbackModel.song.observe(viewLifecycleOwner) { song -> if (song != null) { logD("Updating song display to ${song.rawName}") - binding.song = song - binding.playbackSeekBar.setDuration(song.seconds) + binding.playbackCover.bindAlbumCover(song) + binding.playbackSong.text = song.resolvedName + binding.playbackArtist.text = song.resolvedArtistName + binding.playbackAlbum.text = song.resolvedAlbumName + + // Normally if a song had a duration + val seconds = song.seconds + binding.playbackDuration.text = seconds.toDuration(false) + binding.playbackSeekBar.apply { + valueTo = max(seconds, 1L).toFloat() + isEnabled = seconds > 0L + } } } @@ -125,8 +170,13 @@ class PlaybackPanelFragment : Fragment() { } } - playbackModel.position.observe(viewLifecycleOwner) { pos -> - binding.playbackSeekBar.setProgress(pos) + playbackModel.seconds.observe(viewLifecycleOwner) { pos -> + // Don't update the progress while we are seeking, that will make the SeekBar jump + // around. + if (!binding.playbackSeconds.isActivated) { + binding.playbackSeekBar.value = pos.toFloat() + binding.playbackSeconds.text = pos.toDuration(true) + } } playbackModel.nextUp.observe(viewLifecycleOwner) { @@ -146,17 +196,27 @@ class PlaybackPanelFragment : Fragment() { } logD("Fragment Created") - - return binding.root } - override fun onDestroyView() { - super.onDestroyView() + override fun onDestroyBinding(binding: FragmentPlaybackPanelBinding) { + binding.playbackSong.isSelected = false + binding.playbackSeekBar.removeOnChangeListener(this) + binding.playbackSeekBar.removeOnChangeListener(this) + } - // playbackSong will leak if we don't disable marquee, keep the binding around - // so that we can turn it off when we destroy the view. - lastBinding?.playbackSong?.isSelected = false - lastBinding = null + override fun onStartTrackingTouch(slider: Slider) { + requireBinding().playbackSeconds.isActivated = true + } + + override fun onStopTrackingTouch(slider: Slider) { + requireBinding().playbackSeconds.isActivated = false + playbackModel.setPosition(slider.value.toLong()) + } + + override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) { + if (fromUser) { + requireBinding().playbackSeconds.text = value.toLong().toDuration(true) + } } private fun navigateUp() { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt deleted file mode 100644 index 93546a6f1..000000000 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2021 Auxio Project - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.oxycblt.auxio.playback - -import android.annotation.SuppressLint -import android.content.Context -import android.util.AttributeSet -import androidx.constraintlayout.widget.ConstraintLayout -import com.google.android.material.color.MaterialColors -import com.google.android.material.slider.Slider -import org.oxycblt.auxio.R -import org.oxycblt.auxio.databinding.ViewSeekBarBinding -import org.oxycblt.auxio.music.toDuration -import org.oxycblt.auxio.util.getAttrColorSafe -import org.oxycblt.auxio.util.inflater -import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.util.stateList - -/** - * A custom view that bundles together a seekbar with a current duration and a total duration. The - * sub-views are specifically laid out so that the seekbar has an adequate touch height while still - * not having gobs of whitespace everywhere. - * - * TODO: Add smooth seeking [i.e seeking in sub-second values] - * @author OxygenCobalt - */ -@SuppressLint("RestrictedApi") -class PlaybackSeekBar -@JvmOverloads -constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) : - ConstraintLayout(context, attrs, defStyleRes), - Slider.OnChangeListener, - Slider.OnSliderTouchListener { - private val binding = ViewSeekBarBinding.inflate(context.inflater, this, true) - private val isSeeking: Boolean - get() = binding.playbackDurationCurrent.isActivated - - var onConfirmListener: ((Long) -> Unit)? = null - - init { - binding.seekBar.addOnChangeListener(this) - binding.seekBar.addOnSliderTouchListener(this) - - // Override the inactive color so that it lines up with the playback progress bar. - binding.seekBar.trackInactiveTintList = - MaterialColors.compositeARGBWithAlpha( - context.getAttrColorSafe(R.attr.colorSecondary), (255 * 0.2).toInt()) - .stateList - } - - fun setProgress(seconds: Long) { - // Don't update the progress while we are seeking, that will make the SeekBar jump around. - if (!isSeeking) { - binding.seekBar.value = seconds.toFloat() - binding.playbackDurationCurrent.text = seconds.toDuration(true) - } - } - - fun setDuration(seconds: Long) { - if (seconds == 0L) { - // One of two things occurred: - // - Android couldn't get the total duration of the song - // - The duration of the song was so low as to be rounded to zero when converted - // to seconds. - // In either of these cases, the seekbar is more or less useless. Disable it. - logD("Duration is 0, entering disabled state") - binding.seekBar.apply { - valueTo = 1f - isEnabled = false - } - } else { - binding.seekBar.apply { - valueTo = seconds.toFloat() - isEnabled = true - } - } - - binding.playbackSongDuration.text = seconds.toDuration(false) - } - - override fun onStartTrackingTouch(slider: Slider) { - binding.playbackDurationCurrent.isActivated = true - } - - override fun onStopTrackingTouch(slider: Slider) { - binding.playbackDurationCurrent.isActivated = false - onConfirmListener?.invoke(slider.value.toLong()) - } - - override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) { - if (fromUser) { - // Don't actually seek yet when the user moves the progress bar, as to make our - // player seek during every movement is both inefficient and weird. - binding.playbackDurationCurrent.text = value.toLong().toDuration(true) - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 6d143a8f8..099c12328 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -58,7 +58,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { private val mIsPlaying = MutableLiveData(false) private val mIsShuffling = MutableLiveData(false) private val mLoopMode = MutableLiveData(LoopMode.NONE) - private val mPosition = MutableLiveData(0L) + private val mSeconds = MutableLiveData(0L) // Queue private val mNextUp = MutableLiveData(listOf()) @@ -82,8 +82,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { val loopMode: LiveData get() = mLoopMode /** The current playback position, in seconds */ - val position: LiveData - get() = mPosition + val seconds: LiveData + get() = mSeconds /** The queue, without the previous items. */ val nextUp: LiveData> @@ -336,7 +336,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { } override fun onPositionUpdate(position: Long) { - mPosition.value = position / 1000 + mSeconds.value = position / 1000 } override fun onQueueUpdate(queue: List, index: Int) { diff --git a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt index 8473aeeb1..a20ede0bf 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt @@ -162,6 +162,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : // / --- CONTROL METHODS --- /** + * Show the panel, only if it's hidden. + * @return if the panel was shown */ fun show(): Boolean { if (panelState == PanelState.HIDDEN) { @@ -172,6 +174,10 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : return false } + /** + * Expand the panel if it is currently collapsed. + * @return If the panel was expanded + */ fun expand(): Boolean { if (panelState == PanelState.COLLAPSED) { applyState(PanelState.EXPANDED) @@ -183,7 +189,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : /** * Collapse the panel if it is currently expanded. - * @return If the panel was collapsed or not. + * @return If the panel was collapsed */ fun collapse(): Boolean { if (panelState == PanelState.EXPANDED) { @@ -195,6 +201,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : } /** + * Hide the panel if it is not hidden. + * @return If the panel was hidden */ fun hide(): Boolean { if (panelState != PanelState.HIDDEN) { diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingFragment.kt b/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingFragment.kt new file mode 100644 index 000000000..668db285d --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingFragment.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Auxio Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.viewbinding.ViewBinding + +/** A fragment enabling ViewBinding inflation and usage across the fragment lifecycle. */ +abstract class ViewBindingFragment : Fragment() { + private var mBinding: T? = null + + abstract fun onCreateBinding(inflater: LayoutInflater): T + abstract fun onBindingCreated(binding: T, savedInstanceState: Bundle?) + abstract fun onDestroyBinding(binding: T) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val binding = onCreateBinding(inflater).also { mBinding = it } + onBindingCreated(binding, savedInstanceState) + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + onDestroyBinding(requireBinding()) + mBinding = null + } + + protected val binding: T? + get() = mBinding + + protected fun requireBinding(): T { + return requireNotNull(mBinding) { + "ViewBinding was not available, as the fragment is not in a valid state" + } + } +} diff --git a/app/src/main/res/layout-land/fragment_playback_panel.xml b/app/src/main/res/layout-land/fragment_playback_panel.xml index 9459c2287..fbbd54e49 100644 --- a/app/src/main/res/layout-land/fragment_playback_panel.xml +++ b/app/src/main/res/layout-land/fragment_playback_panel.xml @@ -4,22 +4,6 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".playback.PlaybackPanelFragment"> - - - - - - - - - - + app:title="@string/lbl_playback" + tools:subtitle="@string/lbl_all_songs" /> @@ -78,10 +58,8 @@ style="@style/Widget.Auxio.TextView.Secondary" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/spacing_mid_large" - android:layout_marginEnd="@dimen/spacing_mid_large" - android:onClick="@{() -> detailModel.navToItem(playbackModel.song.album.artist)}" - android:text="@{song.resolvedArtistName}" + android:layout_marginStart="@dimen/spacing_medium" + android:layout_marginEnd="@dimen/spacing_medium" app:layout_constraintBottom_toTopOf="@+id/playback_album" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" @@ -94,10 +72,8 @@ style="@style/Widget.Auxio.TextView.Secondary" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/spacing_mid_large" - android:layout_marginEnd="@dimen/spacing_mid_large" - android:onClick="@{() -> detailModel.navToItem(playbackModel.song.album)}" - android:text="@{song.resolvedAlbumName}" + android:layout_marginStart="@dimen/spacing_medium" + android:layout_marginEnd="@dimen/spacing_medium" app:layout_constraintBottom_toBottomOf="@+id/playback_cover" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" @@ -105,19 +81,45 @@ app:layout_constraintTop_toBottomOf="@+id/playback_artist" tools:text="Album Name" /> - - + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:thumbRadius="@dimen/slider_thumb_radius" /> + + + + @@ -77,8 +73,6 @@ style="@style/Widget.Auxio.TextView.Secondary" android:layout_width="0dp" android:layout_height="wrap_content" - android:onClick="@{() -> detailModel.navToItem(playbackModel.song.album.artist)}" - android:text="@{song.resolvedArtistName}" app:layout_constraintBottom_toTopOf="@+id/playback_album" app:layout_constraintStart_toStartOf="@+id/playback_song_container" app:layout_constraintEnd_toEndOf="@+id/playback_song_container" @@ -91,8 +85,6 @@ style="@style/Widget.Auxio.TextView.Secondary" android:layout_width="0dp" android:layout_height="wrap_content" - android:onClick="@{() -> detailModel.navToItem(playbackModel.song.album)}" - android:text="@{song.resolvedAlbumName}" app:layout_constraintBottom_toTopOf="@+id/playback_seek_bar" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="@+id/playback_song_container" @@ -100,17 +92,48 @@ app:layout_constraintTop_toBottomOf="@+id/playback_artist" tools:text="Album Name" /> - + app:layout_constraintStart_toEndOf="@+id/playback_cover" + app:layout_constraintTop_toBottomOf="@+id/playback_album" + app:thumbRadius="@dimen/slider_thumb_radius" /> + + + + - - - - - - - - - - - + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:thumbRadius="@dimen/slider_thumb_radius" /> + + + + - - - - - - - + app:layout_constraintTop_toBottomOf="@+id/playback_album" + app:thumbRadius="@dimen/slider_thumb_radius" /> + + + + - - - - - - - - - - - - - - - - - - - - - @@ -50,10 +33,8 @@ style="@style/Widget.Auxio.TextView.Primary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/spacing_mid_large" - android:layout_marginEnd="@dimen/spacing_mid_large" - android:onClick="@{() -> detailModel.navToItem(playbackModel.song)}" - android:text="@{song.resolvedName}" + android:layout_marginStart="@dimen/spacing_medium" + android:layout_marginEnd="@dimen/spacing_medium" app:layout_constraintBottom_toTopOf="@+id/playback_artist" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -65,10 +46,8 @@ style="@style/Widget.Auxio.TextView.Secondary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/spacing_mid_large" - android:layout_marginEnd="@dimen/spacing_mid_large" - android:onClick="@{() -> detailModel.navToItem(playbackModel.song.album.artist)}" - android:text="@{song.resolvedArtistName}" + android:layout_marginStart="@dimen/spacing_medium" + android:layout_marginEnd="@dimen/spacing_medium" app:layout_constraintBottom_toTopOf="@+id/playback_album" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -79,24 +58,52 @@ style="@style/Widget.Auxio.TextView.Secondary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/spacing_mid_large" - android:layout_marginEnd="@dimen/spacing_mid_large" - android:onClick="@{() -> detailModel.navToItem(playbackModel.song.album)}" - android:text="@{song.resolvedAlbumName}" + android:layout_marginStart="@dimen/spacing_medium" + android:layout_marginEnd="@dimen/spacing_medium" app:layout_constraintBottom_toTopOf="@+id/playback_seek_bar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" tools:text="Album Name" /> - + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:thumbRadius="@dimen/slider_thumb_radius" /> + + + + 24dp 32dp + -8dp + 48dp 64dp