Browse Source

More bottom sheet improvements (#5183)

* Edge-to-edge bottom sheet when possible

* ReaderSettingsSheet: Animate background dim changes

* Adjust modal bottom sheet in-out animation

* Use public method to get bottom sheet behavior

* Set bottom sheet peek size to 50% screen height

The current auto peek height gives too low value on landscape orientation

* Set bottom sheet navigation bar scrim when needed
Ivan Iskandar 3 years ago
parent
commit
9f744bc445

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt

@@ -33,7 +33,7 @@ class SourceFilterSheet(
 
     override fun show() {
         super.show()
-        sheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
+        behavior.state = BottomSheetBehavior.STATE_EXPANDED
     }
 
     fun setFilters(items: List<IFlexible<*>>) {

+ 2 - 12
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.ui.main
 
 import android.app.SearchManager
 import android.content.Intent
-import android.graphics.Color
-import android.os.Build
 import android.os.Bundle
 import android.view.Gravity
 import android.view.View
@@ -52,9 +50,8 @@ import eu.kanade.tachiyomi.ui.recent.history.HistoryController
 import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
 import eu.kanade.tachiyomi.util.lang.launchIO
 import eu.kanade.tachiyomi.util.lang.launchUI
-import eu.kanade.tachiyomi.util.system.InternalResourceHelper
-import eu.kanade.tachiyomi.util.system.getResourceColor
 import eu.kanade.tachiyomi.util.system.toast
+import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.launchIn
@@ -119,14 +116,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
         // Make sure navigation bar is on bottom before we modify it
         ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
             if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
-                window.navigationBarColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
-                    !InternalResourceHelper.getBoolean(this, "config_navBarNeedsScrim", true)
-                ) {
-                    Color.TRANSPARENT
-                } else {
-                    // Set navbar scrim 70% of navigationBarColor
-                    getResourceColor(android.R.attr.navigationBarColor, 0.7F)
-                }
+                window.setNavigationBarTransparentCompat(this)
             }
             insets
         }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSheet.kt

@@ -53,7 +53,7 @@ class TrackSheet(
     override fun show() {
         super.show()
         controller.presenter.refreshTrackers()
-        sheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
+        behavior.state = BottomSheetBehavior.STATE_COLLAPSED
     }
 
     fun onNextTrackers(trackers: List<TrackItem>) {

+ 22 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt

@@ -1,5 +1,6 @@
 package eu.kanade.tachiyomi.ui.reader.setting
 
+import android.animation.ValueAnimator
 import android.os.Bundle
 import com.google.android.material.tabs.TabLayout
 import eu.kanade.tachiyomi.R
@@ -16,13 +17,21 @@ class ReaderSettingsSheet(
     private val generalSettings = ReaderGeneralSettings(activity)
     private val colorFilterSettings = ReaderColorFilterSettings(activity)
 
+    private val backgroundDimAnimator by lazy {
+        val sheetBackgroundDim = window?.attributes?.dimAmount ?: 0.25f
+        ValueAnimator.ofFloat(sheetBackgroundDim, 0f).also { valueAnimator ->
+            valueAnimator.duration = 250
+            valueAnimator.addUpdateListener {
+                window?.setDimAmount(it.animatedValue as Float)
+            }
+        }
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        sheetBehavior.isFitToContents = false
-        sheetBehavior.halfExpandedRatio = 0.25f
-
-        val sheetBackgroundDim = window?.attributes?.dimAmount ?: 0.25f
+        behavior.isFitToContents = false
+        behavior.halfExpandedRatio = 0.25f
 
         val filterTabIndex = getTabViews().indexOf(colorFilterSettings)
         binding.tabs.addOnTabSelectedListener(object : SimpleTabSelectedListener() {
@@ -30,7 +39,15 @@ class ReaderSettingsSheet(
                 val isFilterTab = tab?.position == filterTabIndex
 
                 // Remove dimmed backdrop so color filter changes can be previewed
-                window?.setDimAmount(if (isFilterTab) 0f else sheetBackgroundDim)
+                backgroundDimAnimator.run {
+                    if (isFilterTab) {
+                        if (animatedFraction < 1f) {
+                            start()
+                        }
+                    } else if (animatedFraction > 0f) {
+                        reverse()
+                    }
+                }
 
                 // Hide toolbars
                 if (activity.menuVisible != !isFilterTab) {

+ 11 - 0
app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt

@@ -15,8 +15,11 @@ import android.content.res.Resources
 import android.graphics.Color
 import android.net.ConnectivityManager
 import android.net.Uri
+import android.os.Build
 import android.os.PowerManager
+import android.view.Display
 import android.view.View
+import android.view.WindowManager
 import android.widget.Toast
 import androidx.annotation.AttrRes
 import androidx.annotation.ColorInt
@@ -172,6 +175,14 @@ val Context.powerManager: PowerManager
 val Context.keyguardManager: KeyguardManager
     get() = getSystemService()!!
 
+val Context.displayCompat: Display?
+    get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+        display
+    } else {
+        @Suppress("DEPRECATION")
+        getSystemService<WindowManager>()?.defaultDisplay
+    }
+
 /**
  * Convenience method to acquire a partial wake lock.
  */

+ 20 - 0
app/src/main/java/eu/kanade/tachiyomi/util/view/WindowExtensions.kt

@@ -1,7 +1,12 @@
 package eu.kanade.tachiyomi.util.view
 
+import android.content.Context
+import android.graphics.Color
+import android.os.Build
 import android.view.View
 import android.view.Window
+import eu.kanade.tachiyomi.util.system.InternalResourceHelper
+import eu.kanade.tachiyomi.util.system.getResourceColor
 
 fun Window.showBar() {
     decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
@@ -22,3 +27,18 @@ fun Window.defaultBar() {
 }
 
 fun Window.isDefaultBar() = decorView.systemUiVisibility == View.SYSTEM_UI_FLAG_VISIBLE
+
+/**
+ * Sets navigation bar color to transparent if system's config_navBarNeedsScrim is false,
+ * otherwise it will use the theme navigationBarColor with 70% opacity.
+ */
+fun Window.setNavigationBarTransparentCompat(context: Context) {
+    navigationBarColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
+        !InternalResourceHelper.getBoolean(context, "config_navBarNeedsScrim", true)
+    ) {
+        Color.TRANSPARENT
+    } else {
+        // Set navbar scrim 70% of navigationBarColor
+        context.getResourceColor(android.R.attr.navigationBarColor, 0.7F)
+    }
+}

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt

@@ -8,6 +8,7 @@ import android.view.ViewGroup
 import android.widget.CheckBox
 import android.widget.CheckedTextView
 import android.widget.EditText
+import android.widget.FrameLayout
 import android.widget.RadioButton
 import android.widget.Spinner
 import android.widget.TextView
@@ -16,7 +17,6 @@ import androidx.core.view.ViewCompat
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.google.android.material.R
-import com.google.android.material.internal.ScrimInsetsFrameLayout
 import com.google.android.material.textfield.TextInputLayout
 import eu.kanade.tachiyomi.util.view.inflate
 import eu.kanade.tachiyomi.R as TR
@@ -27,7 +27,7 @@ open class SimpleNavigationView @JvmOverloads constructor(
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0
-) : ScrimInsetsFrameLayout(context, attrs, defStyleAttr) {
+) : FrameLayout(context, attrs, defStyleAttr) {
 
     /**
      * Recycler view containing all the items.

+ 39 - 6
app/src/main/java/eu/kanade/tachiyomi/widget/sheet/BaseBottomSheetDialog.kt

@@ -1,18 +1,24 @@
 package eu.kanade.tachiyomi.widget.sheet
 
 import android.content.Context
+import android.content.res.Configuration
+import android.os.Build
 import android.os.Bundle
+import android.util.DisplayMetrics
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import com.google.android.material.bottomsheet.BottomSheetBehavior
 import com.google.android.material.bottomsheet.BottomSheetDialog
 import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.preference.PreferenceValues
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.util.system.displayCompat
+import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
 
 abstract class BaseBottomSheetDialog(context: Context) : BottomSheetDialog(context) {
 
-    internal lateinit var sheetBehavior: BottomSheetBehavior<*>
-
     abstract fun createView(inflater: LayoutInflater): View
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -21,12 +27,39 @@ abstract class BaseBottomSheetDialog(context: Context) : BottomSheetDialog(conte
         val rootView = createView(layoutInflater)
         setContentView(rootView)
 
-        sheetBehavior = BottomSheetBehavior.from(rootView.parent as ViewGroup)
-
         // Enforce max width for tablets
         val width = context.resources.getDimensionPixelSize(R.dimen.bottom_sheet_width)
         if (width > 0) {
-            sheetBehavior.maxWidth = width
+            behavior.maxWidth = width
+        }
+
+        // Set peek height to 50% display height
+        context.displayCompat?.let {
+            val metrics = DisplayMetrics()
+            it.getRealMetrics(metrics)
+            behavior.peekHeight = metrics.heightPixels / 2
+        }
+
+        // Set navbar color to transparent for edge-to-edge bottom sheet if we can use light navigation bar
+        // TODO Replace deprecated systemUiVisibility when material-components uses new API to modify status bar icons
+        @Suppress("DEPRECATION")
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            window?.setNavigationBarTransparentCompat(context)
+            val isDarkMode = when (Injekt.get<PreferencesHelper>().themeMode().get()) {
+                PreferenceValues.ThemeMode.light -> false
+                PreferenceValues.ThemeMode.dark -> true
+                PreferenceValues.ThemeMode.system ->
+                    context.resources.configuration.uiMode and
+                        Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+            }
+            val bottomSheet = rootView.parent as ViewGroup
+            var flags = bottomSheet.systemUiVisibility
+            flags = if (isDarkMode) {
+                flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
+            } else {
+                flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+            }
+            bottomSheet.systemUiVisibility = flags
         }
     }
 }

+ 10 - 0
app/src/main/res/anim/bottom_sheet_slide_in.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="300"
+    android:interpolator="@android:interpolator/fast_out_slow_in">
+
+    <translate
+        android:fromYDelta="100%p"
+        android:toYDelta="0" />
+
+</set>

+ 10 - 0
app/src/main/res/anim/bottom_sheet_slide_out.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="300"
+    android:interpolator="@android:interpolator/fast_out_slow_in">
+
+    <translate
+        android:fromYDelta="0"
+        android:toYDelta="100%p" />
+
+</set>

+ 7 - 1
app/src/main/res/values/styles.xml

@@ -100,8 +100,9 @@
     <style name="Theme.BottomSheet" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
         <item name="android:windowIsFloating">false</item>
         <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:navigationBarColor">?attr/colorPrimary</item>
+        <item name="android:navigationBarColor">?attr/colorSurface</item>
         <item name="bottomSheetStyle">@style/Theme.BottomSheet.Style</item>
+        <item name="android:windowAnimationStyle">@style/Animation.BottomSheetDialog</item>
     </style>
 
     <style name="Theme.BottomSheet.Style" parent="Widget.MaterialComponents.BottomSheet">
@@ -116,6 +117,11 @@
         <item name="cornerSizeBottomLeft">0dp</item>
     </style>
 
+    <style name="Animation.BottomSheetDialog" parent="Animation.AppCompat.Dialog">
+        <item name="android:windowEnterAnimation">@anim/bottom_sheet_slide_in</item>
+        <item name="android:windowExitAnimation">@anim/bottom_sheet_slide_out</item>
+    </style>
+
 
     <!--===============-->
     <!--Text Appearance-->