ui: use scroll indicators

Use basic scroll indicators when a dialog shows a list.

Mostly for material guidelines. Excluded dialogs and int pref dialog
have not been modified, as I am still working on revamping those.
This commit is contained in:
OxygenCobalt 2022-06-12 09:31:59 -06:00
parent 48e6868f39
commit 5d124ce771
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
15 changed files with 141 additions and 17 deletions

View file

@ -5,8 +5,9 @@
#### What's New #### What's New
- Added a new view for song properties (Such as Bitrate) - Added a new view for song properties (Such as Bitrate)
- Folders on external drives can now be excluded on Android Q+ [#134] - Folders on external drives can now be excluded on Android Q+ [#134]
- Playback bar now has a skip action - The playback bar now has a new design, with an improved progress
- When playing, the cover now shows an animated indicator indicator and a skip action
- When playing, covers now shows an animated indicator
#### What's Improved #### What's Improved
- The toolbar in the home UI now collapses when scrolling - The toolbar in the home UI now collapses when scrolling
@ -20,7 +21,7 @@
- Songs with no data (i.e size of 0) are now filtered out - Songs with no data (i.e size of 0) are now filtered out
#### Dev/Meta #### Dev/Meta
- New translations [Fjuro -> Czech] - New translations [Fjuro -> Czech, Konstantin Tutsch -> German]
- Moved music loading to a foreground service - Moved music loading to a foreground service
- Phased out `ImageButton` for `MaterialButton` - Phased out `ImageButton` for `MaterialButton`
- Unified icon sizing - Unified icon sizing

View file

@ -110,6 +110,10 @@ class DetailViewModel : ViewModel(), MusicStore.Callback {
generateDetailSong(context, song) generateDetailSong(context, song)
} }
fun clearSong() {
_currentSong.value = null
}
fun setAlbumId(id: Long) { fun setAlbumId(id: Long) {
if (_currentAlbum.value?.id == id) return if (_currentAlbum.value?.id == id) return
val library = unlikelyToBeNull(musicStore.library) val library = unlikelyToBeNull(musicStore.library)

View file

@ -119,7 +119,6 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener {
logD("Navigating to another album") logD("Navigating to another album")
findNavController().navigate(GenreDetailFragmentDirections.actionShowAlbum(item.id)) findNavController().navigate(GenreDetailFragmentDirections.actionShowAlbum(item.id))
} }
// All items will launch new detail fragments.
is Artist -> { is Artist -> {
logD("Navigating to another artist") logD("Navigating to another artist")
findNavController() findNavController()

View file

@ -44,6 +44,7 @@ class ReadOnlyTextInput : TextInputEditText {
) : super(context, attrs, defStyleAttr) ) : super(context, attrs, defStyleAttr)
init { init {
setTextIsSelectable(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusable = View.FOCUSABLE_AUTO focusable = View.FOCUSABLE_AUTO
} }

View file

@ -50,6 +50,11 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
launch { detailModel.currentSong.collect(::updateSong) } launch { detailModel.currentSong.collect(::updateSong) }
} }
override fun onDestroy() {
super.onDestroy()
detailModel.clearSong()
}
private fun updateSong(song: DetailViewModel.DetailSong?) { private fun updateSong(song: DetailViewModel.DetailSong?) {
val binding = requireBinding() val binding = requireBinding()

View file

@ -95,6 +95,7 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
} }
} }
// TODO: Pause indicator animation when not playing
viewHolder.itemView.isActivated = shouldHighlightViewHolder(item) viewHolder.itemView.isActivated = shouldHighlightViewHolder(item)
} }

View file

@ -115,7 +115,6 @@ class ExcludedDialog :
override fun onRemoveDirectory(dir: Dir.Relative) { override fun onRemoveDirectory(dir: Dir.Relative) {
excludedAdapter.data.remove(dir) excludedAdapter.data.remove(dir)
requireBinding().excludedEmpty.isVisible = excludedAdapter.data.currentList.isEmpty()
} }
private fun addDocTreePath(uri: Uri?) { private fun addDocTreePath(uri: Uri?) {

View file

@ -37,6 +37,7 @@ class IntListPreferenceDialog : PreferenceDialogFragmentCompat() {
builder.setTitle(listPreference.title) builder.setTitle(listPreference.title)
builder.setPositiveButton(null, null) builder.setPositiveButton(null, null)
builder.setNegativeButton(R.string.lbl_cancel, null) builder.setNegativeButton(R.string.lbl_cancel, null)
// TODO: Replace this with an in-house view
builder.setSingleChoiceItems(listPreference.entries, listPreference.getValueIndex()) { builder.setSingleChoiceItems(listPreference.entries, listPreference.getValueIndex()) {
_, _,
index -> index ->

View file

@ -0,0 +1,101 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.ui
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.annotation.AttrRes
import androidx.core.view.isInvisible
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDivider
import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.getDimenSizeSafe
import org.oxycblt.auxio.util.logD
/**
* A RecyclerView that enables something resembling the android:scrollIndicators attribute.
* Only used in dialogs.
* @author OxygenCobalt
*/
class DialogRecyclerView
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
RecyclerView(context, attrs, defStyleAttr) {
private val topDivider = MaterialDivider(context)
private val bottomDivider = MaterialDivider(context)
private val spacingMedium = context.getDimenSizeSafe(R.dimen.spacing_medium)
init {
updatePadding(top = spacingMedium)
overScrollMode = OVER_SCROLL_NEVER
overlay.apply {
add(topDivider)
add(bottomDivider)
}
addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val manager = recyclerView.layoutManager as LinearLayoutManager
logD("top invisible: ${manager.findFirstCompletelyVisibleItemPosition() < 1}")
// logD(
// "bottom invisible:
// ${manager.findLastCompletelyVisibleItemPosition() < (manager.itemCount -
// 1)}")
topDivider.isInvisible = manager.findFirstCompletelyVisibleItemPosition() < 1
bottomDivider.isInvisible =
manager.findLastCompletelyVisibleItemPosition() == (manager.itemCount - 1)
}
})
}
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
super.onMeasure(widthSpec, heightSpec)
measureDivider(topDivider)
measureDivider(bottomDivider)
}
private fun measureDivider(divider: MaterialDivider) {
val widthMeasureSpec =
ViewGroup.getChildMeasureSpec(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
0,
divider.layoutParams.width)
val heightMeasureSpec =
ViewGroup.getChildMeasureSpec(
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
0,
divider.layoutParams.height)
divider.measure(widthMeasureSpec, heightMeasureSpec)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
topDivider.layout(l, spacingMedium, r, spacingMedium + topDivider.measuredHeight)
bottomDivider.layout(l, measuredHeight - bottomDivider.measuredHeight, r, b)
}
}

View file

@ -1,15 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" <org.oxycblt.auxio.ui.DialogRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/accent_recycler" android:id="@+id/accent_recycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:overScrollMode="never"
android:paddingStart="@dimen/spacing_medium" android:paddingStart="@dimen/spacing_medium"
android:paddingTop="@dimen/spacing_medium"
android:paddingEnd="@dimen/spacing_medium" android:paddingEnd="@dimen/spacing_medium"
android:paddingBottom="@dimen/spacing_small"
app:layoutManager="org.oxycblt.auxio.ui.accent.AccentGridLayoutManager" app:layoutManager="org.oxycblt.auxio.ui.accent.AccentGridLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/accent_cancel" app:layout_constraintBottom_toTopOf="@+id/accent_cancel"
app:layout_constraintTop_toBottomOf="@+id/accent_header" app:layout_constraintTop_toBottomOf="@+id/accent_header"

View file

@ -3,7 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
style="@style/Widget.Auxio.Dialog.NestedScrollView">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -3,12 +3,12 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:paddingTop="@dimen/spacing_medium">
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
style="@style/Widget.Auxio.Dialog.NestedScrollView">
<LinearLayout <LinearLayout
android:id="@+id/detail_container" android:id="@+id/detail_container"
@ -18,7 +18,8 @@
android:paddingStart="@dimen/spacing_mid_large" android:paddingStart="@dimen/spacing_mid_large"
android:paddingEnd="@dimen/spacing_mid_large" android:paddingEnd="@dimen/spacing_mid_large"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible"
android:showDividers="middle">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -1,12 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" <org.oxycblt.auxio.ui.DialogRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tab_recycler" android:id="@+id/tab_recycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:overScrollMode="never"
android:paddingTop="@dimen/spacing_medium"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/accent_cancel" app:layout_constraintBottom_toTopOf="@+id/accent_cancel"
app:layout_constraintTop_toBottomOf="@+id/accent_header" app:layout_constraintTop_toBottomOf="@+id/accent_header"

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Dialog style that properly impelments dividers in a NestedScrollView -->
<style name="Widget.Auxio.Dialog.NestedScrollView" parent="">
<item name="android:overScrollMode">never</item>
<item name="android:scrollIndicators">top|bottom</item>
<item name="android:paddingTop">@dimen/spacing_medium</item>
</style>
</resources>

View file

@ -33,6 +33,12 @@
<item name="android:layout_marginStart">0dp</item> <item name="android:layout_marginStart">0dp</item>
</style> </style>
<!-- Dialog style that properly impelments dividers in a NestedScrollView -->
<style name="Widget.Auxio.Dialog.NestedScrollView" parent="">
<item name="android:overScrollMode">never</item>
<item name="android:paddingTop">@dimen/spacing_medium</item>
</style>
<!-- Widget TextView that mimics the main Auxio Primary/Secondary TextViews. --> <!-- Widget TextView that mimics the main Auxio Primary/Secondary TextViews. -->
<style name="Widget.Auxio.TextView.AppWidget" parent="Widget.Auxio.TextView.Base"> <style name="Widget.Auxio.TextView.AppWidget" parent="Widget.Auxio.TextView.Base">
<item name="android:singleLine">true</item> <item name="android:singleLine">true</item>