Skip to content

Pre-warm image cache for workflow step states#3421

Closed
vegaro wants to merge 1 commit into
mainfrom
cesar/wfl-46-workflow-image-preloader
Closed

Pre-warm image cache for workflow step states#3421
vegaro wants to merge 1 commit into
mainfrom
cesar/wfl-46-workflow-image-preloader

Conversation

@vegaro

@vegaro vegaro commented Apr 30, 2026

Copy link
Copy Markdown
Member

Motivation

When the two-surface workflow slide animation (#3418) starts a transition, the incoming step's images may not yet be in Coil's cache, causing a visible pop-in mid-slide. Pre-warming images for every workflow step at composition time lets transitions start with warm caches.

Description

WorkflowImagePreloader walks each step's component tree (background, stack, header, sticky footer — plus buttons, carousels, tabs, timelines, countdowns, icons, images, and video fallback frames) and enqueues every unique image URL into Coil's image loader. Light, dark, and low-resolution variants are all submitted up front. A remembered set guards against duplicate enqueues across recompositions.

The composable is invoked from LoadedWorkflowPaywall once the step states are ready; it returns no UI, only side-effecting cache warming.


Note

Medium Risk
Moderate risk: this adds background image preloading across workflow steps, which can increase network/memory usage and affect performance if URL enumeration or enqueueing is incorrect.

Overview
Preloads workflow paywall images into Coil ahead of time to prevent image pop-in during step-to-step slide transitions.

This introduces WorkflowImageUrls.preloadImageUrls() to walk a components paywall tree (backgrounds, stacks, headers/footers, and nested component styles) and extract light/dark/low-res image URLs, then updates PaywallViewModelImpl to cache step states via a new cacheStepState that deduplicates and enqueues those URLs. InternalPaywall now supplies an enqueueImage function (backed by Purchases.getImageLoaderTyped + ImageRequest) through PaywallViewModelFactory into the view model.

Reviewed by Cursor Bugbot for commit 10681c4. Bugbot is set up for automated code reviews on this repo. Configure here.

vegaro commented Apr 30, 2026

Copy link
Copy Markdown
Member Author

@emerge-tools

emerge-tools Bot commented Apr 30, 2026

Copy link
Copy Markdown

📸 Snapshot Test

591 unchanged

Name Added Removed Modified Renamed Unchanged Errored Approval
TestPurchasesUIAndroidCompatibility Paparazzi
com.revenuecat.testpurchasesuiandroidcompatibility.paparazzi
0 0 0 0 257 0 N/A
TestPurchasesUIAndroidCompatibility
com.revenuecat.testpurchasesuiandroidcompatibility
0 0 0 0 334 0 N/A

🛸 Powered by Emerge Tools

@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch from ebc6b1f to d71ea2c Compare April 30, 2026 11:01
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch from 686d62d to c9ac67b Compare April 30, 2026 11:01
@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch from d71ea2c to 2e368bc Compare April 30, 2026 11:31
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch from c9ac67b to 2edc1e7 Compare April 30, 2026 11:31
@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch from 2e368bc to be83bfc Compare April 30, 2026 12:09
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch from 2edc1e7 to 0d10d7d Compare April 30, 2026 12:09
@vegaro vegaro marked this pull request as ready for review April 30, 2026 12:19
@vegaro vegaro requested review from a team and facumenzella April 30, 2026 12:19
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch from 0d10d7d to e4bf0f4 Compare April 30, 2026 12:30
@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch from be83bfc to 997ae23 Compare April 30, 2026 12:30
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch from e4bf0f4 to 6f01bee Compare April 30, 2026 13:09
@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch from 997ae23 to b6e19c5 Compare April 30, 2026 13:14
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch 2 times, most recently from 89f4c43 to d5cedd9 Compare April 30, 2026 13:16
@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch from b6e19c5 to d453daf Compare April 30, 2026 13:16
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch from d5cedd9 to 5ac33ae Compare April 30, 2026 13:21
@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch 2 times, most recently from 0b89ebd to fbd4086 Compare April 30, 2026 13:36
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch from 5ac33ae to 8024cbd Compare April 30, 2026 13:36
@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch from fbd4086 to bc089b7 Compare April 30, 2026 14:00
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch 2 times, most recently from 9908eb5 to e68371c Compare April 30, 2026 14:14
@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch 2 times, most recently from bf8a5e4 to ae335ff Compare April 30, 2026 14:32
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch 4 times, most recently from 431e314 to cfe2af3 Compare May 4, 2026 11:07
@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch 2 times, most recently from c6054fe to b2695a9 Compare May 4, 2026 12:40
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch 2 times, most recently from 54c6173 to 2fcc54d Compare May 4, 2026 13:07
@vegaro vegaro force-pushed the cesar/wfl-46-two-surface-workflow-slide branch from 3112a77 to f63f313 Compare May 4, 2026 13:22
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch from 2fcc54d to 297d4af Compare May 4, 2026 13:22
@vegaro vegaro changed the title perf: pre-warm Coil image cache for workflow step states Pre-warm image cache for workflow step states May 4, 2026

@facumenzella facumenzella left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that warming every single component is definitely not ideal. We should do this, but smarter.

I'd cap to 1–2 steps ahead instead of the full workflow to avoid memory pressure issues.

@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch 2 times, most recently from 431642b to 31b4d2c Compare May 4, 2026 15:37
Base automatically changed from cesar/wfl-46-two-surface-workflow-slide to main May 4, 2026 16:17
Walks every step's component tree (background, stack, header, sticky
footer, including buttons/carousels/tabs/timelines/etc.) and enqueues
each unique image URL into Coil's image loader. Light + dark + low-res
variants are all submitted up front, with a remembered set guarding
against duplicate enqueues across recompositions.

Sits on top of the two-surface workflow slide animation so that when a
transition starts, the incoming surface's images are already warm in
Coil's cache and don't pop in mid-slide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vegaro vegaro force-pushed the cesar/wfl-46-workflow-image-preloader branch from 31b4d2c to 10681c4 Compare May 4, 2026 16:45
@codecov

codecov Bot commented May 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.45%. Comparing base (8383c70) to head (10681c4).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3421   +/-   ##
=======================================
  Coverage   79.45%   79.45%           
=======================================
  Files         362      362           
  Lines       14539    14539           
  Branches     1976     1976           
=======================================
  Hits        11552    11552           
  Misses       2190     2190           
  Partials      797      797           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

matteinn pushed a commit to matteinn/purchases-android that referenced this pull request May 5, 2026
### Motivation

A workflow paywall pre-rendering approach was retired in favor of a
two-surface slide model. Holding every step in the slot table at once
was wasteful (most are off-screen) and the prior animation needed
`snapTo()` + `withFrameNanos()` workarounds to avoid first-frame
flashes.

### Description

Only the current step and the outgoing/incoming step are held in the
slot table during a transition; all other steps are dropped.

Key design:

- `WorkflowPaywallUiState` carries a `pendingTransition` (`fromStepId`,
`direction`, monotonic `id`) set atomically with `currentStepId` in the
ViewModel. The first recomposition after navigation already knows both
surfaces and their initial positions.
- `key(pendingTransition.id)` creates a fresh `Animatable(0f)` during
the composition phase, so frame N's draw immediately sees the correct
offscreen position — no `snapTo()` or `withFrameNanos()` needed.
- `LaunchedEffect` only drives `animateTo(1f)`; it no longer sets up
state.
- `onTransitionComplete(id)` runs after the animation finishes so the
ViewModel can clear `pendingTransition`; a guard on `id` prevents stale
callbacks from clobbering a newer transition.
- Header pinning logic (hero/non-hero, backward) is simplified by
removing the `seenStepId` gap-detection; `pendingTransition` covers both
the "before animation starts" and "animating" cases with the same
branch.
- `navigationDirection` removed from the `PaywallViewModel` interface;
direction lives exclusively in `WorkflowPendingTransition`.

Image cache pre-warming for the off-screen steps is split into a
follow-up PR (RevenueCat#3421) so this PR stays focused on the surface model and
animation.

### Checklist

- [x] Unit tests added (`LoadedWorkflowPaywallHeaderSelectionTest`)
- [ ] If applicable, create follow-up issues for `purchases-ios` and
hybrids

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Introduces new workflow-specific rendering and animation state for
Components paywalls, which can affect navigation, touch handling, and
header/hero layout during transitions. Risk is mostly UI/UX regressions
(flashes, clipping, wrong header selection) rather than data/security
concerns.
> 
> **Overview**
> Adds a dedicated workflow paywall renderer that uses a two-surface
slide transition: `InternalPaywall` now renders `LoadedWorkflowPaywall`
when `workflowState` is present, otherwise it falls back to
`LoadedPaywallComponents`.
> 
> Implements `WorkflowSlideState` to drive horizontal slide animations
keyed by `pendingTransition.id` (keeping only the current +
outgoing/incoming steps in composition) and adds header-selection logic
to keep the correct header visible during hero/non-hero and backward
transitions, covered by new unit tests
(`LoadedWorkflowPaywallHeaderSelectionTest`).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
88595b3. 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: Claude Sonnet 4.6 <noreply@anthropic.com>
@vegaro

vegaro commented May 7, 2026

Copy link
Copy Markdown
Member Author

This is getting superseeded by #3447

@vegaro vegaro closed this May 7, 2026
@vegaro vegaro deleted the cesar/wfl-46-workflow-image-preloader branch May 7, 2026 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants