Skip to content

Fix WaveformSlider thumb and progress fill misalignment#6421

Merged
gpunto merged 2 commits into
developfrom
waveform-slider-fix
May 7, 2026
Merged

Fix WaveformSlider thumb and progress fill misalignment#6421
gpunto merged 2 commits into
developfrom
waveform-slider-fix

Conversation

@gpunto
Copy link
Copy Markdown
Contributor

@gpunto gpunto commented May 7, 2026

Goal

Fix two visual bugs in StaticWaveformSlider:

  1. The colored portion of the track did not reach the thumb. With the default
    adjustBarWidthToLimit = false and waveformData.size < visibleBarLimit, the threshold was
    scaled down by size / visibleBarLimit even though the bars span the full canvas, so only a
    fraction of bars got colored.
  2. The thumb was anchored at the start on the first frame and in static Compose previews, because
    its X offset depended on a widthPx state that was only populated after onSizeChanged
    triggered a recomposition.
  3. Added screenshot tests for the component.

Implementation

  • Replace the outer Box + onSizeChanged/mutableFloatStateOf(0f) with BoxWithConstraints,
    reading constraints.maxWidth at composition time.
  • Change the track threshold denominator from visibleBarLimit to totalBars
    so the colored region matches the rendered bar layout in all three cases (adjustBarWidthToLimit
    true/false, size greater/less than the limit).
  • Minor: drop a stale @param style kdoc entry; collapse the nested when for totalBars into
    minOf(...).

🎨 UI Changes

Issue Before After
Misaligned progress fill Screenshot 2026-05-07 at 11 20 04 Screenshot 2026-05-07 at 11 20 19
Misplaced thumb Screenshot 2026-05-07 at 11 21 53 Screenshot 2026-05-07 at 11 22 10

Testing

  • Open StaticWaveformSlider previews in the IDE; thumb should sit at the correct progress on first
    render.
  • Run the slider in the Compose sample with progress at 0 / 0.5 / 1: thumb and colored bars stay
    aligned.
  • Drag the thumb and confirm progress callbacks still fire correctly (LTR and RTL).

Summary by CodeRabbit

  • Improvements
    • Refactored audio waveform slider with enhanced layout measurement and positioning
    • Added comprehensive snapshot testing for waveform slider across multiple playback states and configurations

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled (or ignored for dependabot PRs).

🎉 Great job! This PR is ready for review.

@gpunto gpunto added the pr:bug Bug fix label May 7, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.82 MB 5.82 MB 0.00 MB 🟢
stream-chat-android-ui-components 11.02 MB 11.02 MB 0.00 MB 🟢
stream-chat-android-compose 12.37 MB 12.38 MB 0.01 MB 🟢

@gpunto gpunto force-pushed the waveform-slider-fix branch 2 times, most recently from f49a78f to 4f65595 Compare May 7, 2026 09:18
@gpunto
Copy link
Copy Markdown
Contributor Author

gpunto commented May 7, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack

Walkthrough

The WaveformSlider composable is refactored to measure available width via BoxWithConstraints instead of mutable state with onSizeChanged, eliminating state side effects. Bar count calculations are simplified, threshold logic is corrected with a zero-guard, preview implementations are expanded for multiple UI states, and new Paparazzi snapshot tests provide coverage for distinct slider configurations.

Changes

Waveform Slider Refactoring and Testing

Layer / File(s) Summary
Constraint-Based Width Measurement
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/audio/WaveformSlider.kt
StaticWaveformSlider now derives widthPx from BoxWithConstraints constraints instead of mutable state with onSizeChanged, eliminating stateful size tracking.
Bar Calculation & Threshold Logic
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/audio/WaveformSlider.kt
WaveformTrack bar count is simplified to minOf(waveformData.size, visibleBarLimit), and progress threshold now includes totalBars > 0 guard and uses visibleBars/totalBars ratio.
Preview & UI Implementations
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/audio/WaveformSlider.kt
Previews expanded with five composables for slider states (start, midway, paused, without-thumb, full-track) using ChatPreviewTheme and sample generators; color-box previews removed.
Snapshot Tests
stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/audio/WaveformSliderTest.kt
New WaveformSliderTest class with five Paparazzi snapshot tests covering distinct slider UI states in dark mode on PIXEL_2 device.
API Surface Generation
stream-chat-android-compose/api/stream-chat-android-compose.api
New singleton class ComposableSingletons$WaveformSliderKt added with INSTANCE field and generated lambda accessors supporting the refactored composable.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A slider once bound by state's weight,
Now dances with constraints—how great!
Bars whisper in harmony,
Thresholds guard safely,
And snapshots ensure it looks straight. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix WaveformSlider thumb and progress fill misalignment' accurately and concisely describes the main bugs being fixed, matching the core changes in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR description comprehensively covers goal, implementation details, UI changes with before/after screenshots, and testing instructions.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch waveform-slider-fix

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
Copy Markdown

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

Caution

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

⚠️ Outside diff range comments (1)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/audio/WaveformSlider.kt (1)

187-190: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard totalBars <= 1 to prevent Infinity/NaN geometry math

At Line 189, barSpacing = spaceWidth / totalSpaces can divide by zero when totalBars == 1 (totalSpaces == 0), and Line 188/189 can also produce invalid geometry when totalBars == 0. This can propagate into topLeft.x and break drawing.

Suggested fix
     Canvas(
         modifier = modifier.graphicsLayer { scaleX = if (isRtl) -1f else 1f },
     ) {
+        if (totalBars <= 0) return@Canvas
+
         val canvasW = size.width
         val canvasH = size.height
         val spaceWidth = canvasW * BarSpacingRatio
         val barsWidth = canvasW - spaceWidth
         val totalSpaces = totalBars - 1
         val barWidth = barsWidth / totalBars
-        val barSpacing = spaceWidth / totalSpaces
+        val barSpacing = if (totalSpaces > 0) spaceWidth / totalSpaces else 0f
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/audio/WaveformSlider.kt`
around lines 187 - 190, Guard against totalBars <= 1 in WaveformSlider.kt:
compute totalSpaces = max(0, totalBars - 1) and if totalBars <= 1 set barWidth
and barSpacing to safe defaults (e.g., barsWidth and 0 or barsWidth / 1 and 0)
so you never divide by zero; update calculations that use barWidth/barSpacing
(including any computation of topLeft.x) to use these guarded values so geometry
stays finite when totalBars is 0 or 1.
🧹 Nitpick comments (1)
stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/audio/WaveformSliderTest.kt (1)

34-57: ⚡ Quick win

Consider adding one RTL snapshot case for this suite

Given the RTL-specific rendering/progress path in the slider, one Paparazzi snapshot wrapped with RTL layout direction would help prevent regressions on that branch too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/audio/WaveformSliderTest.kt`
around lines 34 - 57, Add a Paparazzi RTL snapshot to cover the slider's
RTL-specific rendering: create a new test (e.g., fun `slider rtl progress`) that
uses the existing snapshot helper snapshotWithDarkModeRow but wraps the
composable in an RTL layout direction (either by passing a layoutDirection param
if snapshotWithDarkModeRow supports it, or by wrapping the content with
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl)
around one of the existing fixtures like StaticWaveformSliderMidway() or
FullWaveformTrack()); this new test should mirror one of the existing cases so
RTL rendering/progress is validated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/audio/WaveformSlider.kt`:
- Around line 187-190: Guard against totalBars <= 1 in WaveformSlider.kt:
compute totalSpaces = max(0, totalBars - 1) and if totalBars <= 1 set barWidth
and barSpacing to safe defaults (e.g., barsWidth and 0 or barsWidth / 1 and 0)
so you never divide by zero; update calculations that use barWidth/barSpacing
(including any computation of topLeft.x) to use these guarded values so geometry
stays finite when totalBars is 0 or 1.

---

Nitpick comments:
In
`@stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/audio/WaveformSliderTest.kt`:
- Around line 34-57: Add a Paparazzi RTL snapshot to cover the slider's
RTL-specific rendering: create a new test (e.g., fun `slider rtl progress`) that
uses the existing snapshot helper snapshotWithDarkModeRow but wraps the
composable in an RTL layout direction (either by passing a layoutDirection param
if snapshotWithDarkModeRow supports it, or by wrapping the content with
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl)
around one of the existing fixtures like StaticWaveformSliderMidway() or
FullWaveformTrack()); this new test should mirror one of the existing cases so
RTL rendering/progress is validated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 814481a3-3f3e-4040-8ffa-54b2ecfeca3b

📥 Commits

Reviewing files that changed from the base of the PR and between 75cafde and 4f65595.

⛔ Files ignored due to path filters (5)
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.audio_WaveformSliderTest_full_track.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.audio_WaveformSliderTest_slider_paused.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.audio_WaveformSliderTest_slider_playing_at_start.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.audio_WaveformSliderTest_slider_playing_midway.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.audio_WaveformSliderTest_slider_without_thumb.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • stream-chat-android-compose/api/stream-chat-android-compose.api
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/audio/WaveformSlider.kt
  • stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/audio/WaveformSliderTest.kt

@gpunto gpunto force-pushed the waveform-slider-fix branch from 4f65595 to 7ee8fe3 Compare May 7, 2026 09:48
@gpunto gpunto marked this pull request as ready for review May 7, 2026 09:53
@gpunto gpunto requested a review from a team as a code owner May 7, 2026 09:53
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 7, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
76.9% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@gpunto gpunto merged commit 2c2e9e8 into develop May 7, 2026
16 of 17 checks passed
@gpunto gpunto deleted the waveform-slider-fix branch May 7, 2026 14:22
@stream-public-bot stream-public-bot added the released Included in a release label May 22, 2026
@stream-public-bot
Copy link
Copy Markdown
Contributor

🚀 Available in v7.2.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:bug Bug fix released Included in a release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants