From de3cc7958f946dc7940a7f139ce26e24dcfafef6 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 2 Aug 2022 11:13:12 -0600 Subject: [PATCH] ui: rework bottom sheet state management Try to make the bottom sheet states more coherent, especially regarding when playback ends. --- .../java/org/oxycblt/auxio/MainFragment.kt | 69 +++++++++++++++---- .../detail/recycler/AlbumDetailAdapter.kt | 2 +- .../auxio/playback/PlaybackSheetBehavior.kt | 23 +------ .../playback/state/PlaybackStateManager.kt | 10 +-- .../playback/system/MediaSessionComponent.kt | 13 ++-- .../org/oxycblt/auxio/settings/Settings.kt | 8 +-- .../java/org/oxycblt/auxio/util/LogUtil.kt | 18 ++++- app/src/main/res/drawable/ic_queue_24.xml | 11 --- app/src/main/res/layout/fragment_home.xml | 1 + app/src/main/res/layout/fragment_main.xml | 2 + app/src/main/res/layout/fragment_queue.xml | 1 - app/src/main/res/values/strings.xml | 1 + 12 files changed, 92 insertions(+), 67 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_queue_24.xml diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 376826c65..c0d90bddc 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -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 } /** 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 d36bfa00f..edcc08b3b 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 @@ -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 = "" + val duration = item.durationSecs.formatDuration(true) text = if (item.releaseType != null) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt index 5a4140625..a89a592da 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt @@ -37,28 +37,9 @@ class PlaybackSheetBehavior(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 - } - } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 017dda70c..5dabd54f2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -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]. */ diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt index 729936138..65e89b553 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt @@ -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?) { diff --git a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt index 951f86c43..81ff48732 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -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 = diff --git a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt index 3e17c9e02..9693df59c 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt @@ -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!") } } diff --git a/app/src/main/res/drawable/ic_queue_24.xml b/app/src/main/res/drawable/ic_queue_24.xml deleted file mode 100644 index eb1011e58..000000000 --- a/app/src/main/res/drawable/ic_queue_24.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 056c53550..9eefb5abf 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -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"> diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 47cd3dfca..ab842449f 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -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" /> diff --git a/app/src/main/res/layout/fragment_queue.xml b/app/src/main/res/layout/fragment_queue.xml index 3cbc99e6c..7399b626b 100644 --- a/app/src/main/res/layout/fragment_queue.xml +++ b/app/src/main/res/layout/fragment_queue.xml @@ -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" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 591f3ed84..3ed41e5b1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -236,6 +236,7 @@ Remove this queue song Move this queue song + Open the queue Move this tab Clear search query Remove folder