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:
parent
48e6868f39
commit
5d124ce771
15 changed files with 141 additions and 17 deletions
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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?) {
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
101
app/src/main/java/org/oxycblt/auxio/ui/DialogRecyclerView.kt
Normal file
101
app/src/main/java/org/oxycblt/auxio/ui/DialogRecyclerView.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
9
app/src/main/res/values-v23/styles_android.xml
Normal file
9
app/src/main/res/values-v23/styles_android.xml
Normal 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>
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue