diff --git a/packages/core/platforms/android/widgets-release.aar b/packages/core/platforms/android/widgets-release.aar index d27c48d18e..6b7056f32c 100644 Binary files a/packages/core/platforms/android/widgets-release.aar and b/packages/core/platforms/android/widgets-release.aar differ diff --git a/packages/ui-mobile-base/android/widgetdemo/src/main/java/org/nativescript/widgetsdemo/MainActivity.kt b/packages/ui-mobile-base/android/widgetdemo/src/main/java/org/nativescript/widgetsdemo/MainActivity.kt index 2591ff9b90..aa4c6ea0b1 100644 --- a/packages/ui-mobile-base/android/widgetdemo/src/main/java/org/nativescript/widgetsdemo/MainActivity.kt +++ b/packages/ui-mobile-base/android/widgetdemo/src/main/java/org/nativescript/widgetsdemo/MainActivity.kt @@ -25,7 +25,12 @@ import org.nativescript.widgets.Utils class MainActivity : AppCompatActivity() { - class Frag : DialogFragment() { + companion object { + private const val TAG = "EdgeToEdgeDemo" + } + + // Test 1: Dialog with OverflowEdgeNone + keyboard + class FragEdgeNone : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val dialog = super.onCreateDialog(savedInstanceState) @@ -48,15 +53,154 @@ class MainActivity : AppCompatActivity() { content.layoutParams = params val txt = TextView(requireContext()) - txt.text = "Help" + txt.text = "OverflowEdgeNone — tap the input and check bottom padding" + + val input = EditText(requireContext()) + input.hint = "Tap to open keyboard" + input.setTextColor(Color.WHITE) + + content.addView(txt) + content.addView(input) + page.addView(content) + + dialog.setContentView( + page, ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + + dialog.window?.apply { + Utils.enableEdgeToEdge(requireActivity(), this) + setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + + return dialog + } + } + + // Test 2: Dialog with OverflowEdgeBottom + keyboard (the bug case) + class FragEdgeBottom : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + + val page = StackLayout(requireContext()) + page.setBackgroundColor(Color.parseColor("#FF6600")) + page.overflowEdge = LayoutBase.OverflowEdgeBottom + + val label = TextView(requireContext()) + label.text = "OverflowEdgeBottom — keyboard should NOT double the bottom padding" + label.setTextColor(Color.WHITE) + + val input = EditText(requireContext()) + input.hint = "Tap to open keyboard" + input.setTextColor(Color.WHITE) + + val bottomLabel = TextView(requireContext()) + bottomLabel.text = "This text should sit just above the keyboard, not have a big gap" + bottomLabel.setTextColor(Color.WHITE) + + page.addView(label) + page.addView(input) + page.addView(bottomLabel) + + dialog.setContentView( + page, ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + + dialog.window?.apply { + Utils.enableEdgeToEdge(requireActivity(), this) + setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + + return dialog + } + } + + // Test 3: Dialog with nested layouts both applying bottom insets + class FragNested : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) - content.addView( - txt + val outer = StackLayout(requireContext()) + outer.setBackgroundColor(Color.parseColor("#880088")) + // Parent applies bottom — does NOT consume it + outer.overflowEdge = LayoutBase.OverflowEdgeBottom or LayoutBase.OverflowEdgeBottomDontConsume + + val inner = StackLayout(requireContext()) + inner.setBackgroundColor(Color.parseColor("#008888")) + inner.layoutParams = CommonLayoutParams( + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) ) - page.addView( - content + // Child also applies bottom — so insets could stack + inner.overflowEdge = LayoutBase.OverflowEdgeBottom + + val label = TextView(requireContext()) + label.text = "Nested: outer + inner both apply bottom. Check for double padding." + label.setTextColor(Color.WHITE) + + val input = EditText(requireContext()) + input.hint = "Tap to open keyboard" + input.setTextColor(Color.WHITE) + + inner.addView(label) + inner.addView(input) + outer.addView(inner) + + dialog.setContentView( + outer, ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) ) + dialog.window?.apply { + Utils.enableEdgeToEdge(requireActivity(), this) + setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } + + return dialog + } + } + + // Test 4: Dialog testing setPadding after insets are applied + class FragSetPaddingAfter : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + + val page = StackLayout(requireContext()) + page.setBackgroundColor(Color.parseColor("#006600")) + page.overflowEdge = LayoutBase.OverflowEdgeNone + + val label = TextView(requireContext()) + label.text = "setPadding after insets — bottom should be user padding + inset, not doubled" + label.setTextColor(Color.WHITE) + + val input = EditText(requireContext()) + input.hint = "Tap to open keyboard" + input.setTextColor(Color.WHITE) + + val paddingBtn = Button(requireContext()) + paddingBtn.text = "Call setPadding(20,20,20,20)" + paddingBtn.setOnClickListener { + val dp20 = (20 * page.resources.displayMetrics.density).toInt() + Log.d(TAG, "Before setPadding: paddingBottom=${page.paddingBottom}") + page.setPadding(dp20, dp20, dp20, dp20) + Log.d(TAG, "After setPadding: paddingBottom=${page.paddingBottom}") + Log.d(TAG, " edgeInsets.bottom=${page.edgeInsets.bottom}") + Log.d(TAG, " imeInsets.bottom=${page.imeInsets.bottom}") + } + + page.addView(label) + page.addView(input) + page.addView(paddingBtn) + dialog.setContentView( page, ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -69,6 +213,70 @@ class MainActivity : AppCompatActivity() { setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) } + return dialog + } + } + + // Test 5: Dialog cycling overflow modes while keyboard is open + class FragCycleMode : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + + val page = StackLayout(requireContext()) + page.setBackgroundColor(Color.DKGRAY) + page.overflowEdge = LayoutBase.OverflowEdgeNone + + val statusLabel = TextView(requireContext()) + statusLabel.text = "Mode: OverflowEdgeNone" + statusLabel.setTextColor(Color.YELLOW) + statusLabel.textSize = 18f + + val paddingLabel = TextView(requireContext()) + paddingLabel.text = "paddingBottom: ?" + paddingLabel.setTextColor(Color.CYAN) + paddingLabel.textSize = 14f + + val input = EditText(requireContext()) + input.hint = "Open keyboard FIRST, then cycle modes" + input.setTextColor(Color.WHITE) + + val modes = listOf( + "OverflowEdgeNone" to LayoutBase.OverflowEdgeNone, + "OverflowEdgeBottom" to LayoutBase.OverflowEdgeBottom, + "OverflowEdgeAllButTop" to LayoutBase.OverflowEdgeAllButTop, + "OverflowEdgeDontApply" to LayoutBase.OverflowEdgeDontApply, + ) + var modeIndex = 0 + + val cycleBtn = Button(requireContext()) + cycleBtn.text = "Cycle Overflow Mode" + cycleBtn.setOnClickListener { + modeIndex = (modeIndex + 1) % modes.size + val (name, mode) = modes[modeIndex] + page.overflowEdge = mode + statusLabel.text = "Mode: $name" + page.postDelayed(100) { + paddingLabel.text = "paddingBottom: ${page.paddingBottom} (edge=${page.edgeInsets.bottom}, ime=${page.imeInsets.bottom})" + Log.d(TAG, "Cycle -> $name: paddingBottom=${page.paddingBottom}, edge=${page.edgeInsets.bottom}, ime=${page.imeInsets.bottom}") + } + } + + page.addView(statusLabel) + page.addView(paddingLabel) + page.addView(input) + page.addView(cycleBtn) + + dialog.setContentView( + page, ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + + dialog.window?.apply { + Utils.enableEdgeToEdge(requireActivity(), this) + setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) + } return dialog } @@ -80,7 +288,7 @@ class MainActivity : AppCompatActivity() { Utils.enableEdgeToEdge(this) val frame = ContentLayout(this) - frame.overflowEdge = LayoutBase.OverflowEdgeDontApply + frame.overflowEdge = LayoutBase.OverflowEdgeIgnore val page = GridLayout(this) @@ -125,144 +333,62 @@ class MainActivity : AppCompatActivity() { setSupportActionBar(ab) supportActionBar?.setDisplayShowTitleEnabled(true) - supportActionBar?.title = "???" + page.overflowEdge = LayoutBase.OverflowEdgeNone - page.setBackgroundColor(Color.MAGENTA) - page.overflowEdge = LayoutBase.OverflowEdgeDontApply - - page.setInsetListener { - page.setPadding(0,0,0, it.getInt(32)) - Log.d("com.test", "ime ${it.getInt(32)}") - } -// page.setInsetListener { -// val insets = it.asIntBuffer() -// insets.put(0,0) -// insets.put(1,0) -// insets.put(2,0) -// insets.put(3,0) -//// insets.put(3,0) -//// insets.put(5,1) -// } - - frame.setBackgroundColor(Color.BLUE) - frame.layoutParams = CommonLayoutParams( + val buttonContainer = StackLayout(this) + val btnContainerParams = CommonLayoutParams( ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - ) - - val svp = StackLayout(this) -// svp.setBackgroundColor(Color.YELLOW) - val svParams = CommonLayoutParams( - ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - ) - svParams.row = 1 - svp.layoutParams = svParams -// svp.setInsetListener { -// val insets = it.asIntBuffer() -// insets.put(0,0) -// insets.put(1,0) -// insets.put(2,0) -// insets.put(3,0) -// // insets.put(7, 1) -// } - - val scrollView = NestedScrollView(this) - scrollView.layoutParams = CommonLayoutParams( - ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - ) - svp.addView(scrollView) - - val container = StackLayout(this) - container.setPadding(0,0,0,100) - container.setBackgroundColor(Color.RED) - val text = TextView(this) - text.text = getString(R.string.ipsum) - text.textSize = 40f - text.setTextColor(Color.WHITE) - container.addView(text) - - - val other = EditText(this) - other.hint = "Enter Text" - other.setTextColor(Color.WHITE) - container.addView(other, 0) - - - val input = EditText(this) - input.hint = "Enter Text" - input.setTextColor(Color.WHITE) - container.addView(input) - scrollView.addView(container) - - - page.addView(svp) - val btn = Button(this) - btn.text = "Toggle Overflow" - val params = CommonLayoutParams( - ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ) ) - params.row = 1 - params.width = 300 - params.height = 300 - params.width - btn.layoutParams = params - btn.setOnClickListener { - /* - val new_page = StackLayout(this) - new_page.setBackgroundColor(Color.BLUE) - new_page.layoutParams = CommonLayoutParams( - ViewGroup.LayoutParams( - 300, 300 - ) - ) - container.addView(new_page) - - - svp.overflowEdge = when (svp.overflowEdge) { - LayoutBase.OverflowEdgeNone -> { - LayoutBase.OverflowEdgeDontApply - } - - LayoutBase.OverflowEdgeDontApply -> { - LayoutBase.OverflowEdgeTop - } - - LayoutBase.OverflowEdgeTop -> { - LayoutBase.OverflowEdgeBottom - } + btnContainerParams.row = 1 + buttonContainer.layoutParams = btnContainerParams - LayoutBase.OverflowEdgeBottom -> { - LayoutBase.OverflowEdgeTop.and(LayoutBase.OverflowEdgeBottom) - } - - else -> LayoutBase.OverflowEdgeDontApply + val btnEdgeNone = Button(this).apply { + this.text = "Test: EdgeNone + IME" + setOnClickListener { + FragEdgeNone().show(supportFragmentManager, "edge_none") } + } - */ + val btnEdgeBottom = Button(this).apply { + this.text = "Test: EdgeBottom + IME (bug)" + setOnClickListener { + FragEdgeBottom().show(supportFragmentManager, "edge_bottom") + } + } + val btnNested = Button(this).apply { + this.text = "Test: Nested insets" + setOnClickListener { + FragNested().show(supportFragmentManager, "nested") + } + } - val frame = Frag() + val btnSetPadding = Button(this).apply { + this.text = "Test: setPadding after insets" + setOnClickListener { + FragSetPaddingAfter().show(supportFragmentManager, "set_padding") + } + } - frame.show(supportFragmentManager, "test") + val btnCycle = Button(this).apply { + this.text = "Test: Cycle modes + IME" + setOnClickListener { + FragCycleMode().show(supportFragmentManager, "cycle") + } + } + buttonContainer.addView(btnEdgeNone) + buttonContainer.addView(btnEdgeBottom) + buttonContainer.addView(btnNested) + buttonContainer.addView(btnSetPadding) + buttonContainer.addView(btnCycle) - } - page.addView(btn) + page.addView(buttonContainer) frame.addView(page) setContentView(frame) - - } } diff --git a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java index 8436de5f69..ea0b256115 100644 --- a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java +++ b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java @@ -423,8 +423,7 @@ public WindowInsetsCompat onApplyWindowInsets( int top = mPaddingTop + (apply[1] ? insetTop : 0); int right = mPaddingRight + (apply[2] ? insetRight : 0); int bottom = mPaddingBottom - + (apply[3] ? insetNavBottom : 0) - + (apply[3] ? insetImeBottom : 0); + + (apply[3] ? Math.max(insetNavBottom, insetImeBottom) : 0); edgeInsets = Insets.of( apply[0] ? insetLeft : 0, @@ -475,4 +474,7 @@ public WindowInsetsCompat onApplyWindowInsets( public int getOverflowEdge() { return overflowEdge; } + + + }