Skip to content

feat: BlurHash progressive placeholders for profile-picture avatars#515

Merged
bmc08gt merged 1 commit into
mainfrom
feat/blurhash-avatar-placeholders
Jul 25, 2026
Merged

feat: BlurHash progressive placeholders for profile-picture avatars#515
bmc08gt merged 1 commit into
mainfrom
feat/blurhash-avatar-placeholders

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Why

A counterpart's profile-picture avatar currently shows a blank gradient (then a monogram) while its bytes download, then pops to the image. This ports the BlurHash placeholder from android #1131 so the avatar instead shows an instant blurred preview that resolves to the real thumbnail.

Scope is deliberately lean (the iOS side differs from Android): iOS ProfilePicture collapses the server's renditions to two blob IDs and avatars are fixed-size, so this keeps the existing "largest thumbnail" selection and adds only the BlurHash. Progressive path is BlurHash → full image (no avatar-view rework, no per-surface size selection). The ImageMetadata.blurhash proto field was already present but unused, and it's available the moment ProfilePicture is built from the proto — no extra RPC.

What

  • BlurHash (FlipcashUI/.../Images/BlurHash.swift) — public-domain decoder (decode(_:width:height:punch:) -> UIImage?) plus a process-wide BlurHashCache, ported from the PR's BlurHash.kt.
  • ProfilePicture.thumbnailBlurhash — read off the same thumbnail rendition already selected; decodeIfPresent for JSON back-compat.
  • SQLite — persist the blurhash (new profilePictureThumbnailBlurhash column; SQLiteVersion 25 → 26, so the DB rebuilds from server data on next launch).
  • ContactAvatarView — renders the blurhash while imageData is nil (precedence: bytes → blurhash → monogram).
  • Threading — the tip/DM counterpart surfaces pass profilePicture?.thumbnailBlurhash (conversation title + transcript profile card, tip conversations list). Address-book avatars pass nil.

Tests

  • BlurHashTests — decoder vectors (valid decode to size, nil/blank, component-count mismatch, non-positive dims, out-of-alphabet chars).
  • ProfilePictureTests — blurhash extracted from the selected thumbnail; nil when absent; legacy JSON (no field) decodes to nil.

Notes / deviations from android #1131

  • No size-aware rendition selection (renditionForSize/renditionBelow) — iOS avatars are fixed 44/80/96px and already fetch the largest thumbnail.
  • No "cached smaller rendition" intermediate placeholder — that relies on Coil's placeholderMemoryCacheKey; iOS's Data-driven avatar view has no equivalent.
  • No notification-icon / chat-image changes — no iOS counterparts (CNContact-only notifications; chat image messages unmodeled).

Carry the thumbnail rendition's BlurHash on ProfilePicture and render it as
an instant blurred preview while a counterpart's avatar downloads, instead
of a blank gradient. Ported from android code-payments/code-android-app#1131
(lean scope: BlurHash + placeholder; the existing largest-thumbnail
selection is kept, avatars are fixed-size).

- BlurHash: public-domain decoder + process-wide decode cache (FlipcashUI)
- ProfilePicture.thumbnailBlurhash, read off the selected thumbnail rendition
- SQLite: persist the blurhash (new column, SQLiteVersion 25 -> 26)
- ContactAvatarView renders the blurhash while imageData is nil
  (precedence: bytes -> blurhash -> monogram)
- Threaded through the tip/DM counterpart surfaces (conversation title +
  profile card, tip conversations list)
- Tests: BlurHash decoder vectors; ProfilePicture blurhash extraction +
  legacy JSON back-compat
@bmc08gt bmc08gt self-assigned this Jul 24, 2026
@bmc08gt
bmc08gt merged commit cbccfb1 into main Jul 25, 2026
@bmc08gt
bmc08gt deleted the feat/blurhash-avatar-placeholders branch July 25, 2026 00:20
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