From 0df891b3364d23c6d79739e6546a288407afbfa0 Mon Sep 17 00:00:00 2001 From: RikkaW Date: Fri, 23 Oct 2020 12:58:39 +0800 Subject: [PATCH] Handle window insets with a new way For example, switching pages in home should only have scale and alpha animations, but a "translate y" animation shows. This is because Data Binding is triggered later (like "in the next frame"), causing the animation runs before view attribute changes. This commit introduces WindowInsetsHelper class and use it to handle all window insets. With the help of LayoutInflaterFactory from the previous commit, we can control insets behavior by adding our attributes to the XML and anything is done by WindowInsetsHelper class. As changes are highly coupling, this commit also contains new ItemDecoration for lists, replacing the random combination of padding and empty drawable. And "fixEdgeEffect" extension for RecyclerView, making edge effects respect padding. --- .../topjohnwu/magisk/arch/BaseUIActivity.kt | 2 - .../topjohnwu/magisk/arch/BaseUIComponent.kt | 44 +-- .../topjohnwu/magisk/arch/BaseUIFragment.kt | 3 - .../com/topjohnwu/magisk/ktx/RecyclerView.kt | 178 +++++++++++ .../com/topjohnwu/magisk/ui/MainActivity.kt | 15 - .../topjohnwu/magisk/ui/hide/HideFragment.kt | 18 ++ .../ui/inflater/LayoutInflaterFactory.kt | 5 + .../magisk/ui/inflater/WindowInsetsHelper.kt | 284 ++++++++++++++++++ .../topjohnwu/magisk/ui/log/LogFragment.kt | 18 ++ .../magisk/ui/module/ModuleFragment.kt | 34 +++ .../magisk/ui/settings/SettingsFragment.kt | 18 ++ .../magisk/ui/superuser/SuperuserFragment.kt | 24 ++ app/src/main/res/layout/activity_main_md2.xml | 14 +- .../main/res/layout/fragment_flash_md2.xml | 10 +- app/src/main/res/layout/fragment_hide_md2.xml | 10 +- app/src/main/res/layout/fragment_home_md2.xml | 5 +- .../main/res/layout/fragment_install_md2.xml | 7 +- app/src/main/res/layout/fragment_log_md2.xml | 3 +- .../main/res/layout/fragment_module_md2.xml | 11 +- .../res/layout/fragment_safetynet_md2.xml | 4 +- .../main/res/layout/fragment_settings_md2.xml | 8 +- .../res/layout/fragment_superuser_md2.xml | 7 +- .../main/res/layout/fragment_theme_md2.xml | 9 +- .../main/res/layout/include_hide_filter.xml | 3 +- .../main/res/layout/include_log_magisk.xml | 5 +- .../main/res/layout/include_log_superuser.xml | 7 +- .../main/res/layout/include_module_filter.xml | 9 +- app/src/main/res/values/attrs.xml | 28 ++ app/src/main/res/values/ids.xml | 5 +- 29 files changed, 667 insertions(+), 121 deletions(-) create mode 100644 app/src/main/java/com/topjohnwu/magisk/ktx/RecyclerView.kt create mode 100644 app/src/main/java/com/topjohnwu/magisk/ui/inflater/WindowInsetsHelper.kt diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIActivity.kt b/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIActivity.kt index 13eab5e51..f7a84a279 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIActivity.kt @@ -97,8 +97,6 @@ abstract class BaseUIActivity : it.setVariable(BR.viewModel, viewModel) it.lifecycleOwner = this } - - ensureInsets() } fun setAccessibilityDelegate(delegate: View.AccessibilityDelegate?) { diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIComponent.kt b/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIComponent.kt index 90f2ebeb3..741ddaa1a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIComponent.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIComponent.kt @@ -1,12 +1,9 @@ package com.topjohnwu.magisk.arch import android.view.View -import androidx.core.graphics.Insets -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.LifecycleOwner -interface BaseUIComponent: LifecycleOwner { +interface BaseUIComponent : LifecycleOwner { val viewRoot: View val viewModel: VM @@ -17,47 +14,8 @@ interface BaseUIComponent: LifecycleOwner { } } - fun consumeSystemWindowInsets(insets: Insets): Insets? = null - /** * Called for all [ViewEvent]s published by associated viewModel. */ fun onEventDispatched(event: ViewEvent) {} - - fun ensureInsets() { - ViewCompat.setOnApplyWindowInsetsListener(viewRoot) { _, insets -> - insets.asInsets() - .also { viewModel.insets = it } - .let { consumeSystemWindowInsets(it) } - ?.subtractBy(insets) ?: insets - } - if (ViewCompat.isAttachedToWindow(viewRoot)) { - ViewCompat.requestApplyInsets(viewRoot) - } else { - viewRoot.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { - override fun onViewDetachedFromWindow(v: View) = Unit - override fun onViewAttachedToWindow(v: View) { - ViewCompat.requestApplyInsets(v) - } - }) - } - } - - private fun WindowInsetsCompat.asInsets() = Insets.of( - systemWindowInsetLeft, - systemWindowInsetTop, - systemWindowInsetRight, - systemWindowInsetBottom - ) - - private fun Insets.subtractBy(insets: WindowInsetsCompat) = - WindowInsetsCompat.Builder(insets).setSystemWindowInsets( - Insets.of( - insets.systemWindowInsetLeft - left, - insets.systemWindowInsetTop - top, - insets.systemWindowInsetRight - right, - insets.systemWindowInsetBottom - bottom - ) - ).build() - } diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIFragment.kt b/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIFragment.kt index 3ffe64431..318ab143f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/BaseUIFragment.kt @@ -24,8 +24,6 @@ abstract class BaseUIFragment : override val viewRoot: View get() = binding.root private val navigation get() = activity.navigation - override fun consumeSystemWindowInsets(insets: Insets) = insets - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) startObserveEvents() @@ -65,7 +63,6 @@ abstract class BaseUIFragment : return true } }) - ensureInsets() } override fun onResume() { diff --git a/app/src/main/java/com/topjohnwu/magisk/ktx/RecyclerView.kt b/app/src/main/java/com/topjohnwu/magisk/ktx/RecyclerView.kt new file mode 100644 index 000000000..c38632d48 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/ktx/RecyclerView.kt @@ -0,0 +1,178 @@ +@file:Suppress("unused") + +package com.topjohnwu.magisk.ktx + +import android.graphics.Canvas +import android.graphics.Rect +import android.view.View +import android.widget.EdgeEffect +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.topjohnwu.magisk.R + +fun RecyclerView.addVerticalPadding(paddingTop: Int = 0, paddingBottom: Int = 0) { + addItemDecoration(VerticalPaddingDecoration(paddingTop, paddingBottom)) +} + +private class VerticalPaddingDecoration(private val paddingTop: Int = 0, private val paddingBottom: Int = 0) : RecyclerView.ItemDecoration() { + + private var allowTop: Boolean = true + private var allowBottom: Boolean = true + + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + if (parent.adapter == null) { + return + } + val position = parent.getChildAdapterPosition(view) + val count = parent.adapter!!.itemCount + if (position == 0 && allowTop) { + outRect.top = paddingTop + } else if (position == count - 1 && allowBottom) { + outRect.bottom = paddingBottom + } + } +} + +fun RecyclerView.addSimpleItemDecoration( + left: Int = 0, + top: Int = 0, + right: Int = 0, + bottom: Int = 0, +) { + addItemDecoration(SimpleItemDecoration(left, top, right, bottom)) +} + +private class SimpleItemDecoration( + private val left: Int = 0, + private val top: Int = 0, + private val right: Int = 0, + private val bottom: Int = 0 +) : RecyclerView.ItemDecoration() { + + private var allowLeft: Boolean = true + private var allowTop: Boolean = true + private var allowRight: Boolean = true + private var allowBottom: Boolean = true + + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + if (parent.adapter == null) { + return + } + if (allowLeft) { + outRect.left = left + } + if (allowTop) { + outRect.top = top + } + if (allowRight) { + outRect.right = right + } + if (allowBottom) { + outRect.top = bottom + } + } +} + +fun RecyclerView.fixEdgeEffect(overScrollIfContentScrolls: Boolean = true, alwaysClipToPadding: Boolean = true) { + if (overScrollIfContentScrolls) { + val listener = OverScrollIfContentScrollsListener() + addOnLayoutChangeListener(listener) + setTag(R.id.tag_rikka_recyclerView_OverScrollIfContentScrollsListener, listener) + } else { + val listener = getTag(R.id.tag_rikka_recyclerView_OverScrollIfContentScrollsListener) as? OverScrollIfContentScrollsListener + if (listener != null) { + removeOnLayoutChangeListener(listener) + setTag(R.id.tag_rikka_recyclerView_OverScrollIfContentScrollsListener, null) + } + } + + edgeEffectFactory = if (alwaysClipToPadding && !clipToPadding) { + AlwaysClipToPaddingEdgeEffectFactory() + } else { + RecyclerView.EdgeEffectFactory() + } +} + +private class OverScrollIfContentScrollsListener : View.OnLayoutChangeListener { + private var show = true + override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) { + if (shouldDrawOverScroll(v as RecyclerView) != show) { + show = !show + if (show) { + v.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS) + } else { + v.setOverScrollMode(View.OVER_SCROLL_NEVER) + } + } + } + + fun shouldDrawOverScroll(recyclerView: RecyclerView): Boolean { + if (recyclerView.layoutManager == null || recyclerView.adapter == null || recyclerView.adapter!!.itemCount == 0) { + return false + } + if (recyclerView.layoutManager is LinearLayoutManager) { + val itemCount = recyclerView.layoutManager!!.itemCount + val firstPosition: Int = (recyclerView.layoutManager as LinearLayoutManager?)!!.findFirstCompletelyVisibleItemPosition() + val lastPosition: Int = (recyclerView.layoutManager as LinearLayoutManager?)!!.findLastCompletelyVisibleItemPosition() + return firstPosition != 0 || lastPosition != itemCount - 1 + } + return true + } +} + +private class AlwaysClipToPaddingEdgeEffectFactory : RecyclerView.EdgeEffectFactory() { + + override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect { + + return object : EdgeEffect(view.context) { + private var ensureSize = false + + private fun ensureSize() { + if (ensureSize) return + ensureSize = true + + when (direction) { + DIRECTION_LEFT -> { + setSize(view.measuredHeight - view.paddingTop - view.paddingBottom, + view.measuredWidth - view.paddingLeft - view.paddingRight) + } + DIRECTION_TOP -> { + setSize(view.measuredWidth - view.paddingLeft - view.paddingRight, + view.measuredHeight - view.paddingTop - view.paddingBottom) + } + DIRECTION_RIGHT -> { + setSize(view.measuredHeight - view.paddingTop - view.paddingBottom, + view.measuredWidth - view.paddingLeft - view.paddingRight) + } + DIRECTION_BOTTOM -> { + setSize(view.measuredWidth - view.paddingLeft - view.paddingRight, + view.measuredHeight - view.paddingTop - view.paddingBottom) + } + } + } + + override fun draw(c: Canvas): Boolean { + ensureSize() + + val restore = c.save() + when (direction) { + DIRECTION_LEFT -> { + c.translate(view.paddingBottom.toFloat(), 0f) + } + DIRECTION_TOP -> { + c.translate(view.paddingLeft.toFloat(), view.paddingTop.toFloat()) + } + DIRECTION_RIGHT -> { + c.translate(-view.paddingTop.toFloat(), 0f) + } + DIRECTION_BOTTOM -> { + c.translate(view.paddingRight.toFloat(), view.paddingBottom.toFloat()) + } + } + val res = super.draw(c) + c.restoreToCount(restore) + return res + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt index 0457834a7..f499ae854 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt @@ -39,14 +39,6 @@ open class MainActivity : BaseUIActivity( override val viewModel by viewModel() override val navHost: Int = R.id.main_nav_host - //This temporarily fixes unwanted feature of BottomNavigationView - where the view applies - //padding on itself given insets are not consumed beforehand. Unfortunately the listener - //implementation doesn't favor us against the design library, so on re-create it's often given - //upper hand. - private val navObserver = ViewTreeObserver.OnGlobalLayoutListener { - binding.mainNavigation.setPadding(0) - } - private var isRootFragment = true override fun onCreate(savedInstanceState: Bundle?) { @@ -100,8 +92,6 @@ open class MainActivity : BaseUIActivity( (currentFragment as? ReselectionTarget)?.onReselected() } - binding.mainNavigation.viewTreeObserver.addOnGlobalLayoutListener(navObserver) - val section = if (intent.action == ACTION_APPLICATION_PREFERENCES) Const.Nav.SETTINGS else intent.getStringExtra(Const.Key.OPEN_SECTION) getScreen(section)?.navigate() @@ -121,11 +111,6 @@ open class MainActivity : BaseUIActivity( } } - override fun onDestroy() { - binding.mainNavigation.viewTreeObserver.removeOnGlobalLayoutListener(navObserver) - super.onDestroy() - } - override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { android.R.id.home -> onBackPressed() diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideFragment.kt index 119bd803f..de7649844 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideFragment.kt @@ -12,6 +12,9 @@ import androidx.recyclerview.widget.RecyclerView import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.databinding.FragmentHideMd2Binding +import com.topjohnwu.magisk.ktx.addSimpleItemDecoration +import com.topjohnwu.magisk.ktx.addVerticalPadding +import com.topjohnwu.magisk.ktx.fixEdgeEffect import com.topjohnwu.magisk.ktx.hideKeyboard import com.topjohnwu.magisk.utils.MotionRevealHelper import org.koin.androidx.viewmodel.ext.android.viewModel @@ -49,6 +52,21 @@ class HideFragment : BaseUIFragment() { } }) + val resource = requireContext().resources + val l_50 = resource.getDimensionPixelSize(R.dimen.l_50) + val l1 = resource.getDimensionPixelSize(R.dimen.l1) + binding.hideContent.addVerticalPadding( + l_50, + l1 + resource.getDimensionPixelSize(R.dimen.internal_action_bar_size) + ) + binding.hideContent.addSimpleItemDecoration( + left = l1, + top = l_50, + right = l1, + bottom = l_50, + ) + binding.hideContent.fixEdgeEffect() + val lama = binding.hideContent.layoutManager ?: return lama.isAutoMeasureEnabled = false } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/inflater/LayoutInflaterFactory.kt b/app/src/main/java/com/topjohnwu/magisk/ui/inflater/LayoutInflaterFactory.kt index 686085dd6..6585b2ae3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/inflater/LayoutInflaterFactory.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/inflater/LayoutInflaterFactory.kt @@ -1,6 +1,7 @@ package com.topjohnwu.magisk.ui.inflater import android.content.Context +import android.os.Build import android.util.AttributeSet import android.view.InflateException import android.view.LayoutInflater @@ -24,6 +25,10 @@ open class LayoutInflaterFactory(private val delegate: AppCompatDelegate) : Layo open fun onViewCreated(view: View?, parent: View?, name: String, context: Context, attrs: AttributeSet) { if (view == null) return + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + WindowInsetsHelper.attach(view, attrs) + } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/inflater/WindowInsetsHelper.kt b/app/src/main/java/com/topjohnwu/magisk/ui/inflater/WindowInsetsHelper.kt new file mode 100644 index 000000000..286be439e --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/ui/inflater/WindowInsetsHelper.kt @@ -0,0 +1,284 @@ +@file:Suppress("unused") + +package com.topjohnwu.magisk.ui.inflater + +import android.annotation.SuppressLint +import android.annotation.TargetApi +import android.graphics.Rect +import android.os.Build +import android.util.AttributeSet +import android.view.Gravity.* +import android.view.View +import android.view.ViewGroup +import androidx.core.graphics.Insets +import androidx.core.view.OnApplyWindowInsetsListener +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.topjohnwu.magisk.R + +private typealias ApplyInsetsCallback = (insets: Insets, left: Boolean, top: Boolean, right: Boolean, bottom: Boolean) -> T + +private class ApplyInsets(private val out: Rect) : ApplyInsetsCallback { + + override fun invoke(insets: Insets, left: Boolean, top: Boolean, right: Boolean, bottom: Boolean) { + out.left += if (left) insets.left else 0 + out.top += if (top) insets.top else 0 + out.right += if (right) insets.right else 0 + out.bottom += if (bottom) insets.bottom else 0 + } +} + +private class ConsumeInsets : ApplyInsetsCallback { + + override fun invoke(insets: Insets, left: Boolean, top: Boolean, right: Boolean, bottom: Boolean): Insets { + val insetsLeft = if (left) 0 else insets.left + val insetsTop = if (top) 0 else insets.top + val insetsRight = if (right) 0 else insets.right + val insetsBottom = if (bottom) 0 else insets.bottom + return Insets.of(insetsLeft, insetsTop, insetsRight, insetsBottom) + } +} + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +open class WindowInsetsHelper private constructor( + private val view: View, + private val fitSystemWindows: Int, + private val layout_fitsSystemWindowsInsets: Int, + private val consumeSystemWindows: Int) : OnApplyWindowInsetsListener { + + internal var initialPaddingLeft: Int = view.paddingLeft + internal var initialPaddingTop: Int = view.paddingTop + internal var initialPaddingRight: Int = view.paddingRight + internal var initialPaddingBottom: Int = view.paddingBottom + + private var initialMargin = false + internal var initialMarginLeft: Int = 0 + internal var initialMarginTop: Int = 0 + internal var initialMarginRight: Int = 0 + internal var initialMarginBottom: Int = 0 + internal var initialMarginStart: Int = 0 + internal var initialMarginEnd: Int = 0 + + private var lastInsets: WindowInsetsCompat? = null + + open fun setInitialPadding(left: Int, top: Int, right: Int, bottom: Int) { + initialPaddingLeft = left + initialPaddingTop = top + initialPaddingRight = right + initialPaddingBottom = bottom + + lastInsets?.let { applyWindowInsets(it) } + } + + open fun setInitialPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) { + val isRTL = view.layoutDirection == View.LAYOUT_DIRECTION_RTL + if (isRTL) { + setInitialPadding(start, top, end, bottom) + } else { + setInitialPadding(start, top, end, bottom) + } + } + + open fun setInitialMargin(left: Int, top: Int, right: Int, bottom: Int) { + initialPaddingLeft = left + initialPaddingTop = top + initialPaddingRight = right + initialPaddingBottom = bottom + + lastInsets?.let { applyWindowInsets(it) } + } + + open fun setInitialMarginRelative(start: Int, top: Int, end: Int, bottom: Int) { + initialMarginStart = start + initialMarginTop = top + initialMarginEnd = end + initialMarginBottom = bottom + + lastInsets?.let { applyWindowInsets(it) } + } + + @SuppressLint("RtlHardcoded") + private fun applyInsets(insets: Insets, fit: Int, callback: ApplyInsetsCallback): T { + val relativeMode = (fit and RELATIVE_LAYOUT_DIRECTION) == RELATIVE_LAYOUT_DIRECTION + + val isRTL = view.layoutDirection == View.LAYOUT_DIRECTION_RTL + + val left: Boolean + val top = fit and TOP == TOP + val right: Boolean + val bottom = fit and BOTTOM == BOTTOM + + if (relativeMode) { + val start = fit and START == START + val end = fit and END == END + left = (!isRTL && start) || (isRTL && end) + right = (!isRTL && end) || (isRTL && start) + } else { + left = fit and LEFT == LEFT + right = fit and RIGHT == RIGHT + } + + return callback.invoke(insets, left, top, right, bottom) + } + + private fun applyWindowInsets(windowInsets: WindowInsetsCompat): WindowInsetsCompat { + if (fitSystemWindows != 0) { + val padding = Rect(initialPaddingLeft, initialPaddingTop, initialPaddingRight, initialPaddingBottom) + applyInsets(windowInsets.systemWindowInsets, fitSystemWindows, ApplyInsets(padding)) + view.setPadding(padding.left, padding.top, padding.right, padding.bottom) + } + + if (layout_fitsSystemWindowsInsets != 0) { + if (!initialMargin) { + initialMarginLeft = (view.layoutParams as? ViewGroup.MarginLayoutParams)?.leftMargin ?: 0 + initialMarginTop = (view.layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin ?: 0 + initialMarginRight = (view.layoutParams as? ViewGroup.MarginLayoutParams)?.rightMargin ?: 0 + initialMarginBottom = (view.layoutParams as? ViewGroup.MarginLayoutParams)?.bottomMargin ?: 0 + initialMarginStart = (view.layoutParams as? ViewGroup.MarginLayoutParams)?.marginStart ?: 0 + initialMarginEnd = (view.layoutParams as? ViewGroup.MarginLayoutParams)?.marginEnd ?: 0 + initialMargin = true + } + + val margin = if ((layout_fitsSystemWindowsInsets and RELATIVE_LAYOUT_DIRECTION) == RELATIVE_LAYOUT_DIRECTION) + Rect(initialMarginLeft, initialMarginTop, initialMarginRight, initialMarginBottom) + else + Rect(initialMarginStart, initialMarginTop, initialMarginEnd, initialMarginBottom) + + applyInsets(windowInsets.systemWindowInsets, layout_fitsSystemWindowsInsets, ApplyInsets(margin)) + + val lp = view.layoutParams + if (lp is ViewGroup.MarginLayoutParams) { + lp.topMargin = margin.top + lp.bottomMargin = margin.bottom + + if ((layout_fitsSystemWindowsInsets and RELATIVE_LAYOUT_DIRECTION) == RELATIVE_LAYOUT_DIRECTION) { + lp.marginStart = margin.left + lp.marginEnd = margin.right + } else { + lp.leftMargin = margin.left + lp.rightMargin = margin.right + } + + view.layoutParams = lp + } + } + + val systemWindowInsets = if (consumeSystemWindows != 0) applyInsets(windowInsets.systemWindowInsets, consumeSystemWindows, ConsumeInsets()) else windowInsets.systemWindowInsets + + return WindowInsetsCompat.Builder(windowInsets) + .setSystemWindowInsets(systemWindowInsets) + .build() + } + + override fun onApplyWindowInsets(view: View, insets: WindowInsetsCompat): WindowInsetsCompat { + if (lastInsets == insets) { + return insets + } + + lastInsets = insets + + return applyWindowInsets(insets) + } + + companion object { + + @JvmStatic + fun attach(view: View, attrs: AttributeSet) { + val a = view.context.obtainStyledAttributes(attrs, R.styleable.WindowInsetsHelper, 0, 0) + val edgeToEdge = a.getBoolean(R.styleable.WindowInsetsHelper_edgeToEdge, false) + val fitsSystemWindowsInsets = a.getInt(R.styleable.WindowInsetsHelper_fitsSystemWindowsInsets, 0) + val layout_fitsSystemWindowsInsets = a.getInt(R.styleable.WindowInsetsHelper_layout_fitsSystemWindowsInsets, 0) + val consumeSystemWindowsInsets = a.getInt(R.styleable.WindowInsetsHelper_consumeSystemWindowsInsets, 0) + a.recycle() + + attach(view, edgeToEdge, fitsSystemWindowsInsets, layout_fitsSystemWindowsInsets, consumeSystemWindowsInsets) + } + + @JvmStatic + fun attach(view: View, edgeToEdge: Boolean, fitsSystemWindowsInsets: Int, layout_fitsSystemWindowsInsets: Int, consumeSystemWindowsInsets: Int) { + if (edgeToEdge) { + view.systemUiVisibility = (view.systemUiVisibility + or View.SYSTEM_UI_FLAG_LAYOUT_STABLE + or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) + } + + if (fitsSystemWindowsInsets == 0 && layout_fitsSystemWindowsInsets == 0 && consumeSystemWindowsInsets == 0) { + return + } + + val listener = WindowInsetsHelper(view, fitsSystemWindowsInsets, layout_fitsSystemWindowsInsets, consumeSystemWindowsInsets) + ViewCompat.setOnApplyWindowInsetsListener(view, listener) + view.setTag(R.id.tag_rikka_material_WindowInsetsHelper, listener) + + if (!view.isAttachedToWindow) { + view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + v.removeOnAttachStateChangeListener(this) + v.requestApplyInsets() + } + + override fun onViewDetachedFromWindow(v: View) = Unit + }) + } + } + } +} + +val View.windowInsetsHelper: WindowInsetsHelper? + get() { + val value = getTag(R.id.tag_rikka_material_WindowInsetsHelper) + return if (value is WindowInsetsHelper) value else null + } + +val View.initialPaddingLeft: Int + get() = windowInsetsHelper?.initialPaddingLeft ?: 0 + +val View.initialPaddingTop: Int + get() = windowInsetsHelper?.initialPaddingTop ?: 0 + +val View.initialPaddingRight: Int + get() = windowInsetsHelper?.initialPaddingRight ?: 0 + +val View.initialPaddingBottom: Int + get() = windowInsetsHelper?.initialPaddingBottom ?: 0 + +val View.initialPaddingStart: Int + get() = if (layoutDirection == View.LAYOUT_DIRECTION_RTL) initialPaddingRight else initialPaddingLeft + +val View.initialPaddingEnd: Int + get() = if (layoutDirection == View.LAYOUT_DIRECTION_RTL) initialPaddingLeft else initialPaddingRight + +fun View.setInitialPadding(left: Int, top: Int, right: Int, bottom: Int) { + windowInsetsHelper?.setInitialPadding(left, top, right, bottom) +} + +fun View.setInitialPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) { + windowInsetsHelper?.setInitialPaddingRelative(start, top, end, bottom) +} + +val View.initialMarginLeft: Int + get() = windowInsetsHelper?.initialMarginLeft ?: 0 + +val View.initialMarginTop: Int + get() = windowInsetsHelper?.initialMarginTop ?: 0 + +val View.initialMarginRight: Int + get() = windowInsetsHelper?.initialMarginRight ?: 0 + +val View.initialMarginBottom: Int + get() = windowInsetsHelper?.initialMarginBottom ?: 0 + +val View.initialMarginStart: Int + get() = windowInsetsHelper?.initialMarginStart ?: 0 + +val View.initialMarginEnd: Int + get() = windowInsetsHelper?.initialMarginEnd ?: 0 + +fun View.setInitialMargin(left: Int, top: Int, right: Int, bottom: Int) { + windowInsetsHelper?.setInitialMargin(left, top, right, bottom) +} + +fun View.setInitialMarginRelative(start: Int, top: Int, end: Int, bottom: Int) { + windowInsetsHelper?.setInitialMarginRelative(start, top, end, bottom) +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt index 0197c7944..a7c20ed7b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt @@ -9,6 +9,9 @@ import androidx.core.view.isVisible import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding +import com.topjohnwu.magisk.ktx.addSimpleItemDecoration +import com.topjohnwu.magisk.ktx.addVerticalPadding +import com.topjohnwu.magisk.ktx.fixEdgeEffect import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.utils.MotionRevealHelper import org.koin.androidx.viewmodel.ext.android.viewModel @@ -42,6 +45,21 @@ class LogFragment : BaseUIFragment() { binding.logFilterToggle.setOnClickListener { isMagiskLogVisible = true } + + val resource = requireContext().resources + val l_50 = resource.getDimensionPixelSize(R.dimen.l_50) + val l1 = resource.getDimensionPixelSize(R.dimen.l1) + binding.logFilterSuperuser.logSuperuser.addVerticalPadding( + 0, + l1 + ) + binding.logFilterSuperuser.logSuperuser.addSimpleItemDecoration( + left = l1, + top = l_50, + right = l1, + bottom = l_50, + ) + binding.logFilterSuperuser.logSuperuser.fixEdgeEffect() } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt index d733fdaae..27d804504 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt @@ -13,6 +13,9 @@ import com.topjohnwu.magisk.arch.ReselectionTarget import com.topjohnwu.magisk.arch.ViewEvent import com.topjohnwu.magisk.core.download.BaseDownloader import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding +import com.topjohnwu.magisk.ktx.addSimpleItemDecoration +import com.topjohnwu.magisk.ktx.addVerticalPadding +import com.topjohnwu.magisk.ktx.fixEdgeEffect import com.topjohnwu.magisk.ktx.hideKeyboard import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener @@ -62,6 +65,37 @@ class ModuleFragment : BaseUIFragment if (newState != RecyclerView.SCROLL_STATE_IDLE) hideKeyboard() } }) + + val resource = requireContext().resources + val l_50 = resource.getDimensionPixelSize(R.dimen.l_50) + val l1 = resource.getDimensionPixelSize(R.dimen.l1) + binding.moduleList.apply { + addVerticalPadding( + l_50, + l1 + resource.getDimensionPixelSize(R.dimen.internal_action_bar_size) + ) + addSimpleItemDecoration( + left = l1, + top = l_50, + right = l1, + bottom = l_50, + ) + fixEdgeEffect() + } + + binding.moduleFilterInclude.moduleFilterList.apply { + addVerticalPadding( + l_50, + l1 + resource.getDimensionPixelSize(R.dimen.internal_action_bar_size) + ) + addSimpleItemDecoration( + left = l1, + top = l_50, + right = l1, + bottom = l_50, + ) + fixEdgeEffect() + } } override fun onDestroyView() { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt index 707d55d80..7fc03db2b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt @@ -5,6 +5,9 @@ import android.view.View import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.databinding.FragmentSettingsMd2Binding +import com.topjohnwu.magisk.ktx.addSimpleItemDecoration +import com.topjohnwu.magisk.ktx.addVerticalPadding +import com.topjohnwu.magisk.ktx.fixEdgeEffect import com.topjohnwu.magisk.ktx.setOnViewReadyListener import org.koin.androidx.viewmodel.ext.android.viewModel @@ -24,6 +27,21 @@ class SettingsFragment : BaseUIFragment() { @@ -15,6 +20,25 @@ class SuperuserFragment : BaseUIFragment + app:fitsSystemWindowsInsets="top"> @@ -46,12 +45,13 @@ android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/l1" - android:layout_marginBottom="@{(int) @dimen/l1 + viewModel.insets.bottom}" + android:layout_marginBottom="@dimen/l1" android:onClick="@{() -> viewModel.restartPressed()}" android:text="@string/reboot" android:textAllCaps="false" android:textColor="?colorOnPrimary" android:textStyle="bold" + app:layout_fitsSystemWindowsInsets="bottom" app:backgroundTint="?colorPrimary" app:icon="@drawable/ic_restart" app:iconTint="?colorOnPrimary" /> diff --git a/app/src/main/res/layout/fragment_hide_md2.xml b/app/src/main/res/layout/fragment_hide_md2.xml index 50e6fbb14..82832b517 100644 --- a/app/src/main/res/layout/fragment_hide_md2.xml +++ b/app/src/main/res/layout/fragment_hide_md2.xml @@ -17,7 +17,6 @@ @@ -41,8 +38,9 @@ android:layout_gravity="bottom|end" android:layout_marginStart="@dimen/l1" android:layout_marginEnd="@dimen/l1" - android:layout_marginBottom="@{viewModel.insets.bottom + (int) @dimen/l1}" + android:layout_marginBottom="@dimen/l1" app:backgroundTint="?colorSurfaceSurfaceVariant" + app:layout_fitsSystemWindowsInsets="bottom" app:srcCompat="@drawable/ic_search_md2" app:tint="?colorPrimary" tools:layout_marginBottom="64dp" /> diff --git a/app/src/main/res/layout/fragment_home_md2.xml b/app/src/main/res/layout/fragment_home_md2.xml index 725530853..419870f24 100644 --- a/app/src/main/res/layout/fragment_home_md2.xml +++ b/app/src/main/res/layout/fragment_home_md2.xml @@ -22,8 +22,9 @@ android:layout_height="match_parent" android:clipToPadding="false" android:fillViewport="true" - android:paddingTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size}" - android:paddingBottom="@{viewModel.insets.bottom + (int) @dimen/l3}" + android:paddingTop="@dimen/internal_action_bar_size" + android:paddingBottom="@dimen/l3" + app:fitsSystemWindowsInsets="top|bottom" tools:layout_marginTop="24dp"> + android:paddingTop="@dimen/l_50"> @@ -54,7 +50,8 @@ android:layout_gravity="bottom|end" android:layout_marginStart="@dimen/l1" android:layout_marginEnd="@dimen/l1" - android:layout_marginBottom="@{viewModel.insets.bottom + (int) @dimen/l1}" + android:layout_marginBottom="@dimen/l1" + app:layout_fitsSystemWindowsInsets="bottom" app:backgroundTint="?colorSurfaceSurfaceVariant" app:srcCompat="@drawable/ic_search_md2" app:tint="?colorPrimary" diff --git a/app/src/main/res/layout/fragment_safetynet_md2.xml b/app/src/main/res/layout/fragment_safetynet_md2.xml index 60163c385..f7f6b0b17 100644 --- a/app/src/main/res/layout/fragment_safetynet_md2.xml +++ b/app/src/main/res/layout/fragment_safetynet_md2.xml @@ -22,8 +22,8 @@ android:layout_height="match_parent" android:clipToPadding="false" android:fillViewport="true" - android:paddingTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size}" - android:paddingBottom="@{viewModel.insets.bottom}" + android:paddingTop="@dimen/internal_action_bar_size" + app:fitsSystemWindowsInsets="top|bottom" tools:paddingBottom="48dp" tools:paddingTop="24dp"> diff --git a/app/src/main/res/layout/fragment_settings_md2.xml b/app/src/main/res/layout/fragment_settings_md2.xml index 4f83edc5d..4150efe26 100644 --- a/app/src/main/res/layout/fragment_settings_md2.xml +++ b/app/src/main/res/layout/fragment_settings_md2.xml @@ -15,8 +15,6 @@ diff --git a/app/src/main/res/layout/fragment_theme_md2.xml b/app/src/main/res/layout/fragment_theme_md2.xml index f5f87f703..bf4babb35 100644 --- a/app/src/main/res/layout/fragment_theme_md2.xml +++ b/app/src/main/res/layout/fragment_theme_md2.xml @@ -1,5 +1,6 @@ - + @@ -17,15 +18,17 @@ android:clipToPadding="false" android:fillViewport="true" android:paddingStart="@dimen/l1" - android:paddingTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size + (int) @dimen/l1}" + android:paddingTop="@dimen/internal_action_bar_size" android:paddingEnd="@dimen/l1" - android:paddingBottom="@{viewModel.insets.bottom + (int) @dimen/l1}"> + android:paddingBottom="@dimen/l1" + app:fitsSystemWindowsInsets="top|bottom"> diff --git a/app/src/main/res/layout/include_log_magisk.xml b/app/src/main/res/layout/include_log_magisk.xml index 04b2e6dec..1b786a6c1 100644 --- a/app/src/main/res/layout/include_log_magisk.xml +++ b/app/src/main/res/layout/include_log_magisk.xml @@ -1,5 +1,6 @@ @@ -32,8 +33,8 @@ precomputedText="@{viewModel.consoleText}" android:textAppearance="@style/AppearanceFoundation.Caption" android:textSize="10sp" - android:paddingTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size}" - android:paddingBottom="@{viewModel.insets.bottom}" + android:paddingTop="@dimen/internal_action_bar_size" + app:layout_fitsSystemWindowsInsets="top|bottom" tools:text="@tools:sample/lorem/random" /> diff --git a/app/src/main/res/layout/include_log_superuser.xml b/app/src/main/res/layout/include_log_superuser.xml index a447326a4..a0cfb620d 100644 --- a/app/src/main/res/layout/include_log_superuser.xml +++ b/app/src/main/res/layout/include_log_superuser.xml @@ -16,16 +16,15 @@ android:layout_height="match_parent"> diff --git a/app/src/main/res/layout/include_module_filter.xml b/app/src/main/res/layout/include_module_filter.xml index d598208d3..c95dbe889 100644 --- a/app/src/main/res/layout/include_module_filter.xml +++ b/app/src/main/res/layout/include_module_filter.xml @@ -18,14 +18,13 @@ android:layout_height="match_parent" android:clipToPadding="false" android:orientation="vertical" - android:paddingBottom="@{viewModel.insets.bottom + (int) @dimen/l1}" + android:paddingBottom="@dimen/l1" + app:fitsSystemWindowsInsets="bottom" tools:layout_gravity="bottom" tools:paddingBottom="64dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 398106f66..7be52e7fb 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -1,7 +1,8 @@ - - + + +