Skip to content

Conversation

@behnam-deriv
Copy link
Collaborator

@behnam-deriv behnam-deriv commented Feb 11, 2026

Clickup link:
Fixes issue: #

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

Developers Note (Optional)

Pre-launch Checklist (For PR creator)

As a creator of this PR:

  • ✍️ I have included clickup id and package/app_name in the PR title.
  • 👁️ 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.

Reviewers

Pre-launch Checklist (For Reviewers)

As a reviewer I ensure that:

  • ✴️ This PR follows the standard PR template.
  • 🪩 The information in this PR properly reflects the code changes.
  • 🧪 All the necessary tests for this PR's are passing.

Pre-launch Checklist (For QA)

  • 👌 It passes the acceptance criteria.

Pre-launch Checklist (For Maintainer)

  • [MAINTAINER_NAME] I make sure this PR fulfills its purpose.

Summary by Sourcery

Introduce checkpoint line markers and improved time marker rendering for multi-stage contracts, along with minor demo configuration updates.

New Features:

  • Add checkpoint line and checkpoint line collapsed marker types with a dedicated painter for visualizing intermediate contract checkpoints.
  • Allow end time and checkpoint markers to display bottom text labels instead of only icons.
  • Support per-marker reduced opacity configuration to visually distinguish future or unreached checkpoints.

Enhancements:

  • Unify start and end time line rendering to always use full-height dashed lines with opacity control and theme-based coloring.
  • Refine entry and exit spot marker visuals by separating their drawing logic for clearer state representation in tick charts.
  • Extend time marker utilities with a helper to render bottom-aligned text labels consistent with icon positioning.
  • Adjust the example app defaults to showcase rise/fall contracts with shorter marker durations and sample checkpoint markers for demonstration.

@github-actions
Copy link

github-actions bot commented Feb 11, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails

Scanned Manifest Files

@claude
Copy link

claude bot commented Feb 11, 2026

Claude finished @behnam-deriv's task —— View job


Code Review: Add checkpoint based trade type markers

I've completed a thorough review of this PR. Here are my findings:


Summary

This PR adds support for checkpoint-based markers in multi-stage contracts (e.g., Double Rise/Fall). The implementation introduces two new marker types (checkpointLine and checkpointLineCollapsed), a new painting function, and enhances existing marker utilities to support text rendering and configurable opacity.

Overall Assessment: The code is generally well-structured and follows the repository's patterns. However, there are several issues that need attention before merging.


🔴 High Priority Issues

1. Missing Test Coverage

Location: Entire PR
Issue: No tests added for the new functionality.

According to the PR checklist and repository guidelines, tests are required for new features. The new paintCheckpointLine function, the paintBottomText utility, and the new marker types lack test coverage.

Recommendation: Add unit tests covering:

  • paintCheckpointLine rendering logic
  • TimeMarkerPainters.paintBottomText with various inputs
  • New marker types in tick_marker_icon_painter.dart
  • Edge cases (null text, empty text, reduced opacity)

2. Potential Null Pointer Exception

Location: lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart:111

for (final ChartMarker marker in markerGroup.markers) {
  final Offset center = points[marker.markerType!]!;  // ⚠️ Double bang operator

Issue: Using ! after map access assumes the marker type always exists in the points map. However, MarkerType.tick markers are explicitly excluded from the map (line 96), which could cause runtime crashes.

Recommendation:

final Offset? center = points[marker.markerType];
if (center == null) continue;
// or provide a fallback calculation

3. Inconsistent Null Safety Pattern

Location: lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart:118-125

final Offset center = points[marker.markerType!] ??
    (marker.markerType == MarkerType.contractMarker
        ? Offset(...)
        : Offset(epochToX(marker.epoch), quoteToY(marker.quote)));

Issue: This code recalculates the center offset for markers not in the points map, but the logic is duplicated from lines 80-98. The duplication creates maintenance burden and potential for inconsistency.

Recommendation: Extract the offset calculation into a helper function or ensure markers are always in the map before iteration.


🟡 Medium Priority Issues

4. Incomplete Documentation

Location: lib/src/deriv_chart/chart/data_visualization/markers/chart_marker.dart:178-179

/// The [hasReducedOpacity] determines whether the marker should be rendered with reduced (0.5) opacity.

Issue: Documentation mentions 0.5 opacity, but the actual implementation uses opacity * 0.5 which could result in values lower than 0.5 depending on the base opacity.

Recommendation: Update documentation to clarify: "reduces the marker's opacity by 50% (multiplies by 0.5)".

5. Magic Numbers

Location: Multiple files

Examples:

  • paint_checkpoint_line.dart:54-61 - Padding values (10, 28) are hardcoded
  • paint_time_marker_utils.dart:93 - Container size 24, font size 14
  • tick_marker_icon_painter.dart:77-78 - Radius calculations with magic numbers

Issue: Hardcoded values make it difficult to maintain consistency and adjust styling.

Recommendation: Extract magic numbers to named constants or design tokens, especially values that appear in multiple places.

6. Code Duplication in Marker Line Painters

Location: paint_start_line.dart, paint_end_line.dart, paint_checkpoint_line.dart

Issue: All three files have nearly identical logic for determining marker color:

final Color markerColor = marker.color ??
    (marker.direction == MarkerDirection.up
        ? theme.markerStyle.upColorProminent
        : theme.markerStyle.downColorProminent);

Recommendation: Extract to a shared utility function: getMarkerLineColor(ChartMarker marker, ChartTheme theme).

7. Inconsistent Text Rendering Approach

Location: paint_time_marker_utils.dart:84-114

Issue: paintBottomText manually calculates text offset instead of using the existing paintWithTextPainter utility (which handles alignment properly).

Recommendation:

static void paintBottomText(Canvas canvas, Size size, double x, String text, double zoom, Color color) {
  final TextPainter textPainter = TextPainter(
    text: TextSpan(
      text: text,
      style: TextStyle(color: color, fontSize: 14 * zoom, fontWeight: FontWeight.w400),
    ),
    textDirection: TextDirection.ltr,
  )..layout();

  paintWithTextPainter(
    canvas,
    painter: textPainter,
    anchor: Offset(x, size.height - 24 * zoom / 2),
    anchorAlignment: Alignment.center,
  );
}

🟢 Low Priority / Style Issues

8. Unused Parameter

Location: paint_start_line.dart:26, paint_checkpoint_line.dart:28

/// The [marker] is the chart marker (kept for signature parity with other line painters).

Issue: The marker parameter is used (for color/direction), so the comment is misleading.

Recommendation: Update documentation to reflect actual usage.

9. Export Missing from Main Library

Location: lib/deriv_chart.dart:1

Issue: The new paint_checkpoint_line.dart file is not exported from the main library file, though this might be intentional if it's only used internally.

Recommendation: Verify if this should be exported for external use, or document that it's internal-only.

10. PR Description Missing

Location: PR description

Issue: The PR body is almost entirely empty. No description of what checkpoint markers are, how they work, or what problem they solve.

Recommendation: Add:

  • Clear description of the feature
  • Use case examples (Double Rise/Fall contracts)
  • Screenshots/visual examples if available
  • Breaking changes (if any)

11. Formatting Consistency

Location: digit_marker_icon_painter.dart:190-222

Issue: The _drawTick method signature wraps parameters differently than other methods in the file.

Recommendation: Run dart format . to ensure consistent formatting across all files.


✅ Positive Observations

  1. Good separation of concerns - New checkpoint painting logic is properly separated into its own file
  2. Consistent naming - New marker types follow existing naming conventions
  3. Comprehensive documentation - Dartdoc comments are thorough and helpful
  4. Reuses existing utilities - Leverages TimeMarkerPainters.paintVerticalTimeLine appropriately
  5. Proper immutability - New properties follow the immutability pattern used in the codebase
  6. Example app updated - Good that the example was updated to demonstrate new functionality

Recommendations

Before Merging:

  1. MUST: Fix null safety issues (High Priority Ramin/animate_to_current_tick #2, Rustem/crosshair #3)
  2. MUST: Add test coverage for new functionality
  3. SHOULD: Run flutter analyze --no-fatal-infos and fix any warnings
  4. SHOULD: Run dart format . to ensure consistent formatting
  5. SHOULD: Fill out PR description with feature details
  6. SHOULD: Address code duplication (Medium Priority Morteza/add_ci_config #6)

Post-Merge (Technical Debt):

  • Extract magic numbers to design tokens
  • Refactor shared marker color logic into utilities

Security & Performance

Security: No security concerns identified
Performance: No performance regressions expected - follows existing painting patterns
Breaking Changes: None - additive changes only


Let me know if you'd like me to implement any of these fixes!

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 11, 2026

Reviewer's Guide

Implements checkpoint-based trade type markers for multi-stage contracts by introducing new marker types and painters, updating tick/digit marker painters to support opacity and checkpoint rendering, extending time marker utilities for bottom text labels, and wiring an example showcasing checkpoint lines and exit spots.

Sequence diagram for rendering checkpoint line markers

sequenceDiagram
  actor User
  participant FullscreenChartState
  participant Chart as DerivChart
  participant TickMarkerIconPainter
  participant paintCheckpointLine
  participant TimeMarkerPainters

  User->>FullscreenChartState: selectRiseFallTradeType()
  FullscreenChartState->>FullscreenChartState: create ChartMarker entrySpot
  FullscreenChartState->>FullscreenChartState: create ChartMarker checkpointLine(text=1)
  FullscreenChartState->>FullscreenChartState: create ChartMarker checkpointLineCollapsed
  FullscreenChartState->>FullscreenChartState: create ChartMarker exitSpot
  FullscreenChartState->>FullscreenChartState: create ChartMarker exitTime(text=2)
  FullscreenChartState->>Chart: updateMarkers(List<ChartMarker>)

  Chart->>TickMarkerIconPainter: paintMarkerGroup(canvas,size,theme,markerGroup,...)
  TickMarkerIconPainter->>TickMarkerIconPainter: computeOpacity(marker.hasReducedOpacity)
  TickMarkerIconPainter->>TickMarkerIconPainter: _drawMarker(...,markerType=checkpointLine,...)
  TickMarkerIconPainter->>paintCheckpointLine: paintCheckpointLine(canvas,size,marker,anchor,style,theme,zoom,opacity,props)

  paintCheckpointLine->>TimeMarkerPainters: paintVerticalTimeLine(canvas,size,x,color,dashed=true,...)
  TimeMarkerPainters-->>paintCheckpointLine: vertical dashed line drawn

  alt marker.text not empty
    paintCheckpointLine->>TimeMarkerPainters: paintBottomText(canvas,size,x,text,zoom,color)
    TimeMarkerPainters-->>paintCheckpointLine: bottom text drawn
  end

  paintCheckpointLine-->>TickMarkerIconPainter: checkpoint line rendered
  TickMarkerIconPainter-->>Chart: marker group rendered
  Chart-->>User: chart displays checkpoint-based markers
Loading

Class diagram for checkpoint-based trade type markers

classDiagram
  class Marker {
    <<abstract>>
    int epoch
    double quote
    MarkerDirection direction
    void onTap()
  }

  class ChartMarker {
    int epoch
    double quote
    MarkerDirection direction
    MarkerType markerType
    String text
    Color color
    bool hasReducedOpacity
  }

  Marker <|-- ChartMarker

  class MarkerType {
    <<enumeration>>
    startTime
    start
    entry
    entrySpot
    exitSpot
    exit
    end
    exitTime
    startTimeCollapsed
    exitTimeCollapsed
    profitAndLossLabel
    profitAndLossLabelFixed
    checkpointLine
    checkpointLineCollapsed
  }

  class MarkerGroupIconPainter {
    <<abstract>>
    +void paintMarkerGroup(Canvas canvas,Size size,ChartTheme theme,MarkerGroup markerGroup,double Function(int epoch) epochToX,double Function(double quote) quoteToY,MarkerPainterProps painterProps,AnimationInfo animationInfo)
  }

  class TickMarkerIconPainter {
    +void paintMarkerGroup(Canvas canvas,Size size,ChartTheme theme,MarkerGroup markerGroup,double Function(int epoch) epochToX,double Function(double quote) quoteToY,MarkerPainterProps painterProps,AnimationInfo animationInfo)
    -void _drawMarker(Canvas canvas,Size size,ChartTheme theme,ChartMarker marker,Offset anchor,MarkerStyle style,double zoom,int granularity,double opacity,Paint paint,AnimationInfo animationInfo,String markerGroupId,MarkerGroup markerGroup)
    -void _drawEntrySpot(Canvas canvas,ChartMarker marker,Offset anchor,MarkerStyle style,ChartTheme theme,double zoom,double opacity)
    -void _drawExitSpot(Canvas canvas,ChartMarker marker,Offset anchor,MarkerStyle style,ChartTheme theme,double zoom,double opacity)
  }

  class DigitMarkerIconPainter {
    +int pipSize
    +DigitMarkerIconPainter(int pipSize)
    +void paintMarkerGroup(Canvas canvas,Size size,ChartTheme theme,MarkerGroup markerGroup,double Function(int epoch) epochToX,double Function(double quote) quoteToY,MarkerPainterProps painterProps,AnimationInfo animationInfo)
    -void _drawMarker(Canvas canvas,Size size,ChartTheme theme,Marker marker,Offset anchor,MarkerStyle style,double zoom,double opacity,MarkerProps props)
  }

  MarkerGroupIconPainter <|-- TickMarkerIconPainter
  MarkerGroupIconPainter <|-- DigitMarkerIconPainter

  class TimeMarkerPainters {
    <<static>>
    +void paintVerticalTimeLine(Canvas canvas,Size size,double x,Color color,bool dashed,double strokeWidth,double dashWidth,double dashSpace)
    +void paintBottomIcon(Canvas canvas,Size size,double x,IconData icon,double zoom,Color color)
    +void paintBottomText(Canvas canvas,Size size,double x,String text,double zoom,Color color)
  }

  class paintStartLine {
    +void paintStartLine(Canvas canvas,Size size,ChartMarker marker,Offset anchor,MarkerStyle style,ChartTheme theme,double zoom,double opacity,MarkerProps props)
  }

  class paintEndLine {
    +void paintEndLine(Canvas canvas,Size size,ChartMarker marker,Offset anchor,MarkerStyle style,ChartTheme theme,double zoom,double opacity,MarkerProps props)
  }

  class paintCheckpointLine {
    +void paintCheckpointLine(Canvas canvas,Size size,ChartMarker marker,Offset anchor,MarkerStyle style,ChartTheme theme,double zoom,double opacity,MarkerProps props)
  }

  TickMarkerIconPainter ..> ChartMarker
  TickMarkerIconPainter ..> MarkerType
  TickMarkerIconPainter ..> paintStartLine
  TickMarkerIconPainter ..> paintEndLine
  TickMarkerIconPainter ..> paintCheckpointLine
  DigitMarkerIconPainter ..> paintStartLine

  paintStartLine ..> TimeMarkerPainters
  paintEndLine ..> TimeMarkerPainters
  paintCheckpointLine ..> TimeMarkerPainters

  class FullscreenChartState {
    -TradeType _currentTradeType
    -int _markerDurationMs
    -void _addCheckpointExampleMarkers()
  }

  FullscreenChartState ..> ChartMarker
  FullscreenChartState ..> MarkerType
Loading

File-Level Changes

Change Details Files
Extend tick marker painter to support checkpoint markers, per-marker opacity, and distinct entry/exit spot rendering.
  • Import and use new checkpoint line painter within tick marker rendering.
  • Apply reduced opacity for markers flagged with hasReducedOpacity when painting.
  • Route startTime and exitTime markers through updated start/end line painters that now accept opacity and support text rendering.
  • Differentiate entry and exit spot drawing via dedicated _drawEntrySpot and _drawExitSpot helpers, with exit spots rendered as solid colored circles suitable for checkpoint outcomes.
  • Handle new checkpointLine and checkpointLineCollapsed marker types in the marker dispatch, including collapsed timeline rendering.
lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/tick_marker_icon_painter.dart
Align digit marker painter with new start line API.
  • Update _drawMarker switch to call paintStartLine with the new opacity parameter while preserving existing behavior for digit contracts.
  • Refresh documentation comments to use Dartdoc-style parameter descriptions.
lib/src/deriv_chart/chart/data_visualization/markers/marker_icon_painters/digit_marker_icon_painter.dart
Enhance start and end time line painters and utilities to support opacity and text labels.
  • Change paintStartLine signature to accept ChartTheme, opacity, and MarkerProps while standardizing color computation and always drawing dashed vertical lines with a start icon at the bottom.
  • Change paintEndLine to always draw a dashed time line using opacity-adjusted color and to render either bottom text (when marker.text is present) or the end icon.
  • Refactor TimeMarkerPainters.paintVerticalTimeLine to compute start/end Y positions once and reuse them for dashed and solid variants.
  • Introduce TimeMarkerPainters.paintBottomText to render zoom-scaled labels at the bottom of the chart aligned with time markers.
lib/src/deriv_chart/chart/helpers/paint_functions/paint_start_line.dart
lib/src/deriv_chart/chart/helpers/paint_functions/paint_end_line.dart
lib/src/deriv_chart/chart/helpers/paint_functions/paint_time_marker_utils.dart
Introduce checkpoint marker types and properties to ChartMarker and expose checkpoint painter.
  • Add MarkerType.checkpointLine and MarkerType.checkpointLineCollapsed enums for full and collapsed checkpoint time markers.
  • Extend ChartMarker with an optional hasReducedOpacity flag to drive per-marker opacity decisions in painters.
  • Export the new checkpoint line painter from the package entrypoint so consumers can use it externally if needed.
lib/src/deriv_chart/chart/data_visualization/markers/chart_marker.dart
lib/deriv_chart.dart
Add dedicated checkpoint line painter for intermediate contract stages.
  • Create paintCheckpointLine helper to draw a dashed vertical checkpoint time line using direction- or color-based styling and applied opacity.
  • Render optional bottom text for checkpoint labels (e.g., "1", "2") via TimeMarkerPainters.paintBottomText without any icon at the base.
  • Reuse shared time-marker utilities and styling conventions for consistency with start/end line painters.
lib/src/deriv_chart/chart/helpers/paint_functions/paint_checkpoint_line.dart
Update example app to demonstrate checkpoint markers and adjust defaults.
  • Change the default trade type and persisted preference from multipliers to riseFall to better showcase tick/checkpoint behavior.
  • Shorten the demo marker duration to 5 seconds to make checkpoint visualization more responsive.
  • Insert additional ChartMarkers in the sample contract to represent a checkpoint line (with label "1" and collapsed variant) midway between start and end, and label the exit time marker as "2" to illustrate sequencing.
example/lib/main.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

Copy link
Contributor

@sourcery-ai sourcery-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.

Hey - I've found 1 issue, and left some high level feedback:

  • In TickMarkerIconPainter.paintMarkerGroup you now pass a hard-coded 1.2 zoom into _drawMarker instead of painterProps.zoom; consider either using the real zoom value or explaining why markers should be scaled independently so marker sizing stays consistent with chart zoom.
  • The new ChartMarker.hasReducedOpacity field is documented as applying to vertical line markers, but the opacity reduction is applied to all markers in TickMarkerIconPainter.paintMarkerGroup; consider either restricting its use to the intended marker types or updating the documentation to reflect the actual behavior.
  • In paintEndLine you now always pass dashed: true to paintVerticalTimeLine and no longer use props.isRunning, which changes finished contracts from solid to dashed end lines; if that visual distinction is still desired, consider keeping the dashed/solid decision based on props.isRunning.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `TickMarkerIconPainter.paintMarkerGroup` you now pass a hard-coded `1.2` zoom into `_drawMarker` instead of `painterProps.zoom`; consider either using the real zoom value or explaining why markers should be scaled independently so marker sizing stays consistent with chart zoom.
- The new `ChartMarker.hasReducedOpacity` field is documented as applying to vertical line markers, but the opacity reduction is applied to all markers in `TickMarkerIconPainter.paintMarkerGroup`; consider either restricting its use to the intended marker types or updating the documentation to reflect the actual behavior.
- In `paintEndLine` you now always pass `dashed: true` to `paintVerticalTimeLine` and no longer use `props.isRunning`, which changes finished contracts from solid to dashed end lines; if that visual distinction is still desired, consider keeping the dashed/solid decision based on `props.isRunning`.

## Individual Comments

### Comment 1
<location> `lib/src/deriv_chart/chart/data_visualization/markers/chart_marker.dart:217-223` </location>
<code_context>
   /// If null, uses [style.upColor] for [MarkerDirection.up] or [style.downColor] for [MarkerDirection.down].
   final Color? color;
+
+  /// Whether vertical line markers should be rendered with reduced (0.5) opacity.
+  ///
+  /// When set to true, marker will be rendered at half opacity. This is useful for indicating markers that represent
+  /// future or unreached checkpoints in multi-stage contracts.
+  ///
+  /// Defaults to false (full opacity).
+  final bool hasReducedOpacity;
 }
</code_context>

<issue_to_address>
**suggestion:** The hasReducedOpacity field is documented as vertical-line-specific but is applied to all markers.

`hasReducedOpacity` currently adjusts `markerOpacity` for all marker types in `paintMarkerGroup`, but its docstring describes it as vertical-line-specific. Please either generalize the wording to match the current behavior, or constrain its usage so only vertical line markers use the reduced opacity, so the flag’s intent is unambiguous.

```suggestion
  /// Whether this marker should be rendered with reduced (0.5) opacity.
  ///
  /// When set to true, the marker will be rendered at half opacity. This is useful for indicating markers that represent
  /// future or unreached checkpoints in multi-stage contracts, but can be used for any marker type.
  ///
  /// Defaults to false (full opacity).
  final bool hasReducedOpacity;
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@claude
Copy link

claude bot commented Feb 11, 2026

Claude finished @behnam-deriv's task —— View job


Code Review: Checkpoint-Based Trade Type Markers

I've completed a comprehensive review of this PR. The implementation adds checkpoint marker support for multi-stage contracts with solid architecture, but there are several critical issues that must be addressed before merging.


Summary

This PR introduces checkpoint-based markers for multi-stage contracts (e.g., Double Rise/Fall), adding two new marker types (checkpointLine and checkpointLineCollapsed), a dedicated painter, and enhanced marker utilities for text rendering and configurable opacity. The implementation follows the repository's patterns well, but has critical null safety issues and missing test coverage.

Overall Assessment: ⚠️ Changes Requested - Critical issues must be fixed before merging.


🔴 Critical Issues (Must Fix)

1. Null Pointer Exception Risk in TickMarkerIconPainter

Location: tick_marker_icon_painter.dart:118

final Offset center = points[marker.markerType!] ??
    (marker.markerType == MarkerType.contractMarker
        ? Offset(...)
        : Offset(epochToX(marker.epoch), quoteToY(marker.quote)));

Issue: This code uses the ?? operator assuming that points[marker.markerType!] will gracefully return null if the key doesn't exist. However, MarkerType.tick markers are explicitly excluded from the points map (line 96), yet this code is iterating over ALL markers including ticks. This creates a runtime crash risk.

Why this is critical: If a tick marker somehow gets processed here, accessing marker.markerType! with the double-bang operator will throw if markerType is null, and accessing points[marker.markerType!] when the type isn't in the map will cause a null pointer exception.

Recommendation:

final Offset? centerFromMap = marker.markerType != null ? points[marker.markerType!] : null;
final Offset center = centerFromMap ??
    (marker.markerType == MarkerType.contractMarker
        ? Offset(
            markerGroup.props.contractMarkerLeftPadding + _contractOuterRadius,
            quoteToY(marker.quote),
          )
        : Offset(epochToX(marker.epoch), quoteToY(marker.quote)));

2. Missing Test Coverage

Location: Entire PR

Issue: According to the PR checklist and CLAUDE.md requirements, tests are mandatory for new features. No tests were added for:

  • paintCheckpointLine rendering logic
  • TimeMarkerPainters.paintBottomText utility
  • New marker types (checkpointLine, checkpointLineCollapsed)
  • hasReducedOpacity functionality
  • Edge cases (null text, empty text, reduced opacity interactions)

Recommendation: Add unit tests covering:

// Example test structure needed:
testWidgets('paintCheckpointLine renders with text', (WidgetTester tester) async {
  // Test checkpoint line with text label
});

testWidgets('paintCheckpointLine renders without text', (WidgetTester tester) async {
  // Test checkpoint line without text
});

test('ChartMarker hasReducedOpacity applies 50% opacity', () {
  // Test opacity calculation
});

3. Hard-coded Zoom Value Inconsistency

Location: tick_marker_icon_painter.dart:143

_drawMarker(
    canvas,
    size,
    theme,
    marker,
    center,
    markerGroup.style,
    1.2,  // ⚠️ Hard-coded zoom instead of painterProps.zoom
    painterProps.granularity,
    markerOpacity,
    // ...

Issue: Previous review by Sourcery pointed this out - the zoom value is hard-coded to 1.2 instead of using painterProps.zoom. This was intentional for the contract marker (line 320), but now it applies to ALL markers including the new checkpoint markers.

Why this matters: Checkpoint markers won't scale properly with chart zoom level, creating inconsistent sizing.

Recommendation: Either:

  1. Use painterProps.zoom for most markers and only override for contractMarker
  2. Document why 1.2 is used for all markers and explain the design decision

🟡 High Priority Issues (Should Fix)

4. Documentation Mismatch on hasReducedOpacity

Location: chart_marker.dart:217-223

Issue: Both previous reviewers (Claude and Sourcery) identified this. The documentation states:

/// Whether marker should be rendered with reduced opacity by 50%.

But the actual behavior in tick_marker_icon_painter.dart:134 is:

final double markerOpacity = marker.hasReducedOpacity ? opacity * 0.5 : opacity;

This multiplies the existing opacity by 0.5, which could result in values lower than 0.5 (e.g., if base opacity is 0.6, result is 0.3, not 0.5).

Recommendation:

/// Whether marker should be rendered with reduced opacity.
///
/// When set to true, the marker's opacity is multiplied by 0.5 (reduced by 50%).
/// This is useful for indicating markers that represent future or unreached 
/// checkpoints in multi-stage contracts.
///
/// Defaults to false (full opacity).
final bool hasReducedOpacity;

5. Removed props.isRunning Check in paintEndLine

Location: paint_end_line.dart:52-58

Issue: Sourcery's review noted that paintEndLine now always passes dashed: true, removing the previous behavior where running vs. finished contracts had different line styles (solid vs. dashed).

Previous code:

dashed: !props.isRunning  // Solid when running, dashed when finished

New code:

dashed: true  // Always dashed

Why this matters: This removes visual feedback distinguishing active from completed contracts. If this was intentional, it should be documented. If not, it's a regression.

Recommendation: If the change is intentional, add a comment explaining why. If not, restore the props.isRunning logic.

6. Code Duplication in Marker Color Logic

Location: paint_start_line.dart:44-47, paint_end_line.dart:43-47, paint_checkpoint_line.dart:47-50

Issue: All three files have identical color determination logic:

final Color markerColor = marker.color ??
    (marker.direction == MarkerDirection.up
        ? theme.markerStyle.upColorProminent
        : theme.markerStyle.downColorProminent);

Recommendation: Extract to a shared utility:

// In paint_time_marker_utils.dart
static Color getTimeMarkerColor(ChartMarker marker, ChartTheme theme) {
  return marker.color ??
      (marker.direction == MarkerDirection.up
          ? theme.markerStyle.upColorProminent
          : theme.markerStyle.downColorProminent);
}

🟢 Medium Priority Issues (Nice to Fix)

7. Magic Numbers Should Be Design Tokens

Location: Multiple files

Examples:

  • paint_checkpoint_line.dart:54-61 - Padding values (10, 28)
  • paint_time_marker_utils.dart:93 - Container size 24, font size 14
  • tick_marker_icon_painter.dart:77-78 - Radius calculations

Issue: Per CLAUDE.md, design tokens should be used for consistent styling values.

Recommendation: Move these to design tokens or at minimum extract as named constants:

static const double _bottomPadding = 28.0;
static const double _topPadding = 10.0;
static const double _iconSize = 24.0;
static const double _textFontSize = 14.0;

8. Text Rendering Approach Inconsistency

Location: paint_time_marker_utils.dart:84-109

Issue: paintBottomText manually calculates text offset instead of using the existing paintWithTextPainter utility (which handles alignment properly, as seen in paintBottomIcon).

Current approach:

paintWithTextPainter(
  canvas,
  painter: textPainter,
  anchor: Offset(x, size.height - 24 * zoom / 2),  // Manual offset calculation
);

Recommendation:

paintWithTextPainter(
  canvas,
  painter: textPainter,
  anchor: Offset(x, size.height),
  anchorAlignment: Alignment.bottomCenter,  // Let the utility handle alignment
);

9. Misleading Comment in paint_start_line.dart and paint_checkpoint_line.dart

Location: paint_start_line.dart:26, paint_checkpoint_line.dart:28

Issue: Comments state:

/// The [marker] is the chart marker (kept for signature parity with other line painters).

But the marker parameter IS actively used (for color/direction), so this comment is misleading.

Recommendation: Update or remove the misleading comment.


🔵 Low Priority / Style Issues

10. Incomplete PR Description

Location: PR description

Issue: The PR body is almost entirely empty (just checkbox templates). Per CLAUDE.md contribution guidelines, PRs should include:

  • Clear description of the feature
  • Use case examples
  • Breaking changes (if any)

Recommendation: Add:

## Description
This PR adds checkpoint-based marker support for multi-stage contracts like Double Rise/Fall, 
where intermediate evaluation points need to be visualized on the chart timeline.

### Key Changes
- New marker types: `checkpointLine` and `checkpointLineCollapsed`
- New painting function: `paintCheckpointLine`
- Enhanced marker utilities to support text rendering
- Added `hasReducedOpacity` flag for future/unreached checkpoints

### Use Case
In Double Rise/Fall contracts, traders need to see multiple checkpoint markers along the 
contract duration, each showing whether that checkpoint passed or failed.

### Visual Example
[Include screenshot if available]

11. Export Missing from Main Library (Clarification Needed)

Location: lib/deriv_chart.dart

Issue: The new paint_checkpoint_line.dart file is exported, which is good. However, verify if this should be public API or internal-only. If internal, consider marking with underscore prefix.


✅ Positive Observations

  1. Clean separation of concerns - New checkpoint painting logic properly separated into its own file
  2. Consistent naming conventions - New marker types follow existing patterns (checkpointLine, checkpointLineCollapsed)
  3. Comprehensive documentation - Dartdoc comments are thorough and helpful
  4. Reuses existing utilities - Leverages TimeMarkerPainters.paintVerticalTimeLine appropriately
  5. Proper immutability - New properties follow the immutability pattern
  6. Example app updated - Good demonstration of new functionality
  7. Architecture alignment - Follows the documented architecture in CLAUDE.md

Security & Performance

Security: No security concerns identified
Performance: No performance regressions expected - follows existing painting patterns efficiently
Breaking Changes: None - all changes are additive


Pre-Merge Checklist

Must Complete Before Merging:

Recommended (Can Be Post-Merge):


Recommendations Summary

For immediate merge readiness:

  1. Fix the null safety issue in TickMarkerIconPainter.paintMarkerGroup (lines 118-125)
  2. Add comprehensive unit tests for all new functionality
  3. Clarify the zoom value usage and document if intentional
  4. Update documentation for hasReducedOpacity to reflect actual behavior
  5. Document the paintEndLine behavioral change or restore previous logic

For code quality:

  • Extract duplicate marker color logic
  • Convert magic numbers to named constants or design tokens
  • Update misleading comments
  • Complete PR description

The implementation is architecturally sound and follows the repository patterns well. Once the critical null safety and testing issues are addressed, this will be a solid addition to the library.


@behnam-deriv
Copy link
Collaborator Author

@claude Null Pointer Exception is not applicable to changes on this PR.

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