diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingBottomSheetDialogFragment.kt b/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingBottomSheetDialogFragment.kt index 3a5adce95..e3087d42f 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingBottomSheetDialogFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingBottomSheetDialogFragment.kt @@ -20,16 +20,25 @@ package org.oxycblt.auxio.ui import android.content.Context import android.os.Bundle +import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.annotation.StyleRes import androidx.fragment.app.DialogFragment import androidx.viewbinding.ViewBinding import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import java.lang.reflect.Field +import java.lang.reflect.Method +import org.oxycblt.auxio.util.coordinatorLayoutBehavior +import org.oxycblt.auxio.util.getDimenPixels +import org.oxycblt.auxio.util.lazyReflectedField +import org.oxycblt.auxio.util.lazyReflectedMethod import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.systemBarInsetsCompat import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -91,7 +100,10 @@ abstract class ViewBindingBottomSheetDialogFragment : inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ) = onCreateBinding(inflater).also { _binding = it }.root + ): View { + val root = onCreateBinding(inflater).also { _binding = it }.root + return EdgeToEdgeFixWrapperLayout(root.context).apply { addView(root) } + } final override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -110,18 +122,66 @@ abstract class ViewBindingBottomSheetDialogFragment : private inner class TweakedBottomSheetDialog @JvmOverloads constructor(context: Context, @StyleRes theme: Int = 0) : BottomSheetDialog(context, theme) { + private var avoidUnusableCollapsedState = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // Collapsed state is bugged in phone landscape mode and shows only 10% of the dialog. - // Just disable it and go directly from expanded -> hidden. - behavior.skipCollapsed = true + // Automatic peek height calculations are bugged in phone landscape mode and show only + // 10% of the dialog. Just disable it in that case and go directly from expanded -> + // hidden. + val metrics = context.resources.displayMetrics + avoidUnusableCollapsedState = + metrics.heightPixels - metrics.widthPixels < + context.getDimenPixels( + com.google.android.material.R.dimen.design_bottom_sheet_peek_height_min) + behavior.skipCollapsed = avoidUnusableCollapsedState } override fun onStart() { super.onStart() - // Manually trigger an expanded transition to make window insets actually apply to - // the dialog on the first layout pass. I don't know why this works. - behavior.state = BottomSheetBehavior.STATE_EXPANDED + if (avoidUnusableCollapsedState) { + // skipCollapsed isn't enough, also need to immediately snap to expanded state. + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + } + } + + private class EdgeToEdgeFixWrapperLayout + @JvmOverloads + constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : + FrameLayout(context, attrs, defStyleAttr) { + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + // BottomSheetBehavior's normal window inset behavior is awful. It doesn't + // follow true edge-to-edge and instead just blindly pads the bottom part of + // the view, causing visual clipping. We can turn it off, but that throws + // of the peek height calculation and results in a collapsed state that only + // expands a few pixels (specifically the size of the bottom inset) into an + // expanded state. So, ideally we would just vendor and eliminate the padding + // changes entirely, but due to layout dependencies that requires vendoring + // both BottomSheetDialog and BottomSheetDialogFragment, which I generally + // don't want to do. Instead, we deliberately clobber the window insets listener + // of our bottom sheet and only re-implement the update of the cached inset + // variables and the peek height update. This way, the peek height calculation + // remains consistent and the top inset animation continues to work correctly + // without the other absurd edge-to-edge behaviors. + // TODO: Do a fix for this upstream + (parent as View).setOnApplyWindowInsetsListener { v, insets -> + val bsb = v.coordinatorLayoutBehavior as BottomSheetBehavior + BSB_INSET_TOP_FIELD.set(bsb, insets.systemBarInsetsCompat.top) + BSB_INSET_BOTTOM_FIELD.set(bsb, insets.systemBarInsetsCompat.bottom) + BSB_UPDATE_PEEK_HEIGHT_METHOD.invoke(bsb, false) + insets + } + } + + private companion object { + val BSB_INSET_TOP_FIELD: Field by + lazyReflectedField(BottomSheetBehavior::class, "insetTop") + val BSB_INSET_BOTTOM_FIELD: Field by + lazyReflectedField(BottomSheetBehavior::class, "insetBottom") + val BSB_UPDATE_PEEK_HEIGHT_METHOD: Method by + lazyReflectedMethod(BottomSheetBehavior::class, "updatePeekHeight", Boolean::class) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/util/LangUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/LangUtil.kt index d1b0125eb..8b8d8c6a1 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/LangUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/LangUtil.kt @@ -82,8 +82,10 @@ fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy { * @param clazz The [KClass] to reflect into. * @param method The name of the method to obtain. */ -fun lazyReflectedMethod(clazz: KClass<*>, method: String) = lazy { - clazz.java.getDeclaredMethod(method).also { it.isAccessible = true } +fun lazyReflectedMethod(clazz: KClass<*>, method: String, vararg params: KClass<*>) = lazy { + clazz.java.getDeclaredMethod(method, *params.map { it.java }.toTypedArray()).also { + it.isAccessible = true + } } /** diff --git a/app/src/main/res/values/styles_ui.xml b/app/src/main/res/values/styles_ui.xml index 5106e12b9..18e87da49 100644 --- a/app/src/main/res/values/styles_ui.xml +++ b/app/src/main/res/values/styles_ui.xml @@ -32,19 +32,11 @@