Skip to content

LayrzAvatarInput

Kenny Mochizuki Escalona edited this page Aug 20, 2026 · 3 revisions

LayrzAvatarInput

An image upload field that composes LayrzTextInput and opens a system image picker to select and upload an image, storing the result as base64.

Metadata
Mirrors: ThemedAvatarPicker (layrz_theme)
Phase: M4 (Pickers)
Domain: Pickers
Primitive: file_picker 10.3.10 (verified clean, no Material coupling); custom image preview rendering
Status: Derived from layrz_theme. Not yet team-confirmed.


⚠️ IMPORTANT: Specification Status

This page is derived from analysis of ThemedAvatarPicker in layrz_theme and represents planning assumptions only. The specification has not been reviewed or confirmed by the team. Implementation details, parameter names, and behavior may change during the M4 design phase.

All details below are subject to revision.


Overview

LayrzAvatarInput renders a read-only LayrzTextInput with a thumbnail preview of the selected image in the prefix slot. Tapping the field opens the system image picker. When an image is selected, the field displays a thumbnail and stores the image content as base64 for later use (web display, upload, etc.).

Composition

LayrzAvatarInput is a thin wrapper over LayrzTextInput configured as read-only, with an image thumbnail in the prefix. It follows the input family pattern (see Input Contract).


Conformance

LayrzAvatarInput conforms to the Layrz*Input family contract defined in Input Contract. Inherited parameters:

  • labelText (String) — the only label representation.
  • placeholder — shown inside the field when no image is selected.
  • prefixIcon / prefixWidget / onPrefixTap — mutually exclusive icon or widget in the leading slot.
  • suffixIcon / suffixWidget / onSuffixTap — mutually exclusive icon or widget in the trailing slot.
  • helpTitleText / helpContentText — help affordance (tooltip).
  • onTap — callback when the field is tapped (opens the image picker).
  • readOnly — always true for LayrzAvatarInput.
  • focusNode / controller — standard lifecycle management.
  • padding — customizable per-field; defaults to M1 spacing tokens.

See the input contract for the complete shared API and disposal guarantees.


Value Type and Selection Surface

Value Type

// Design sketch — illustrative only
class LayrzAvatarInput extends LayrzTextInput {
  /// The currently selected image, encoded as a data URI.
  ///
  /// Null or empty if no image is selected.
  /// Format: "data:{mimeType};base64,{base64Content}"
  /// 
  /// This string can be:
  /// - Directly embedded in HTML <img> tags (web).
  /// - Sent to a backend for storage.
  /// - Decoded and saved as a file on the device.
  final String? value;

  // ...
}

Stores the selected image as a base64 data URI, ready for web or backend use.

Selection Surface

Type: System image picker (native camera roll, file browser, or photo library)
Input: Image files only (JPEG, PNG, GIF, WebP, etc.)

When the user taps the field:

  1. The native image picker opens (behavior varies by platform).
  2. The user selects an image from their device.
  3. The image is read and converted to base64.
  4. A thumbnail is generated for display.
  5. onChanged is invoked with the base64 data URI.
  6. The field displays the thumbnail.

Deltas from Base Contract

LayrzAvatarInput adds the following to the base LayrzTextInput contract:

// Design sketch — illustrative only
class LayrzAvatarInput extends LayrzTextInput {
  /// The currently selected image, encoded as a data URI.
  ///
  /// Null or empty if no image is selected.
  /// Format: "data:{mimeType};base64,{base64Content}"
  final String? value;

  /// Callback invoked when the user selects an image.
  ///
  /// Receives a base64 data URI string, ready for use in web contexts
  /// or transmission to a backend.
  final ValueChanged<String?>? onChanged;

  /// Width and height of the thumbnail preview.
  ///
  /// Defaults to 100×100 logical pixels.
  /// The thumbnail is square and centered in the display area.
  final double thumbnailSize;

  /// Border radius of the thumbnail preview.
  ///
  /// Defaults to 20 logical pixels (moderately rounded corners).
  final double borderRadius;

  /// Callback to render a custom thumbnail.
  ///
  /// If not supplied, the base64 image is rendered directly
  /// using an Image widget with BoxFit.cover.
  /// Supply this to add effects (borders, badges, etc.).
  final Widget Function(String)? thumbnailBuilder;

  // ...
}

Reference: layrz_theme API

ThemedAvatarPicker exposes these parameters (simplified):

class ThemedAvatarPicker extends StatefulWidget {
  final String? labelText;
  final Widget? label;
  final String? value;  // base64 data URI
  final void Function(String?)? onChanged;
  final bool disabled;
  final List<String> errors;
  final bool hideDetails;
  final Widget? customChild;
  // ... Material-specific color/focus/splash parameters
}

Differences for layrz_ui:

  • Remove all Material-specific color/focus/splash parameters.
  • Inherit from base LayrzTextInput contract rather than re-declare shared parameters.
  • Add thumbnailSize, borderRadius, and thumbnailBuilder for customization.
  • Remove customChild (M1 foundation does not support arbitrary wrapping).

Thumbnail Display

Default Thumbnail Rendering

By default, when an image is selected:

  • A square thumbnail (100×100 pixels) is displayed with rounded corners (20 px radius).
  • The image fills the thumbnail with BoxFit.cover (aspect-ratio-preserving, center-cropped).
  • A small delete icon (X) appears in the top-right corner; tapping it clears the selection.

Custom Thumbnail Builder

For advanced use cases, callers can supply a thumbnailBuilder callback to customize the thumbnail appearance (e.g., adding a border, badge, or filter).


Image Constraints

Format and Size

Which image formats should be supported?

  • Minimum: JPEG, PNG.
  • Recommended: JPEG, PNG, GIF, WebP.
  • Size limit: Is there a maximum file size? (layrz_theme has no built-in constraint; recommend adding one to prevent memory issues.)

Image Quality

When the image is converted to base64, should it be:

  • Unchanged: Raw data from the file system (preserves quality, large size).
  • Compressed: Re-encoded at a lower quality (smaller size, potential quality loss).
  • Resized: Scaled to a maximum dimension (e.g., 512×512) before encoding (reduces file size).

Open question: What is the intended use case? For avatar storage, a smaller size (512×512, ~20-50 KB) is typical. For document attachments, preserving quality may be more important.


Dependencies

  • M1 Theme System (LayrzTheme, LayrzThemeData) — colors and text styling.
  • M2 Tooltip Component (LayrzTooltip) — for help affordance.
  • M3 LayrzTextInput — base field chrome and behavior.
  • file_picker 10.3.10 (verified clean) — system image picker integration.

Implementation Notes

Platform Differences

The image picker behaves differently on different platforms:

  • Android: Native file browser or camera app.
  • iOS: Photo library, camera, or photo capture.
  • Desktop (Windows/macOS/Linux): Native file open dialog.
  • Web: Browser's <input type="file" accept="image/*"> dialog.

Test on each platform to ensure consistent UX.

Thumbnail Rendering

The thumbnail is rendered using Flutter's Image.memory() or Image.network() depending on the source. Since the value is a base64 data URI, use Image.memory() with the decoded bytes.

Animation

layrz_theme uses AnimationController to fade in the delete button when an image is selected. Consider a similar approach for visual polish, but it's not a critical feature.


Clear Selection Behavior

When the user taps the delete icon on the thumbnail:

  • The selection is cleared immediately.
  • onChanged is invoked with null or empty string.
  • The thumbnail fades out (animated).

Open Questions

  • Image size constraints: Should there be a maximum file size? If so, what is it? (Recommend 5-10 MB for most use cases.)

  • Image dimensions: Should there be a maximum resolution (e.g., 1920×1080 or 2048×2048)? Should layrz_ui reject very large images?

  • Compression: Should the image be automatically compressed before base64 encoding? If yes, what quality level (0-100)?

  • Animated images: Should the picker support animated GIFs or WebP? If selected, should the animation be preserved in the base64 data?

  • Error handling: What if the image picker fails (permissions denied, file access error)? Should an error message be displayed in the input field, or handled by the caller?

  • Placeholder icon: The upload icon is MdiIcons.upload (from flutter_material_design_icons).

  • Delete icon placement: Should it always be visible, or only on hover (desktop) / tap (mobile)?


Design Reference Gap

Critical path item: A design reference (Figma, spec, or annotated screenshot) must be provided before M4 implementation begins, specifying:

  • Thumbnail size and aspect ratio (square? circular?).
  • Border radius and border styling.
  • Delete icon size, placement, and styling.
  • Placeholder icon and empty state styling.
  • Hover/focus state appearance.
  • Animation timing for fade-in/fade-out.
  • Light and dark theme variants.
  • Disabled state appearance.
  • Error display (related to help affordance in [Input Contract](Input-Contract)).
  • Touch target sizing (minimum 44×44 for mobile).

Last updated: 2026-08-13
Related documents: Input Contract, Dependencies, Design Tokens, Architecture, Roadmap

Clone this wiki locally