Skip to content

Reader: Apply theme to post detail and improve preferences dialog#22723

Merged
nbradbury merged 27 commits intotrunkfrom
issue/CMM-1972-reader-apply-theme
Mar 23, 2026
Merged

Reader: Apply theme to post detail and improve preferences dialog#22723
nbradbury merged 27 commits intotrunkfrom
issue/CMM-1972-reader-apply-theme

Conversation

@nbradbury
Copy link
Copy Markdown
Contributor

@nbradbury nbradbury commented Mar 20, 2026

Description

This PR started with a simple task: ensure that after choosing a theme from the Reader Preferences, that theme is applied when returning to the post detail. Previously the theme wouldn't be applied until you returned to the post list and re-opened the post.

Unfortunately, the existing Reader Preferences screen suffered from a number of problems, mostly related to using a bottom sheet instead of a standard dialog, and was implemented very differently than the iOS version. So I chose to redo this screen as a standard dialog, similar to iOS, and add icons to the toolbar to save or discard changes (previously there was just an "X" icon).

This was a much bigger task than expected, but I think the end result is quite an improvement over the existing reader preferences.

Testing instructions

Theme applies to open post:

  1. Open a Reader post
  2. Tap the reading preferences icon
  3. Change the theme (e.g., to Sepia or OLED)
  4. Tap the checkmark to save
  • Verify the post detail immediately reflects the new theme colors (background, text, toolbar, nav bar)

Save/discard behavior:

  1. Open reading preferences dialog
  2. Change the theme
  • Verify the checkmark icon appears in the toolbar
  1. Tap the X to close without saving
  2. Reopen the dialog
  • Verify the theme reverted to the previous selection
  1. Change the theme again and tap the checkmark
  • Verify the change is saved and applied

Landscape scrolling:

  1. Rotate the device to landscape
  2. Open the reading preferences dialog
  • Verify the entire dialog content is scrollable and nothing is cut off
reader.mp4

nbradbury and others added 18 commits March 20, 2026 10:52
Three issues prevented theme changes from being reflected in an
already-open post:

1. SharedFlow event dropped: UpdatePostDetails was emitted via
   launch(bgDispatcher) but the collector was bound to the dialog's
   viewLifecycleOwner, which was already destroyed by onDismiss time.
   Fix: call postDetailViewModel directly from onDismiss().

2. detach/reattach doesn't work with ViewPager2: FragmentStateAdapter
   manages its own fragments, so manual detach/attach was ignored.
   Fix: re-render WebView content and update native view colors in place.

3. Repository cache race: the save coroutine on ioDispatcher may not
   have executed by the time the fragment re-reads preferences.
   Fix: synchronously update the in-memory cache before the UI reads it.

Also explicitly set header text colors from ThemeValues since the
ContextThemeWrapper used at inflation time becomes stale, and update
the activity status bar color on theme change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The status bar area on API 35+ is transparent (edge-to-edge), so the
visible color comes from the AppBarLayout and its CollapsingToolbarLayout
content scrim drawn behind it. Update both when reading preferences
theme changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update footer bar background color when reading preferences theme
  changes so the area behind the transparent navigation bar matches
- Remove dead setWindowStatusBarColor call (no-op on SDK 35+)
- Inline isDirty() into hasUnsavedChanges property

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of manually updating colors on each view (root, appBar,
collapsingToolbar, footer, chips, header text) when reading preferences
change, the ViewPager2 adapter now destroys and recreates the current
fragment. The new fragment is inflated with a fresh ContextThemeWrapper
so all views get correct theme colors automatically.

Also removes the Experimental badge from the reading preferences screen
and adds chip color support via ColorStateList.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove dead initNavigationBar() method, simplify ReaderExpandableTagsView
and ReaderPostDetailHeaderView, and add setDialogWindowStatusBarColor()
for dialog windows that aren't subject to edge-to-edge enforcement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On SDK 35+, Window.setStatusBarColor() is ignored because the status
bar is always transparent. Enable edge-to-edge on the dialog window
so the TopAppBar's background color shows through the status bar.
Add navigationBarsPadding to the bottom preferences section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of trying to set the status bar color (which is ignored on
SDK 35+), hide the status and navigation bars on the dialog window
to match the post detail screen behavior. This removes all status
bar color management from the dialog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The edge-to-edge setup was causing a gap at the top of the dialog
where the activity's toolbar was visible. Just hiding the system
bars is sufficient.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The BottomSheetDialog's internal container, coordinator, and bottom
sheet views all have fitsSystemWindows=true, which adds top padding
for the status bar. Clear fitsSystemWindows and padding on these
views in onStart() so the dialog content fills the entire screen,
matching the post detail screen's full-screen layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The various approaches to fix the dialog's status bar color on
SDK 35+ (setDialogWindowStatusBarColor, edge-to-edge, hiding
system bars) did not work correctly. Revert to the original
dialog state before those changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…discard UX

Replace fullscreen bottom sheet with a non-fullscreen one to avoid
SDK 35+ status bar color issues. X (close) discards changes, checkmark
saves. Remove all status bar color management logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove updateCache/updateCachedPreferences which became dead code
after the ViewModel's syncCachedPreferences was removed. Also remove
the nestedScroll modifier from the reading preferences screen since
it is no longer a fullscreen draggable bottom sheet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s exist

Move STATE_EXPANDED/skipCollapsed into setOnShowListener to avoid the
scroll-up-then-down animation on open. Add hasUnsavedChanges StateFlow
to conditionally show the save checkmark only when preferences differ
from the original values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The SlideDialogFragmentAnimation was designed for fullscreen dialogs and
conflicts with the BottomSheetDialog's built-in entrance animation,
causing the sheet to slide to the top of the screen then settle back
down. Removing it lets the bottom sheet animate smoothly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace BottomSheetDialogFragment with DialogFragment to eliminate
animation conflicts between the window-level slide animation and the
BottomSheetBehavior entrance animation. Add rounded corners to the
Compose content and use a Material dialog theme overlay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dangermattic
Copy link
Copy Markdown
Collaborator

dangermattic commented Mar 20, 2026

2 Warnings
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
⚠️ PR is not assigned to a milestone.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Mar 20, 2026

App Icon📲 You can test the changes from this Pull Request in Jetpack Android by scanning the QR code below to install the corresponding build.

App NameJetpack Android
Build TypeDebug
Versionpr22723-1eb34f4
Build Number1488
Application IDcom.jetpack.android.prealpha
Commit1eb34f4
Installation URL0urhs63ghu0s0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Mar 20, 2026

App Icon📲 You can test the changes from this Pull Request in WordPress Android by scanning the QR code below to install the corresponding build.

App NameWordPress Android
Build TypeDebug
Versionpr22723-1eb34f4
Build Number1488
Application IDorg.wordpress.android.prealpha
Commit1eb34f4
Installation URL0pjjsp8s9g4m0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nbradbury and others added 5 commits March 20, 2026 18:51
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 37.35%. Comparing base (0c9a9a3) to head (1eb34f4).
⚠️ Report is 2 commits behind head on trunk.

Additional details and impacted files
@@            Coverage Diff             @@
##            trunk   #22723      +/-   ##
==========================================
- Coverage   37.35%   37.35%   -0.01%     
==========================================
  Files        2316     2316              
  Lines      123272   123264       -8     
  Branches    16712    16711       -1     
==========================================
- Hits        46053    46046       -7     
+ Misses      73520    73519       -1     
  Partials     3699     3699              

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nbradbury nbradbury requested review from adalpari and dcalhoun March 21, 2026 16:09
@nbradbury nbradbury marked this pull request as ready for review March 21, 2026 16:09
@adalpari
Copy link
Copy Markdown
Contributor

Landscape scrolling:

If I rotate the device with the dialog opened, it disappears. But since this is not contemplated in tests, I assume it's expected.

Copy link
Copy Markdown
Contributor

@adalpari adalpari left a comment

Choose a reason for hiding this comment

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

🚢 it!

@nbradbury
Copy link
Copy Markdown
Contributor Author

If I rotate the device with the dialog opened, it disappears.

Hmm, that shouldn't be happening, even when "Don't keep activities" is enabled. I'm not able to reproduce this, either. Do you see this on multiple devices?

@adalpari
Copy link
Copy Markdown
Contributor

If I rotate the device with the dialog opened, it disappears.

Hmm, that shouldn't be happening, even when "Don't keep activities" is enabled. I'm not able to reproduce this, either. Do you see this on multiple devices?

Interesting, it does not always happen. I have been able to reproduce it only with a Pixel 6 physical device.

@adalpari
Copy link
Copy Markdown
Contributor

See: following the same steps, but only happens once

Screen_recording_20260323_114529.mp4

The reading preferences dialog disappeared on rotation after changing
the reader theme because the adapter's generation counter reset to 0,
causing FragmentStateAdapter to discard the restored fragments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

@nbradbury
Copy link
Copy Markdown
Contributor Author

@adalpari Ah! I was able to reproduce this by changing and saving the theme, then re-opening the dialog and rotating the device. Claude identified the problem and I pushed the fix in Persist pager generation across config changes to fix dialog loss.

Root cause: The generation counter in PostPagerAdapter was a local field that reset to 0 every time the activity was recreated (on rotation). After changing the reader theme, recreateFragments() bumped generation to 1, so all fragments got IDs based on generation 1. On rotation, the new adapter started with generation = 0, causing containsItem() to return false for all the saved generation-1 fragments. FragmentStateAdapter then discarded them — including the child dialog fragment — and created new ones.

Fix: Moved generation from the adapter to the activity as pagerGeneration, and save/restore it via onSaveInstanceState/onCreate. This way the generation counter survives configuration changes, and FragmentStateAdapter correctly recognizes the existing fragments after rotation.

@nbradbury nbradbury enabled auto-merge (squash) March 23, 2026 11:35
@nbradbury nbradbury merged commit 5b9c4cf into trunk Mar 23, 2026
21 of 22 checks passed
@nbradbury nbradbury deleted the issue/CMM-1972-reader-apply-theme branch March 23, 2026 11:42
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.

5 participants