feat(theme): iOS-parity dark-mode polish for Android#565
Merged
barrydeen merged 6 commits intoMay 25, 2026
Conversation
5db13e5 to
7968da3
Compare
The default ("custom") Android dark theme rendered noticeably lighter
than iOS, which uses near-black backgrounds. Align with iOS HIG dark
system colors (slight off-black for the base, iOS secondary/tertiary
greys for elevated surfaces) so the two platforms feel like the same
app in dark mode.
- background: #131215 → #0A0A0B (slight off-black, OLED-friendly
without the harsh #000 step on LCD)
- surface: #1F1E21 → #1C1C1E (iOS secondarySystemBackground)
- surfaceVariant: #2B2A2E → #2C2C2E (iOS tertiarySystemBackground)
- outline: #343338 → #38383A (iOS separator on dark)
Named presets (Nord, Dracula, Gruvbox, …) are left untouched — their
distinctive backgrounds are part of each preset's identity.
Default `TopAppBarDefaults.topAppBarColors` uses `MaterialTheme.color Scheme.surface`, which sat noticeably lighter than the body after the preceding dark-mode background darken. iOS uses one near-black across body + chrome and reserves the lighter "surface" tone for elevated controls (pills, cards). Switch every screen's TopAppBar container to `background` so chrome reads as part of the page, not as a raised layer above it. 30 screens touched; only `containerColor` lines inside `TopAppBarDefaults.topAppBarColors(...)` blocks are changed, so other surface usages (cards, dialogs, sheets, the elevated pills the home top bar overlays) keep their existing tone.
iOS-style cleanup on the home screen's top + bottom chrome: - `FeedScreen` `CenterAlignedTopAppBar` clamps to 48dp content + status-bar inset (was Material's default ~64dp + inset). Drops the gap below the icon row that pushed the feed down. - `BottomBar` `NavigationBar` clamps to 56dp content + gesture inset (was Material's default 80dp). The 80dp slot reserves space for the label text we don't render — pure waste on small phones. - Tab indicator pill is suppressed (`indicatorColor = Color.Transparent`). The selected-icon orange tint is enough signal; matches iOS where the tab bar has no rounded background on the active tab. - Notification dot uses iOS systemRed (#FF3B30) instead of the app's primary accent so it reads as "alert" rather than "branded highlight" — same red iOS shows on the bell. - Filter icon for "All" content types switches from `Icons.Outlined.Dashboard` (1 large + 3 small panels) to `Icons.Outlined.GridView` (2x2 of equal squares) to match the iOS toolbar icon.
Two post-card refinements that move the feed toward the iOS look: - `PostCard` now wraps content + the inter-post `HorizontalDivider` in an outer Column. The content Column keeps its 16dp horizontal padding (so post body / action bar / metadata stay inset), but the divider sits outside that padding and runs edge-to-edge. Matches iOS where the separator spans the full viewport width. - `ActionBar` gates each of the four counters (`replyCount`, `likeCount`, `repostCount`, `zapSats`) on `> 0`. Empty engagement no longer shows "0" beside the icon — matches iOS where the count text only appears when there's something to show. As soon as the count crosses 1, the number reappears.
Two more iOS-parity tweaks: - `WispTheme` sets `error = #FF3B30` (and `onError = white`) explicitly on every color-scheme variant (custom dark/light + preset dark/light). Material 3's defaults for `error` render pinkish in dark mode and a muted brick red in light mode — neither matches the iOS systemRed used by the rest of the destructive UI in this app. With this override, every `MaterialTheme.colorScheme.error` consumer (logout button, destructive labels, error text) now matches the iOS counterpart and the existing #FF3B30 used directly on Disconnect/Switch wallet flows. - `UserProfileScreen` sticky-header tab strip + the sort-pill row below it use `background` (#0A0A0B) instead of `surface` (#1C1C1E). The two grey tiers stacked above each other read as visually noisy on the profile; the iOS profile uses one near-black across both. Body posts below still render with the elevated tier where they need to.
Previous attempt computed total chrome height as
`Modifier.height(content + insetReadViaPaddingValues)`. The inset is
read at composition time and arrives as 0 on the very first frame
before the system-bar inset connection delivers its value — the bar
laid out at the shorter (no-inset) height, then re-measured once the
inset arrived. Visible as a one-frame snap on app cold start.
Switch to a layout-time pattern that subscribes to inset changes
correctly:
Modifier
.windowInsetsPadding(insets) // reserves the inset via padding
.height(contentHeight) // content area only
`windowInsetsPadding` is a Modifier.Node that re-layouts (not re-
composes) on inset arrival, so the bar measures at the right total
height on the first frame. The bar's own `windowInsets` is set to
`WindowInsets(0)` so it doesn't double-pad.
Applied to:
- `BottomBar` NavigationBar — content height 56dp + navigation-bars inset
- `FeedScreen` CenterAlignedTopAppBar — content height 48dp + status-bars
inset
7968da3 to
6f97c42
Compare
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
iOS-parity refinements layered on top of the previous
d6d30ea"darken default dark-mode backgrounds" commit. Together they bring the Android dark-mode chrome, post separators, action bar metrics, and destructive-action red in line with the iOS design.What's in
Chrome color sweep (
ee56c04) — Every screen'sTopAppBarcontainerColorswitches fromcolorScheme.surface(a step lighter than the body) tocolorScheme.background. iOS uses one near-black across body + chrome and reserves the lightersurfacetier for elevated controls (pills, cards); this makes Android match. 30 screens touched.Compact chrome heights + iOS-style bottom nav (
3fc9d02, refined in5db13e5) —FeedScreen.CenterAlignedTopAppBarcontent area clamps to 48dp (was Material's 64dp).BottomBar.NavigationBarcontent area clamps to 56dp (was Material's 80dp — most of which is wasted on a label slot we don't render).indicatorColor = Color.Transparent); the orange tint on the selected icon is enough signal and matches iOS.#FF3B30) instead of the app's primary accent so it reads as "alert" rather than "branded highlight."Icons.Outlined.Dashboard(1 large + 3 small panels) toIcons.Outlined.GridView(2×2 of equal squares) to match the iOS toolbar icon.Modifier.windowInsetsPadding(insets).height(content)withwindowInsets = WindowInsets(0)on the bar — layout-time inset resolution, no first-frame snap that anasPaddingValues().calculate*Padding()read at composition time would cause.Full-width post divider + hide zero-value counters (
f1177fe) —PostCardwraps content + the inter-postHorizontalDividerin an outer Column. Content keeps its 16dp horizontal padding; the divider sits outside it and runs edge-to-edge, matching iOS.ActionBargates each of the four counters (replyCount,likeCount,repostCount,zapSats) on> 0. No more "0" beside icons; counts reappear as soon as engagement crosses 1.iOS-red error color + darken profile tab strip (
d7bae63) —WispThemesetserror = #FF3B30(andonError = white) explicitly on every color-scheme variant (custom dark/light + preset dark/light). Replaces Material 3's pinkish dark-mode default / brick-red light-mode default with the iOS systemRed already used by Disconnect Wallet, Switch, and the bell badge.UserProfileScreensticky-header tab strip + sort-pill row usebackground(#0A0A0B) instead ofsurface(#1C1C1E) so the chrome reads as part of the body and doesn't stack two grey tiers.Test plan
0s); add one reply / like / zap, the corresponding count appears.🤖 Generated with Claude Code