Skip to content

fix(interactive_layer): clamp floating menu within parents smaller than the menu - #497

Closed
usman-deriv wants to merge 1 commit into
deriv-com:masterfrom
usman-deriv:fix/floating-menu-clamp-negative-bounds
Closed

fix(interactive_layer): clamp floating menu within parents smaller than the menu#497
usman-deriv wants to merge 1 commit into
deriv-com:masterfrom
usman-deriv:fix/floating-menu-clamp-negative-bounds

Conversation

@usman-deriv

@usman-deriv usman-deriv commented Sep 7, 2026

Copy link
Copy Markdown

Fixes issue: production fatal — Crashlytics 398cd50ce4d525e33e61b79fd1f640b8 (DerivApp Android 1.17.8); no GitHub issue filed.

This PR contains the following changes:

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionality to change)
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore

Root cause

SelectedDrawingFloatingMenu.onPanUpdate (lib/src/deriv_chart/interactive_layer/widgets/selected_drawing_floating_menu.dart) keeps the dragged menu inside its parent with:

final constrainedX = newPosition.dx.clamp(0.0, parentSize.width - _menuSize.width);
final constrainedY = newPosition.dy.clamp(0.0, parentSize.height - _menuSize.height);

Dart's num.clamp(lower, upper) throws ArgumentError(lower) when lower > upper. Whenever the parent Stack (the chart's interactive layer) is smaller than the menu (~144×40 logical px) on either axis, parentSize.* - _menuSize.* goes negative, the bounds invert, and every drag update throws.

Crash signature

ArgumentError: Invalid argument(s): 0.0
#0  double.clamp (dart:core-patch/double.dart)
#1  _SelectedDrawingFloatingMenuState.build.<anonymous closure> (selected_drawing_floating_menu.dart:129)

Reported as a fatal in production (Crashlytics 398cd50ce4d525e33e61b79fd1f640b8, DerivApp Android 1.17.8).

When it triggers

Any drag of the floating menu while the chart is narrower or shorter than the menu:

  • the on-screen keyboard squeezing the chart,
  • split-screen / pop-up (multi-window) view,
  • very small chart sizes.

Fix

Floor both upper bounds at zero before clamping:

final double maxX = math.max(0, parentSize.width - _menuSize.width);
final double maxY = math.max(0, parentSize.height - _menuSize.height);
final constrainedX = newPosition.dx.clamp(0.0, maxX);
final constrainedY = newPosition.dy.clamp(0.0, maxY);

When the parent is larger than the menu max(0, …) is the identity, so existing behaviour is unchanged. When it is smaller, the menu is pinned to the parent's origin instead of throwing. _menuSize == Size.zero (not yet measured) still yields the parent size as the bound, as before.

Test

New test/deriv_chart/chart/interactive_layer/selected_drawing_floating_menu_test.dart mounts the real menu (with a HorizontalLineInteractableDrawing) inside a Stack of a controlled size and pans it via tester.dragFrom:

  1. Parent smaller on both axes (100×20) — asserts tester.takeException() is null and the menu / InteractiveLayerController.floatingMenuPosition end at (0, 0).
  2. Parent smaller on one axis only (400×20) — the fitting axis still moves within [0, parent − menu], the overflowing axis stays pinned at 0.
  3. Parent larger (400×200) — a drag far past the corner still clamps to parent − menu (regression guard for the unchanged path).

On the previous code test 1 and 2 fail with exactly the production signature (Invalid argument(s): 0.0 from double.clamp via _SelectedDrawingFloatingMenuState.build.<anonymous closure>, lines 129 / 131); with the fix all three pass. Full flutter test suite: 177 passed. flutter analyze reports no issues in the changed files; dart format clean.

Pre-launch Checklist (For PR creator)

  • 👁️ I have gone through the code and removed any temporary changes (commented lines, prints, debug statements etc.).
  • ⚒️ I have fixed any errors/warnings shown by the analyzer/linter.
  • 📝 I have added documentation, comments and logging wherever required.
  • 🧪 I have added necessary tests for these changes.
  • 🔎 I have ensured all existing tests are passing.

🤖 Generated with Claude Code

Summary by Sourcery

Prevent floating drawing menu drag crashes in constrained chart layouts.

Bug Fixes:

  • Prevented floating drawing menus from throwing during drag operations when their parent is smaller than the menu by pinning them to the parent origin.

Tests:

  • Added widget coverage for menus overflowing both axes, overflowing one axis, and fitting within the parent bounds.

…an the menu

`SelectedDrawingFloatingMenu.onPanUpdate` clamps the dragged position with
`newPosition.dx.clamp(0.0, parentSize.width - _menuSize.width)` (and the
same for Y). `num.clamp(lower, upper)` throws `ArgumentError(lower)` when
`lower > upper`, so whenever the parent Stack (the chart's interactive
layer) is smaller than the ~144x40 menu on either axis — a keyboard
squeezing the chart, split-screen / pop-up view, very small charts — the
upper bound goes negative and every drag update throws
`Invalid argument(s): 0.0` from `_SelectedDrawingFloatingMenuState.build.<fn>`.
This surfaced as a fatal crash in production (Crashlytics
398cd50ce4d525e33e61b79fd1f640b8, DerivApp Android 1.17.8).

Floor both upper bounds at 0 with `math.max` before clamping. When the
parent is larger than the menu this is the identity, so behaviour is
unchanged; when it is smaller the menu is pinned to the parent's origin
instead of throwing.

Adds a widget test that mounts the menu in a Stack smaller than the menu
and drags it: it reproduces the ArgumentError on the previous code and
passes with the fix. It also covers one-axis overflow and the normal
"menu fits" clamping.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Fixes a production drag-time ArgumentError by ensuring floating-menu clamp upper bounds never fall below zero, pinning oversized menus to the parent origin while retaining normal boundary behavior; focused widget tests cover both overflow axes and the unchanged fitting case.

Sequence diagram for safe floating-menu dragging

sequenceDiagram
    participant User
    participant Menu as SelectedDrawingFloatingMenu
    participant Parent as ParentRenderBox
    participant Dart as Dart clamp
    participant Controller as InteractiveLayerController

    User->>Menu: onPanUpdate(details)
    Menu->>Parent: read size
    Menu->>Menu: math.max(0, parentSize - menuSize)
    Menu->>Dart: newPosition.dx.clamp(0.0, maxX)
    Menu->>Dart: newPosition.dy.clamp(0.0, maxY)
    alt parent smaller than menu
        Dart-->>Menu: (0, 0)
    else parent fits menu
        Dart-->>Menu: bounded position
    end
    Menu->>Controller: update floatingMenuPosition
Loading

Flow diagram for oversized floating-menu clamping

flowchart TD
    A[Drag floating menu] --> B[Read parent and menu sizes]
    B --> C["Compute maxX and maxY with math.max(0, parent minus menu)"]
    C --> D{Parent smaller on an axis?}
    D -->|Yes| E[Clamp that axis to 0]
    D -->|No| F[Clamp within normal parent bounds]
    E --> G[Update menu position without exception]
    F --> G
Loading

File-Level Changes

Change Details Files
Make floating-menu drag bounds valid when the hosting parent is smaller than the measured menu.
  • Add the math dependency and floor each calculated maximum X/Y bound at zero before calling clamp.
  • Preserve existing in-bounds clamping and pin an oversized menu to the parent origin instead of throwing.
lib/src/deriv_chart/interactive_layer/widgets/selected_drawing_floating_menu.dart
Add widget coverage for oversized-menu and regression clamping behavior.
  • Mount the real menu in controlled-size stacks and exercise dragging through the public widget-test path.
  • Verify no exception and origin pinning when both axes overflow, independent-axis behavior when one axis overflows, and unchanged bottom-right clamping when the menu fits.
test/deriv_chart/chart/interactive_layer/selected_drawing_floating_menu_test.dart

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@usman-deriv usman-deriv closed this Sep 7, 2026
@usman-deriv
usman-deriv deleted the fix/floating-menu-clamp-negative-bounds branch September 7, 2026 04:13
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