Fix ripple effect ignoring component shape and extending into margins#3324
Merged
tonidero merged 3 commits intoApr 15, 2026
Conversation
Move Modifier.clickable() from callers into StackComponentView via a new interactionModifier parameter, applied after .clip(shape) so the ripple respects rounded corners and stays within component bounds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace CTA StackComponent with a ButtonComponent in the bless sample paywall to make it easier to reproduce and verify the ripple fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
|
Thank you so much for your contribution @bahdaniec! This looks good! We will get this merged and should be part of the next SDK release 👍 |
tonidero
approved these changes
Apr 15, 2026
0aa0dfc
into
RevenueCat:external/bahdaniec/fix/ripple_clipping
3 checks passed
3 tasks
matteinn
pushed a commit
to matteinn/purchases-android
that referenced
this pull request
May 5, 2026
…evenueCat#3395) ## Summary - Follow-up to RevenueCat#3324, which fixed the original ripple-vs-margin issue (RevenueCat#3321) by adding `.clip(composeShape)` on the click target so the ripple respects rounded corners. That fix uses a graphics-layer clip, which also crops descendants — stacks with nested children that intentionally extend outside the parent (badges with offsets, drop shadows, decorative elements) get visibly clipped during press. - Introduces declarative `onStackClick: (() -> Unit)?`, `enabled: Boolean`, `interactionSource: MutableInteractionSource?` parameters on `StackComponentView`, threaded through `StackWithOverlaidBadge`, `StackWithLongEdgeToEdgeBadge`, `StackWithShortEdgeToEdgeBadge`, and `MainStackComponent`. This lets `MainStackComponent` split the click gesture from the indication. - **Plain stack path:** wraps in a `Box {}` containing the stack with `clickable(indication = null)` (gesture only, no clip on the stack) and a sibling `Box(Modifier.matchParentSize().padding(margin).clip(composeShape).indication(source, LocalIndication.current))` that draws the shape-clipped ripple. Children render unclipped. - **Other render paths** (video background, nested badge, overlay) keep their existing `.clip(composeShape)` on the click target — only the click wiring is swapped over to the new `onStackClick` parameter. The overflow-clip fix therefore only applies to the plain render path; this limitation is documented in the `onStackClick` kdoc so future callers know what to expect. - Updates the three call sites (`ButtonComponentView`, `PackageComponentView`, `TabControlButtonView`) to use the new declarative API. - Adds a Compose preview (`StackComponentView_Preview_Clickable_With_Overflowing_Child_Shadow`) as a regression guard for the overflowing-child shadow case. ## Test plan - [x] `./gradlew :ui:revenuecatui:testDefaultsBc8DebugUnitTest` (full suite + Paparazzi `verify`) — green - [x] `./gradlew detektAll` — green - [x] Manual: build a paywall whose plain stack has a nested child positioned to extend outside the parent (offset, negative padding, or drop shadow); tap the parent — overflowing child must remain fully visible during the press, and the ripple must follow the parent's rounded corners 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches core Compose layout/click/semantics for paywall components, so regressions could affect tap handling, accessibility, or visual clipping across many paywalls despite being UI-only. > > **Overview** > Fixes clickable `StackComponentView` ripple rendering so the ripple remains clipped to the stack shape **without clipping overflowing children** (e.g. shadows/badges) by splitting gesture handling and indication onto separate sibling nodes. > > Introduces a new declarative click API on `StackComponentView` (`onStackClick`, `enabled`, `interactionSource`) and updates existing button/tab/package components to use it instead of applying `Modifier.clickable` externally; adds Compose previews as regression guards. The paywall-tester sample paywall is also updated to use a `ButtonComponent` for its CTA. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2471d57. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Siaržuk Bahdaniec <sweid531@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Resolves #3321
The ripple effect in Paywalls V2 ignores rounded corners and extends into the component's margin area. This happens because
Modifier.clickable()is applied beforeStackComponentViewapplies.clip(shape)and margin internally — so the ripple fills the entire rectangular area including margins.Description
Introduces an
interactionModifierparameter onStackComponentViewthat gets threaded down toMainStackComponentand applied after.clip(composeShape)in all 4 code paths (standard, video background, nested badge, overlay). This ensures the ripple is bounded by the component's actual shape.Updated all callers:
clickableintointeractionModifierclickableintointeractionModifier, removes unusedconditionalimportclickableintointeractionModifierAlso adds a
ButtonComponentto the bless sample paywall (#8) to make it easier to reproduce and verify the fix visually.Testing: All existing unit tests pass — StackComponentViewTests, StackComponentViewWindowTests, ButtonComponentViewTests, PackageComponentViewTests, TabsComponentViewTests. Verified visually via the updated sample paywall.
Note
Medium Risk
Touches the core
StackComponentViewcontainer and changes where click/interaction modifiers are applied, which could subtly affect touch targets and interaction behavior across multiple paywall components. Functionality is UI-scoped (ripple/press feedback) with no data/auth implications.Overview
Fixes Compose ripple/press feedback on paywall components so it respects rounded corners and does not extend into margins by adding an
interactionModifiertoStackComponentViewand applying it afterclip(shape)across all rendering paths (standard, video background, nested badge, overlay/badge variants).Updates callers (
ButtonComponentView,PackageComponentView,TabControlButtonView) to pass theirModifier.clickableviainteractionModifier(instead of on the outermodifier), and tweaks the paywall tester sample (#8) to use aButtonComponentto make the issue easy to reproduce/verify.Reviewed by Cursor Bugbot for commit c7368b0. Bugbot is set up for automated code reviews on this repo. Configure here.