Skip to content

LayrzDynamicAvatarInput

Kenny Mochizuki Escalona edited this page Aug 13, 2026 · 2 revisions

LayrzDynamicAvatarInput

A flexible avatar selection field that composes LayrzTextInput and offers four avatar types: URL, base64-encoded image, icon, or emoji. The user chooses which type via a tabbed dialog, then selects the value within that type.

Metadata
Mirrors: ThemedDynamicAvatarInput (layrz_theme)
Phase: M4 (Pickers)
Domain: Pickers
Primitive: Composite (composes LayrzAvatarInput, LayrzIconInput, LayrzEmojiInput, and standard text inputs)
Status: Derived from layrz_theme. Not yet team-confirmed.


⚠️ IMPORTANT: Specification Status

This page is derived from analysis of ThemedDynamicAvatarInput 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

LayrzDynamicAvatarInput renders a read-only LayrzTextInput with a thumbnail preview of the selected avatar in the prefix slot. Tapping the field opens a tabbed dialog offering four avatar types:

  1. URL — enter an image URL that layrz_ui will fetch and display.
  2. Upload — upload an image file (uses LayrzAvatarInput internally).
  3. Icon — pick an icon from layrz_icons (uses LayrzIconInput internally).
  4. Emoji — pick an emoji (uses LayrzEmojiInput internally).

The user selects a type and then provides the value within that type. The field displays a thumbnail and stores both the type and value as an AvatarInput object.

Composition

LayrzDynamicAvatarInput is a composite widget that builds a tabbed dialog composed of four separate selection surfaces. It follows the input family pattern (see Input Contract).

Dependency Ordering

Critical: LayrzDynamicAvatarInput must be implemented after the following components are complete:

  • LayrzAvatarInput (M4)
  • LayrzIconInput (M4)
  • LayrzEmojiInput (M4)

It cannot ship until all three dependencies are complete.


Conformance

LayrzDynamicAvatarInput 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 avatar 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 avatar picker).
  • readOnly — always true for LayrzDynamicAvatarInput.
  • 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 LayrzDynamicAvatarInput extends LayrzTextInput {
  /// The currently selected avatar.
  ///
  /// An AvatarInput object that encodes:
  /// - type: AvatarType (none, url, base64, icon, emoji)
  /// - value: The raw value (URL string, base64 string, LayrzIcon, or emoji string)
  /// 
  /// Null if no avatar is selected.
  final AvatarInput? value;

  // ...
}

Stores an AvatarInput object that bundles both the avatar type and the value for that type.

// Design sketch — illustrative only
class AvatarInput {
  /// The type of avatar selected.
  final AvatarType type;

  /// The value for this type.
  /// 
  /// Interpretation depends on type:
  /// - AvatarType.url → String (URL)
  /// - AvatarType.base64 → String (base64 data URI)
  /// - AvatarType.icon → LayrzIcon
  /// - AvatarType.emoji → String (emoji character)
  /// - AvatarType.none → null
  final dynamic value;

  // Constructor and methods omitted for brevity
}

enum AvatarType {
  none,     // No avatar selected
  url,      // Image from URL
  base64,   // Image uploaded and encoded as base64
  icon,     // Icon from layrz_icons
  emoji,    // Single emoji character
}

Selection Surface

Type: Modal dialog with tabs
Tabs: Four tabs, one for each avatar type
Content: Each tab displays the appropriate selection surface:

  1. URL Tab: A text input field for entering an image URL + preview of the fetched image.
  2. Upload Tab: LayrzAvatarInput component for system image picker.
  3. Icon Tab: LayrzIconInput component for icon selection.
  4. Emoji Tab: LayrzEmojiInput component for emoji selection.

When the user taps the field:

  1. A dialog opens with four tabs.
  2. The tab corresponding to the current avatar type is active.
  3. The user can switch tabs to change the avatar type.
  4. Within each tab, the user selects or enters the value.
  5. Cancel closes the dialog without changing the selection; Save/OK confirms.
  6. onChanged is invoked with the new AvatarInput.

Deltas from Base Contract

LayrzDynamicAvatarInput adds the following to the base LayrzTextInput contract:

// Design sketch — illustrative only
class LayrzDynamicAvatarInput extends LayrzTextInput {
  /// The currently selected avatar.
  /// Null if no avatar is selected.
  final AvatarInput? value;

  /// Callback invoked when the user selects a new avatar.
  final ValueChanged<AvatarInput?>? onChanged;

  /// Which avatar types are enabled for selection.
  ///
  /// If empty (default), all types are enabled: [.url, .base64, .icon, .emoji].
  /// Supply a filtered list to hide certain types.
  /// (AvatarType.none is always available as a "clear selection" option.)
  final List<AvatarType> enabledTypes;

  /// Height factor for the dialog content.
  ///
  /// Defaults to 0.7 (dialog takes 70% of screen height).
  /// Used to calculate responsive dialog sizing.
  final double heightFactor;

  /// Maximum height of the dialog in logical pixels.
  ///
  /// Defaults to 350 logical pixels.
  /// The dialog height is calculated as min(screen.height * heightFactor, maxHeight).
  final double maxHeight;

  // ... (inherits all other LayrzTextInput parameters)
}

Reference: layrz_theme API

ThemedDynamicAvatarInput exposes these parameters (simplified):

class ThemedDynamicAvatarInput extends StatefulWidget {
  final String? labelText;
  final Widget? label;
  final AvatarInput? value;
  final void Function(AvatarInput?)? onChanged;
  final bool disabled;
  final List<String> errors;
  final bool hideDetails;
  final EdgeInsets? padding;
  final List<AvatarType> enabledTypes;  // default: [.url, .base64, .icon, .emoji]
  final double heightFactor;             // default: 0.7
  final double maxHeight;                // default: 350
}

Differences for layrz_ui:

  • Remove all Material-specific parameters.
  • Inherit from base LayrzTextInput contract rather than re-declare shared parameters.

URL Tab Behavior

The URL tab allows entering an image URL:

  • Input field: A text input for the URL.
  • Preview: Below the input, display a preview of the image fetched from the URL (if valid and accessible).
  • Validation: Provide feedback if the URL is invalid or the image fails to load (e.g., "Invalid URL", "Image not found").

Open question: How should the preview handle slow or non-responsive servers? Should there be a timeout for image fetching?


Upload Tab Behavior

The Upload tab renders an LayrzAvatarInput component. The user can:

  • Tap the thumbnail area to open the system image picker.
  • Select an image.
  • The image is converted to base64.
  • The base64 data URI is stored in the AvatarInput.value field.

Icon Tab Behavior

The Icon tab renders an LayrzIconInput component. The user can:

  • Tap the field to open the icon picker dialog.
  • Search and select an icon.
  • The selected LayrzIcon is stored in the AvatarInput.value field.

Emoji Tab Behavior

The Emoji tab renders an LayrzEmojiInput component. The user can:

  • Tap the field to open the emoji picker dialog.
  • Search and select an emoji.
  • The emoji character is stored in the AvatarInput.value field.

Thumbnail Display

A thumbnail preview appears in the prefix slot of the main field, showing:

  • URL avatar: The fetched image (or a placeholder if loading or failed).
  • Base64 avatar: The uploaded image.
  • Icon avatar: The selected icon.
  • Emoji avatar: The emoji character.
  • No avatar: A generic avatar placeholder or the label text.

Dependencies

  • M1 Theme System (LayrzTheme, LayrzThemeData) — colors and text styling.
  • M2 Tooltip Component (LayrzTooltip) — for help affordance.
  • M3 LayrzTextInput — base field chrome and behavior.
  • M4 LayrzAvatarInput — for the Upload tab.
  • M4 LayrzIconInput — for the Icon tab.
  • M4 LayrzEmojiInput — for the Emoji tab.

Implementation Notes

Tab State Management

When the dialog opens, which tab should be active?

  • If value.type is set, open to that tab.
  • If no avatar is selected, open to the first enabled type (or a default like URL).

Type Enabling/Disabling

If a type is disabled via enabledTypes, its tab should be hidden or grayed out. If no types are enabled, an error should be raised in debug mode.

Image Fetching for URL Tab

When the user enters a URL in the URL tab:

  • Validate the URL format.
  • Attempt to fetch the image.
  • Display a loading indicator while fetching.
  • Show the image on success; show an error message on failure.
  • Cache the fetched image so it doesn't need to be re-fetched on dialog reopen.

Open question: Should the cached image be cleared when the dialog closes, or retained?


Open Questions

  • AvatarType enum location: Where should AvatarType and AvatarInput be defined? In a separate models file, or in the input components module?

  • Icon thumbnail rendering: When an icon is selected, how should it be displayed in the thumbnail? As the icon itself, or in an avatar circle with background color?

  • Emoji thumbnail rendering: Same question for emojis.

  • URL image caching: Should fetched images be cached, or re-fetched on each dialog reopen? If cached, where and for how long?

  • Tab completion flow: When the user selects a value in a tab (e.g., selects an icon), does the dialog immediately close, or do they need to click OK/Save to confirm?

  • Switching tabs with unsaved changes: If the user enters a URL, then switches to the Icon tab, what happens to the URL? (Store it and restore if they switch back, or discard it?)


Design Reference Gap

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

  • Dialog chrome (padding, buttons, title, size).
  • Tab layout (horizontal tabs above content, or another layout).
  • Content layout for each tab (spacing, alignment).
  • URL tab: text input field and image preview layout and sizing.
  • Upload/Icon/Emoji tab: integration with their respective pickers.
  • Thumbnail display in the main field (size, border radius, background).
  • Loading state for URL image fetching.
  • Error state and error message styling.
  • Light and dark theme variants.
  • Responsive behavior on phone, tablet, and desktop.
  • Keyboard navigation (Tab to switch tabs, Tab/arrow keys to navigate within tab content).

Ordering Dependency

CRITICAL: This component cannot be started until the following are complete and stable:

  • LayrzAvatarInput (fully implemented and tested)
  • LayrzIconInput (fully implemented and tested)
  • LayrzEmojiInput (fully implemented and tested)

List this as an explicit blocker in the M4 project plan.


Last updated: 2026-08-13
Related documents: Input Contract, Dependencies, Design Tokens, Architecture, Roadmap, l_avatar_input.md, l_icon_input.md, l_emoji_input.md

Clone this wiki locally