Skip to content

perry/ui: single Image widget for remote URLs (avatars, cover photos, list-item thumbnails) #635

Description

@proggeramlug

Summary

perry/ui exposes ImageFile(path) (local file path), ImageSymbol(name) (system symbols), and ImageGallery(...) (paged gallery — added in #553, accepts [{url, alt}]). There's no inline single-image-by-URL primitive — i.e. an Image(url) you can drop into an HStack for an avatar or into a card body for a thumbnail.

Most production mobile apps need this widget at multiple zoom levels:

  • Avatar circle in chat row, profile header, owner strip on item detail
  • Cover photo at the top of a profile / item detail
  • Thumbnail on each LazyVStack row (Items list, search results, my-items)

ImageGallery already does the URL-fetching work for the gallery case; the inline-single equivalent is what's missing.

Proposed surface

Image({
  url: string,           // remote URL; `data:` URIs welcome
  alt?: string,          // accessibility label
  placeholder?: Widget,  // shown while the request is in flight + on error
  contentMode?: "fit" | "fill" | "cover",  // matches UIImageView contentMode
}): Widget

Native iOS: UIImageView + URLSession.dataTask (or SDWebImage if a dependency is acceptable).
Native Android: ImageView + Glide/Coil/Picasso.
Web: <img src=…>.

Why it's a blocker for production apps

Reusing ImageGallery for single-image cases works only if the layout can accommodate a paged carousel — it can't, for an avatar in a chat row. Downloading to a local file and using ImageFile works but pushes a non-trivial amount of caching/expiry/ETag plumbing into every app.

Workaround until this lands

Currently downloading the URL to a local cache file via fetch + fs.writeFile, then rendering ImageFile(localPath). Acceptable for one-off avatars; painful for a list of 20 cards each with a thumbnail.

Out of scope

  • Image upload (already covered elsewhere via imagePickerPick from perry/system).
  • Animation / progressive decode — not required for v1.
  • Resize-on-fetch / responsive srcset — server-side concern.

Acceptance

  • Image({ url, alt }) returns a widget that can sit in an HStack next to other content.
  • The image loads asynchronously; the widget shows placeholder (or a transparent box) until the response arrives.
  • Failed requests show placeholder indefinitely (no crash); a follow-up callback for "load completed / failed" is nice-to-have but not v1.
  • Available on iOS, Android, and web.

Context

Surfaced while porting an Angular Material mobile app (sharity-app → wishare) to Perry. Three production screens currently fall back to colored-circle placeholders or "Lade …" text where Angular shows actual photos: profile header (avatar + cover photo), item detail (gallery already works via #553), and item list (thumbnail on each card).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions