Skip to content

Commit f011e8b

Browse files
authored
ADFA-2646 | Fix window insets accumulation and landscape appbar behavior (#1114)
* fix: resolve window insets accumulation on app recreation and landscape mode Refactor inset extensions to cache initial padding and prevent redundant padding calculations * fix: resolve inset handling and swipe-reveal animation issues
1 parent a7fa4b8 commit f011e8b

File tree

4 files changed

+188
-41
lines changed

4 files changed

+188
-41
lines changed

app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ import androidx.appcompat.app.ActionBarDrawerToggle
5454
import androidx.collection.MutableIntIntMap
5555
import androidx.core.graphics.Insets
5656
import androidx.core.view.GravityCompat
57+
import androidx.core.view.ViewCompat
5758
import androidx.core.view.WindowInsetsCompat
5859
import androidx.core.view.updateLayoutParams
5960
import androidx.core.view.updatePadding
6061
import androidx.fragment.app.Fragment
61-
import com.itsaky.androidide.utils.applyBottomWindowInsetsPadding
6262
import androidx.lifecycle.Lifecycle
6363
import androidx.lifecycle.lifecycleScope
6464
import androidx.lifecycle.repeatOnLifecycle
@@ -118,8 +118,13 @@ import com.itsaky.androidide.utils.FlashType
118118
import com.itsaky.androidide.utils.InstallationResultHandler.onResult
119119
import com.itsaky.androidide.utils.IntentUtils
120120
import com.itsaky.androidide.utils.MemoryUsageWatcher
121+
import com.itsaky.androidide.utils.applyResponsiveAppBarInsets
122+
import com.itsaky.androidide.utils.applyImmersiveModeInsets
123+
import com.itsaky.androidide.utils.applyRootSystemInsetsAsPadding
124+
import com.itsaky.androidide.utils.applyBottomSheetAnchorForOrientation
121125
import com.itsaky.androidide.utils.flashError
122126
import com.itsaky.androidide.utils.flashMessage
127+
import com.itsaky.androidide.utils.getOrStoreInitialPadding
123128
import com.itsaky.androidide.utils.isAtLeastR
124129
import com.itsaky.androidide.utils.resolveAttr
125130
import com.itsaky.androidide.viewmodel.ApkInstallationViewModel
@@ -374,7 +379,6 @@ abstract class BaseEditorActivity :
374379
private val flingVelocityThreshold by lazy { SizeUtils.dp2px(100f) }
375380

376381
private var editorAppBarInsetTop: Int = 0
377-
private var sidebarLastInsetTop: Int = 0
378382

379383
companion object {
380384
private const val TAG = "ResizePanelDebugger"
@@ -484,47 +488,22 @@ abstract class BaseEditorActivity :
484488
val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime())
485489
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
486490

487-
applyStandardInsets(systemBars, insets)
491+
applyStandardInsets(systemBars)
488492

489493
applyImmersiveModeInsets(systemBars)
490494

491495
handleKeyboardInsets(imeInsets)
492496
}
493497

494-
private fun applyStandardInsets(systemBars: Insets, windowInsets: WindowInsetsCompat) {
495-
val content = _binding?.content ?: return
496-
val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
497-
498-
if (isLandscape) {
499-
content.editorAppBarLayout.updatePadding(top = 0)
500-
content.editorAppbarContent.updatePadding(top = systemBars.top)
501-
} else {
502-
content.editorAppBarLayout.updatePadding(top = systemBars.top)
503-
content.editorAppbarContent.updatePadding(top = 0)
504-
}
505-
498+
private fun applyStandardInsets(systemBars: Insets) {
506499
immersiveController?.onSystemBarInsetsChanged(systemBars.top)
507-
applySidebarInsets(systemBars)
508-
_binding?.root?.applyBottomWindowInsetsPadding(windowInsets)
500+
val root = _binding?.root ?: return
501+
val initial = root.getOrStoreInitialPadding()
502+
root.updatePadding(bottom = initial.bottom + systemBars.bottom)
509503
}
510504

511505
private fun applyImmersiveModeInsets(systemBars: Insets) {
512-
val content = _binding?.content ?: return
513-
val baseMargin = SizeUtils.dp2px(16f)
514-
val isRtl = content.root.layoutDirection == View.LAYOUT_DIRECTION_RTL
515-
val endInset = if (isRtl) systemBars.left else systemBars.right
516-
517-
content.btnToggleTopBar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
518-
topMargin = baseMargin + systemBars.top
519-
marginEnd = baseMargin + endInset
520-
}
521-
522-
content.btnToggleBottomBar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
523-
bottomMargin = baseMargin + systemBars.bottom
524-
marginEnd = baseMargin + endInset
525-
}
526-
527-
content.bottomSheet.updatePadding(top = systemBars.top)
506+
_binding?.content?.applyImmersiveModeInsets(systemBars)
528507
}
529508

530509
private fun handleKeyboardInsets(imeInsets: Insets) {
@@ -558,13 +537,6 @@ abstract class BaseEditorActivity :
558537
editorAppBarInsetTop = insets.top
559538
}
560539

561-
private fun applySidebarInsets(systemBars: Insets) {
562-
val sidebar = _binding?.drawerSidebar ?: return
563-
val baseTop = sidebar.paddingTop - sidebarLastInsetTop
564-
sidebarLastInsetTop = systemBars.top
565-
sidebar.updatePadding(top = baseTop + systemBars.top)
566-
}
567-
568540
@Subscribe(threadMode = MAIN)
569541
open fun onInstallationResult(event: InstallationEvent.InstallationResultEvent) {
570542
val intent = event.intent
@@ -696,14 +668,33 @@ abstract class BaseEditorActivity :
696668
override fun onConfigurationChanged(newConfig: Configuration) {
697669
super.onConfigurationChanged(newConfig)
698670
immersiveController?.onConfigurationChanged(newConfig)
671+
window?.decorView?.let { ViewCompat.requestApplyInsets(it) }
672+
reapplySystemBarInsetsFromRoot()
673+
_binding?.content?.applyBottomSheetAnchorForOrientation(newConfig.orientation)
699674
}
700675

676+
private fun reapplySystemBarInsetsFromRoot() {
677+
val root = _binding?.root ?: return
678+
val rootInsets = ViewCompat.getRootWindowInsets(root)
679+
if (rootInsets == null) {
680+
root.post { reapplySystemBarInsetsFromRoot() }
681+
return
682+
}
683+
684+
val systemBars = rootInsets.getInsets(WindowInsetsCompat.Type.systemBars())
685+
applyStandardInsets(systemBars)
686+
applyImmersiveModeInsets(systemBars)
687+
}
688+
689+
701690
private fun setupToolbar() {
702691
// Set the project name in the title TextView
703692
content.root.findViewById<TextView>(R.id.title_text)?.apply {
704693
text = editorViewModel.getProjectName()
705694
}
706695

696+
content.editorAppBarLayout.applyResponsiveAppBarInsets(content.editorAppbarContent)
697+
707698
// Set up the drawer toggle on the title toolbar (where the hamburger menu should be)
708699
content.titleToolbar.apply {
709700
val toggle =
@@ -755,7 +746,13 @@ abstract class BaseEditorActivity :
755746
val insetsTop = systemBarInsets?.top ?: 0
756747
val topInset = (insetsTop * (1f - progress)).roundToInt()
757748

758-
content.editorAppbarContent.updatePadding(top = topInset)
749+
val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
750+
751+
if (isLandscape) {
752+
content.editorAppbarContent.updatePadding(top = topInset)
753+
} else {
754+
content.editorAppBarLayout.updatePadding(top = topInset)
755+
}
759756

760757
memUsageView.chart.updateLayoutParams<ViewGroup.MarginLayoutParams> {
761758
topMargin = (insetsTop * progress).roundToInt()
@@ -1111,6 +1108,7 @@ abstract class BaseEditorActivity :
11111108
ContentTranslatingDrawerLayout.TranslationBehavior.FULL
11121109
setScrimColor(Color.TRANSPARENT)
11131110
}
1111+
drawerSidebar.applyRootSystemInsetsAsPadding(applyTop = true)
11141112
}
11151113
}
11161114

app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,19 @@ constructor(
366366
view.viewTreeObserver.addOnGlobalLayoutListener(listener)
367367
}
368368

369+
fun resetOffsetAnchor() {
370+
anchorOffset = 0
371+
behavior.peekHeight = collapsedHeight.roundToInt()
372+
behavior.expandedOffset = 0
373+
binding.root.updatePadding(bottom = insetBottom)
374+
binding.headerContainer.apply {
375+
updatePaddingRelative(bottom = insetBottom)
376+
updateLayoutParams<LayoutParams> {
377+
height = (collapsedHeight + insetBottom).roundToInt()
378+
}
379+
}
380+
}
381+
369382
fun onSlide(sheetOffset: Float) {
370383
val heightScale =
371384
if (sheetOffset >= COLLAPSE_HEADER_AT_OFFSET) {
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package com.itsaky.androidide.utils
2+
3+
import android.content.res.Configuration
4+
import android.view.View
5+
import androidx.core.view.ViewCompat
6+
import androidx.core.view.WindowInsetsCompat
7+
import androidx.core.view.doOnAttach
8+
import androidx.core.view.updatePadding
9+
import com.itsaky.androidide.R
10+
import com.itsaky.androidide.databinding.ContentEditorBinding
11+
import com.blankj.utilcode.util.SizeUtils
12+
import androidx.core.graphics.Insets
13+
import android.view.ViewGroup
14+
import androidx.core.view.updateLayoutParams
15+
16+
data class InitialPadding(val left: Int, val top: Int, val right: Int, val bottom: Int)
17+
18+
/**
19+
* Gets or stores the view's original padding to prevent infinite accumulation when applying insets.
20+
*
21+
* @return The original [InitialPadding].
22+
*/
23+
fun View.getOrStoreInitialPadding(): InitialPadding {
24+
return (getTag(R.id.tag_initial_padding) as? InitialPadding)
25+
?: InitialPadding(paddingLeft, paddingTop, paddingRight, paddingBottom).also {
26+
setTag(R.id.tag_initial_padding, it)
27+
}
28+
}
29+
30+
/**
31+
* Applies top window insets responsively. Hides the AppBar in landscape mode and adjusts [appbarContent].
32+
* Forces an inset request on attach to prevent drawing behind system bars after activity recreation.
33+
*
34+
* @param appbarContent The inner content view to pad in landscape mode.
35+
*/
36+
fun View.applyResponsiveAppBarInsets(appbarContent: View) {
37+
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
38+
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
39+
val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
40+
41+
if (isLandscape) {
42+
view.updatePadding(top = 0)
43+
appbarContent.updatePadding(top = insets.top)
44+
} else {
45+
view.updatePadding(top = insets.top)
46+
appbarContent.updatePadding(top = 0)
47+
}
48+
windowInsets
49+
}
50+
51+
doOnAttach { it.requestApplyInsets() }
52+
}
53+
54+
/**
55+
* Applies root system window insets as padding, preserving the view's initial padding.
56+
* Useful for deeply nested views (like DrawerLayouts) where standard inset listeners fail.
57+
*
58+
* @param applyLeft Apply left inset.
59+
* @param applyTop Apply top inset.
60+
* @param applyRight Apply right inset.
61+
* @param applyBottom Apply bottom inset.
62+
*/
63+
fun View.applyRootSystemInsetsAsPadding(
64+
applyLeft: Boolean = false,
65+
applyTop: Boolean = false,
66+
applyRight: Boolean = false,
67+
applyBottom: Boolean = false
68+
) {
69+
val initial = getOrStoreInitialPadding()
70+
71+
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
72+
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
73+
74+
view.updatePadding(
75+
left = initial.left + if (applyLeft) insets.left else 0,
76+
top = initial.top + if (applyTop) insets.top else 0,
77+
right = initial.right + if (applyRight) insets.right else 0,
78+
bottom = initial.bottom + if (applyBottom) insets.bottom else 0
79+
)
80+
windowInsets
81+
}
82+
83+
doOnAttach { it.requestApplyInsets() }
84+
}
85+
86+
/**
87+
* Applies immersive mode insets to editor UI elements that float near system bars.
88+
*
89+
* Keeps toggle buttons and bottom sheet aligned with system bars (status/nav).
90+
*/
91+
fun ContentEditorBinding.applyImmersiveModeInsets(systemBars: Insets) {
92+
val baseMargin = SizeUtils.dp2px(16f)
93+
val isRtl = root.layoutDirection == View.LAYOUT_DIRECTION_RTL
94+
val endInset = if (isRtl) systemBars.left else systemBars.right
95+
96+
btnToggleTopBar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
97+
topMargin = baseMargin + systemBars.top
98+
marginEnd = baseMargin + endInset
99+
}
100+
101+
btnToggleBottomBar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
102+
bottomMargin = baseMargin + systemBars.bottom
103+
marginEnd = baseMargin + endInset
104+
}
105+
106+
bottomSheet.updatePadding(top = systemBars.top)
107+
}
108+
109+
/**
110+
* Recomputes bottom sheet offsets based on the current app bar height.
111+
*/
112+
fun ContentEditorBinding.refreshBottomSheetAnchor() {
113+
bottomSheet.setOffsetAnchor(editorAppBarLayout)
114+
}
115+
116+
/**
117+
* Allows the bottom sheet to expand fully (no app bar anchor).
118+
*/
119+
fun ContentEditorBinding.resetBottomSheetAnchor() {
120+
bottomSheet.resetOffsetAnchor()
121+
}
122+
123+
/**
124+
* Applies the correct bottom sheet anchor based on orientation.
125+
*/
126+
fun ContentEditorBinding.applyBottomSheetAnchorForOrientation(orientation: Int) {
127+
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
128+
refreshBottomSheetAnchor()
129+
} else {
130+
resetBottomSheetAnchor()
131+
}
132+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<item name="tag_initial_padding" type="id" />
4+
</resources>

0 commit comments

Comments
 (0)