ui: rework bottom sheet state management
Try to make the bottom sheet states more coherent, especially regarding when playback ends.
This commit is contained in:
parent
35cfea78df
commit
de3cc7958f
12 changed files with 92 additions and 67 deletions
|
@ -52,6 +52,7 @@ class MainFragment :
|
|||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
private var callback: DynamicBackPressedCallback? = null
|
||||
private var lastInsets: WindowInsets? = null
|
||||
private var keepPlaybackSheetHidden = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -117,11 +118,6 @@ class MainFragment :
|
|||
override fun onPreDraw(): Boolean {
|
||||
// CoordinatorLayout is insane and thus makes bottom sheet callbacks insane. Do our
|
||||
// checks before every draw.
|
||||
handleSheetTransitions()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleSheetTransitions() {
|
||||
val binding = requireBinding()
|
||||
val playbackSheetBehavior =
|
||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
||||
|
@ -162,9 +158,25 @@ class MainFragment :
|
|||
isInvisible = alpha == 0f
|
||||
}
|
||||
|
||||
playbackSheetBehavior.isDraggable =
|
||||
playbackSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN &&
|
||||
if (playbackModel.song.value != null) {
|
||||
// Hack around the playback sheet intercepting swipe events on the queue bar
|
||||
playbackSheetBehavior.isDraggable =
|
||||
queueSheetBehavior.state == BottomSheetBehavior.STATE_COLLAPSED
|
||||
} else {
|
||||
// Sometimes lingering drags can un-hide the playback sheet even when we intend to
|
||||
// hide it, make sure we keep it hidden.
|
||||
tryHideAll()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun updateSong(song: Song?) {
|
||||
if (song != null) {
|
||||
tryUnhideAll()
|
||||
} else {
|
||||
tryHideAll()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMainNavigation(action: MainNavigationAction?) {
|
||||
|
@ -212,11 +224,10 @@ class MainFragment :
|
|||
|
||||
if (playbackSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN &&
|
||||
playbackSheetBehavior.state != BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
playbackSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
|
||||
val queueSheetBehavior =
|
||||
binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior
|
||||
|
||||
playbackSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
queueSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
|
||||
return true
|
||||
|
@ -225,15 +236,45 @@ class MainFragment :
|
|||
return false
|
||||
}
|
||||
|
||||
private fun updateSong(song: Song?) {
|
||||
private fun tryUnhideAll(): Boolean {
|
||||
val binding = requireBinding()
|
||||
val playbackSheetBehavior =
|
||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
||||
if (song != null) {
|
||||
playbackSheetBehavior.unhideSafe()
|
||||
} else {
|
||||
playbackSheetBehavior.hideSafe()
|
||||
|
||||
if (playbackSheetBehavior.state == BottomSheetBehavior.STATE_HIDDEN) {
|
||||
val queueSheetBehavior =
|
||||
binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior
|
||||
|
||||
playbackSheetBehavior.isDraggable = true
|
||||
playbackSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
|
||||
queueSheetBehavior.isDraggable = true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun tryHideAll(): Boolean {
|
||||
val binding = requireBinding()
|
||||
val playbackSheetBehavior =
|
||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
||||
|
||||
if (playbackSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
|
||||
val queueSheetBehavior =
|
||||
binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior
|
||||
|
||||
playbackSheetBehavior.isDraggable = false
|
||||
queueSheetBehavior.isDraggable = false
|
||||
|
||||
playbackSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
queueSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -130,7 +130,7 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite
|
|||
|
||||
val songCount = context.getPluralSafe(R.plurals.fmt_song_count, item.songs.size)
|
||||
|
||||
val duration = "<duration>"
|
||||
val duration = item.durationSecs.formatDuration(true)
|
||||
|
||||
text =
|
||||
if (item.releaseType != null) {
|
||||
|
|
|
@ -37,28 +37,9 @@ class PlaybackSheetBehavior<V : View>(context: Context, attributeSet: AttributeS
|
|||
|
||||
// Hack around issue where the playback sheet will try to intercept nested scrolling events
|
||||
// before the queue sheet.
|
||||
override fun onInterceptTouchEvent(
|
||||
parent: CoordinatorLayout,
|
||||
child: V,
|
||||
event: MotionEvent
|
||||
): Boolean = super.onInterceptTouchEvent(parent, child, event) && state != STATE_EXPANDED
|
||||
override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent) =
|
||||
super.onInterceptTouchEvent(parent, child, event) && state != STATE_EXPANDED
|
||||
|
||||
// Note: This is an extension to Auxio's vendored BottomSheetBehavior
|
||||
override fun enableHidingGestures() = false
|
||||
|
||||
/** Hide this sheet in a safe manner. */
|
||||
fun hideSafe() {
|
||||
if (state != STATE_HIDDEN) {
|
||||
isDraggable = false
|
||||
state = STATE_HIDDEN
|
||||
}
|
||||
}
|
||||
|
||||
/** Unhide this sheet in a safe manner. */
|
||||
fun unhideSafe() {
|
||||
if (state == STATE_HIDDEN) {
|
||||
state = STATE_COLLAPSED
|
||||
isDraggable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.oxycblt.auxio.playback.state
|
|||
|
||||
import kotlin.math.max
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.music.Album
|
||||
|
@ -400,14 +399,7 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
suspend fun wipeState(database: PlaybackStateDatabase) {
|
||||
logD("Wiping state")
|
||||
withContext(Dispatchers.IO) {
|
||||
delay(5000)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
index = -1
|
||||
notifyNewPlayback()
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.IO) { database.write(null) }
|
||||
}
|
||||
|
||||
/** Sanitize the state with [newLibrary]. */
|
||||
|
|
|
@ -42,8 +42,13 @@ import org.oxycblt.auxio.util.logD
|
|||
/**
|
||||
* The component managing the [MediaSessionCompat] instance.
|
||||
*
|
||||
* I really don't like how I have to do this, but until I can work with the ExoPlayer queue system
|
||||
* using something like MediaSessionConnector is more or less impossible.
|
||||
* Media3 is a joke. It tries so hard to be "hElpfUl" and implement so many fundamental behaviors
|
||||
* into a one-size-fits-all package that it only ends up causing unending bugs and frustration. The
|
||||
* queue system is horribly designed, the notification code is outdated, and the overstretched
|
||||
* abstractions result in terrible performance bottlenecks and insane state bugs..
|
||||
*
|
||||
* Show me a way to adapt my internal queue into the new system and I will change my mind, but
|
||||
* otherwise, I will stick with my normal system that works correctly.
|
||||
*
|
||||
* @author OxygenCobalt
|
||||
*
|
||||
|
@ -265,12 +270,12 @@ class MediaSessionComponent(
|
|||
|
||||
override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
|
||||
super.onPlayFromMediaId(mediaId, extras)
|
||||
// STUB: Unimplemented
|
||||
// STUB: Unimplemented, no media browser
|
||||
}
|
||||
|
||||
override fun onPlayFromUri(uri: Uri?, extras: Bundle?) {
|
||||
super.onPlayFromUri(uri, extras)
|
||||
// STUB: Unimplemented
|
||||
// STUB: Unimplemented, no media browser
|
||||
}
|
||||
|
||||
override fun onPlayFromSearch(query: String?, extras: Bundle?) {
|
||||
|
|
|
@ -185,14 +185,14 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
val pauseOnRepeat: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_repeat_pause), false)
|
||||
|
||||
/** Whether to be actively watching for changes in the music library. */
|
||||
val shouldBeObserving: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_observing), false)
|
||||
|
||||
/** Whether to parse metadata directly with ExoPlayer. */
|
||||
val useQualityTags: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_quality_tags), false)
|
||||
|
||||
/** Whether to be actively watching for changes in the music library. */
|
||||
val shouldBeObserving: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_observing), false)
|
||||
|
||||
/** Get the list of directories that music should be hidden/loaded from. */
|
||||
fun getMusicDirs(storageManager: StorageManager): MusicDirs {
|
||||
val dirs =
|
||||
|
|
|
@ -80,7 +80,21 @@ private val Any.autoTag: String
|
|||
* taking work others did and making it objectively worse so you could arbitrage a fraction of a
|
||||
* penny on every AdMob impression you get? You could do so many great things if you simply had the
|
||||
* courage to come up with an idea of your own. If you still want to go on, I guess the only thing I
|
||||
* can say is this: JUNE 1989 TIANAMEN SQUARE PROTESTS AND MASSACRE 六四事件
|
||||
* can say is this:
|
||||
*
|
||||
* JUNE 1989 TIANAMEN SQUARE PROTESTS AND MASSACRE 六四事件
|
||||
*
|
||||
* UYGHUR GENOCIDE 新疆种族灭绝指控
|
||||
*
|
||||
* XINJIANG INTERMENT CAMPS 新疆再教育營
|
||||
*
|
||||
* KASHMIR INDEPENDENCE MOVEMENT
|
||||
*
|
||||
* WOMEN'S RIGHTS IN THE ISLAMIC REPUBLIC OF IRAN حقوق زنان در ایران
|
||||
*
|
||||
* 2022 RUSSIAN INVASION OF UKRAINE Вторжение России на Украину
|
||||
*
|
||||
* KURDISTAN WORKERS PARTY KÜRDISTAN İŞÇI PARTISI (PKK)
|
||||
*/
|
||||
private fun basedCopyleftNotice() {
|
||||
if (BuildConfig.APPLICATION_ID != "org.oxycblt.auxio" &&
|
||||
|
@ -88,6 +102,6 @@ private fun basedCopyleftNotice() {
|
|||
Log.d(
|
||||
"Auxio Project",
|
||||
"Friendly reminder: Auxio is licensed under the " +
|
||||
"GPLv3 and all modifications must be made open source!")
|
||||
"GPLv3 and all derivative apps must be made open source!")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16,20Q14.75,20 13.875,19.125Q13,18.25 13,17Q13,15.75 13.875,14.875Q14.75,14 16,14Q16.275,14 16.525,14.037Q16.775,14.075 17,14.2V6H22V8H19V17Q19,18.25 18.125,19.125Q17.25,20 16,20ZM3,16V14H11V16ZM3,12V10H15V12ZM3,8V6H15V8Z" />
|
||||
</vector>
|
|
@ -53,6 +53,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:fitsSystemWindows="true"
|
||||
android:layout_margin="@dimen/spacing_medium"
|
||||
android:visibility="invisible">
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
android:scaleType="center"
|
||||
android:paddingBottom="@dimen/spacing_small"
|
||||
android:src="@drawable/ic_down_24"
|
||||
android:contentDescription="@string/desc_queue_bar"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
|
@ -62,6 +63,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="@string/lbl_queue"
|
||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
||||
android:textColor="?attr/colorOnSurfaceVariant"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/handle"
|
||||
app:layout_constraintEnd_toEndOf="@+id/handle"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
android:id="@+id/queue_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:overScrollMode="ifContentScrolls"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
tools:listitem="@layout/item_queue_song" />
|
||||
|
|
|
@ -236,6 +236,7 @@
|
|||
|
||||
<string name="desc_clear_queue_item">Remove this queue song</string>
|
||||
<string name="desc_queue_handle">Move this queue song</string>
|
||||
<string name="desc_queue_bar">Open the queue</string>
|
||||
<string name="desc_tab_handle">Move this tab</string>
|
||||
<string name="desc_clear_search">Clear search query</string>
|
||||
<string name="desc_music_dir_delete">Remove folder</string>
|
||||
|
|
Loading…
Reference in a new issue