Skip to content

Added better support for pocket taco/8bitdo flippad type portrait con…#541

Merged
utkarshdalal merged 1 commit intomasterfrom
portrait-orientation
Feb 17, 2026
Merged

Added better support for pocket taco/8bitdo flippad type portrait con…#541
utkarshdalal merged 1 commit intomasterfrom
portrait-orientation

Conversation

@utkarshdalal
Copy link
Owner

@utkarshdalal utkarshdalal commented Feb 17, 2026

…trollers


Summary by cubic

Adds a portrait-specific layout and touch routing to better support Pocket TACO/8BitDo FlipPad-style controllers. Game stays on top; controls live in a fixed-height bottom panel for a more consistent feel.

  • New Features
    • Portrait layout: game on top; controls in a bottom container sized to 9:16 of screen width (black background).
    • Touch routing: in portrait, events go directly to the game view; landscape behavior unchanged (controls first, then touchpad).

Written for commit 8e97aa3. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • Improvements
    • Added adaptive control layout system that automatically adjusts placement based on device orientation (portrait displays controls at the bottom; landscape as overlay)
    • Enhanced touch input dispatch and event routing to optimize interactions across different screen orientations
    • Improved internal view management for better stability

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

Introduces orientation-aware UI layout scaffolding to XServerScreen by conditionally creating portrait (vertical LinearLayout) or landscape (FrameLayout) containers, repositioning InputControlsView placement based on orientation, and routing touch events through gameRoot state in portrait mode while retaining previous behavior in landscape.

Changes

Cohort / File(s) Summary
Orientation-Aware Layout Refactoring
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
Introduces isPortrait-based conditional logic to create either LinearLayout (portrait) or FrameLayout (landscape) as mainRoot container. Adds gameRoot state variable for touch event dispatch in portrait mode. Repositions InputControlsView to bottom-fixed controlsContainer in portrait vs. overlay in landscape. Updates touch event routing in pointerInteropFilter. Adds imports for Configuration, LinearLayout, and LocalConfiguration. Adjusts lifecycle management to assign and clear gameRoot reference.

Sequence Diagram

sequenceDiagram
    participant User
    participant XServerScreen
    participant Config as Configuration
    participant Layout as Layout Container
    participant InputControls as InputControlsView
    participant GameRoot as gameRoot

    User->>XServerScreen: Render with orientation
    XServerScreen->>Config: Check isPortrait
    alt Portrait Mode
        Config-->>XServerScreen: isPortrait = true
        XServerScreen->>Layout: Create vertical LinearLayout as mainRoot
        XServerScreen->>GameRoot: Assign gameRoot reference
        XServerScreen->>Layout: Create bottom controlsContainer
        XServerScreen->>InputControls: Place in controlsContainer (bottom-fixed)
        User->>InputControls: Touch input
        InputControls->>GameRoot: Route via gameRoot.dispatchTouchEvent()
    else Landscape Mode
        Config-->>XServerScreen: isPortrait = false
        XServerScreen->>Layout: Use FrameLayout as mainRoot
        XServerScreen->>InputControls: Add as overlay to frameLayout
        User->>InputControls: Touch input
        InputControls->>Layout: Handle directly (previous behavior)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops through portrait and land,
Adjusting layouts with a careful hand,
Touch events flow where gameRoot may lead,
Controls repositioned for every creed,
Orientation wisdom in every bound! 🎮✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding support for portrait controllers (pocket taco/8bitdo flippad), which aligns with the portrait vs. landscape orientation handling introduced in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into master

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch portrait-orientation

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt (1)

654-672: ⚠️ Potential issue | 🟡 Minor

Portrait mode may double-dispatch touch events to swapInputOverlay.

When the overlay is visible but doesn't consume the event (line 657 returns false), the code falls through to line 661 which dispatches to gameRoot. Since the overlay is a descendant of gameRoot (added at line 1013 inside frameLayout), Android's view dispatch will attempt to deliver the same event to the overlay again.

In landscape mode this isn't an issue because the fallthrough path (lines 663–670) manually routes to specific views, skipping the overlay. Consider adding the same manual routing for portrait, or skipping the overlay in gameRoot dispatch:

Suggested approach
             if (isPortrait) {
-                    gameRoot?.dispatchTouchEvent(event)
+                    val controlsHandled = if (areControlsVisible) {
+                        PluviaApp.inputControlsView?.onTouchEvent(event) ?: false
+                    } else {
+                        false
+                    }
+                    if (!controlsHandled) {
+                        PluviaApp.touchpadView?.onTouchEvent(event)
+                    }

Alternatively, if gameRoot?.dispatchTouchEvent is the desired approach for coordinate-based routing, consider making the overlay intercept check exhaustive (i.e., always return true from pointerInteropFilter before reaching gameRoot if overlay is visible, even if overlay didn't consume).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` around
lines 654 - 672, The pointerInteropFilter can double-dispatch when
swapInputOverlay is visible but returns false; modify the pointerInteropFilter
in XServerScreen so that when swapInputOverlay?.visibility == View.VISIBLE you
do not fall through to gameRoot: call
swapInputOverlay?.dispatchTouchEvent(event) as you do now, and if it returns
true return true, otherwise also return true (consume the event) to prevent
gameRoot?.dispatchTouchEvent(event) from running; reference swapInputOverlay,
pointerInteropFilter, gameRoot and isPortrait and keep the existing landscape
routing unchanged (or alternatively replace the portrait path with manual
routing to specific controls if you prefer coordinate-based dispatch).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Line 647: The factory-created view hierarchy inside AndroidView can become
out-of-sync with the composable-scoped isPortrait used by pointerInteropFilter
because the factory lambda (where context.resources.configuration.orientation is
captured) only runs once; update the code to ensure the view is recreated when
orientation changes by wrapping the AndroidView in a key(isPortrait) (or
otherwise trigger re-creation) so the factory rebuilds the correct layout, or
refactor the view construction to be orientation-adaptive; reference isPortrait,
the factory lambda that uses context.resources.configuration.orientation,
AndroidView, pointerInteropFilter, and PrefManager.allowedOrientation when
making the change.

---

Outside diff comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Around line 654-672: The pointerInteropFilter can double-dispatch when
swapInputOverlay is visible but returns false; modify the pointerInteropFilter
in XServerScreen so that when swapInputOverlay?.visibility == View.VISIBLE you
do not fall through to gameRoot: call
swapInputOverlay?.dispatchTouchEvent(event) as you do now, and if it returns
true return true, otherwise also return true (consume the event) to prevent
gameRoot?.dispatchTouchEvent(event) from running; reference swapInputOverlay,
pointerInteropFilter, gameRoot and isPortrait and keep the existing landscape
routing unchanged (or alternatively replace the portrait path with manual
routing to specific controls if you prefer coordinate-based dispatch).

}
}

val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, search for orientation locking mechanisms
rg -n "SetAllowedOrientation\|screenOrientation\|SCREEN_ORIENTATION\|setRequestedOrientation" --type=kt -C3

Repository: utkarshdalal/GameNative

Length of output: 93


🏁 Script executed:

# Also check the context around lines 647 and 676
sed -n '640,680p' app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt

Repository: utkarshdalal/GameNative

Length of output: 1977


🏁 Script executed:

# Check if there are activity orientation policies or AndroidView key recomposition
rg -n "AndroidView\|DisposableEffect\|key(" app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt | head -20

Repository: utkarshdalal/GameNative

Length of output: 187


🏁 Script executed:

# Search for orientation locking with correct syntax
rg "SetAllowedOrientation|screenOrientation|SCREEN_ORIENTATION|setRequestedOrientation" -C3

Repository: utkarshdalal/GameNative

Length of output: 7612


🏁 Script executed:

# Search for AndroidView and key usage
rg "AndroidView" -A2 -B2 | head -30

Repository: utkarshdalal/GameNative

Length of output: 3199


🏁 Script executed:

# Check the activity configuration settings
rg "android:screenOrientation" --type=xml

Repository: utkarshdalal/GameNative

Length of output: 49


🏁 Script executed:

# Look for AndroidManifest entries for this activity
fd "AndroidManifest.xml" -x grep -n "xserver\|XServerScreen" {} +

Repository: utkarshdalal/GameNative

Length of output: 136


🏁 Script executed:

# Check the setOrientationTo implementation
rg "fun setOrientationTo" -A15

Repository: utkarshdalal/GameNative

Length of output: 1616


🏁 Script executed:

# Check PrefManager.allowedOrientation
rg "allowedOrientation" -B2 -A2 | head -40

Repository: utkarshdalal/GameNative

Length of output: 2565


🏁 Script executed:

# Look for DisposableEffect or other recomposition triggers around AndroidView
rg "DisposableEffect|LaunchedEffect" app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt -B3 -A5

Repository: utkarshdalal/GameNative

Length of output: 1708


The isPortrait variable in the factory can diverge from the composable scope if device orientation changes during gameplay.

Line 647 reads LocalConfiguration.current.orientation (reactive, updates on recomposition), while line 676 captures context.resources.configuration.orientation once in the factory lambda. Since AndroidView's factory is invoked only once at creation, the view hierarchy (e.g., LinearLayout for portrait vs FrameLayout for landscape) is determined at that moment. If the device orientation changes after factory creation—which is possible since PrefManager.allowedOrientation is an EnumSet<Orientation> that can include multiple values and the app respects device rotation within allowed orientations—the pointerInteropFilter (line 660) will dispatch touch events according to the updated isPortrait value from the composable scope, but the underlying view structure built in the factory will not adapt.

To ensure the view hierarchy matches the touch dispatch logic, either wrap the AndroidView in a key(isPortrait) to force recreation on orientation change, or redesign the layout to be orientation-adaptive rather than structure-dependent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` at line
647, The factory-created view hierarchy inside AndroidView can become
out-of-sync with the composable-scoped isPortrait used by pointerInteropFilter
because the factory lambda (where context.resources.configuration.orientation is
captured) only runs once; update the code to ensure the view is recreated when
orientation changes by wrapping the AndroidView in a key(isPortrait) (or
otherwise trigger re-creation) so the factory rebuilds the correct layout, or
refactor the view construction to be orientation-adaptive; reference isPortrait,
the factory lambda that uses context.resources.configuration.orientation,
AndroidView, pointerInteropFilter, and PrefManager.allowedOrientation when
making the change.

@utkarshdalal utkarshdalal merged commit 0b55fff into master Feb 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant