unified_fields 0.2.6
unified_fields: ^0.2.6 copied to clipboard
Unified Flutter form fields, phone input with country flags, pickers, Jalali/Gregorian date and time, Persian digits, and Form validate/save/reset.
unified_fields #
Unified Flutter input widgets—text, numbers, pickers, duration, date & time—and form-aware wrappers that plug into Form with validation, save, reset, and optional shake-on-error feedback. Includes a Gregorian / Jalali (Shamsi) calendar sheet and a vendored scrollable positioned list for large picker lists (no scrollable_positioned_list pub dependency).
Table of contents #
- Add to your app
- What you get (feature map)
- Core concepts
- Form integration (validate · save · reset)
- Shake on validation error
- Date & calendar
- Time
- Duration
- Pickers
- Phone (
UnifiedPhoneField) - Bindings with
UnifiedInputPicker - Field controllers
- Field states & label layout
- UI strings (
UnifiedFieldsStrings) - Theming & layout
- Global theme (
UnifiedInputThemeScope) - Persian digits (
UnifiedFieldsTypography) - Localization
- Try the built-in demo
- Dependencies
- Version
Add to your app #
Published on pub.dev/packages/unified_fields. In pubspec.yaml:
dependencies:
unified_fields: ^0.2.6
Run dart pub get (or flutter pub get).
Import
import 'package:unified_fields/unified_fields.dart';
SDK: Dart ^3.10, Flutter >=3.22 (see pubspec.yaml).
Git or local path (contributors / forks) #
If you need a specific commit or a local checkout instead of pub.dev:
dependencies:
unified_fields:
git:
url: https://github.com/MahmoodBakhshayesh/unified_fields.git
ref: main # or a tag / commit SHA
dependencies:
unified_fields:
path: ../unified_fields # relative path to your clone
What you get (feature map) #
| Area | Widgets / APIs | Purpose |
|---|---|---|
| Plain fields | UnifiedTextField, UnifiedNumberField, UnifiedNumericStepField, UnifiedPhoneField, UnifiedDurationField, UnifiedDateField, UnifiedDateRangeField, UnifiedTimeOfDayField |
Same visual system as form fields, without FormField |
| Phone | UnifiedPhoneField, UnifiedPhoneFieldController, UnifiedCountry, UnifiedCountries, UnifiedCountryWidget, UnifiedFlag, showUnifiedPhoneCountryPicker, UnifiedPhoneNumber |
Country flag, dial code, masked national number, ~250-country enum, Persian digits |
| Pickers | UnifiedSinglePickerField, UnifiedMultiPickerField, UnifiedAsyncPickerField, UnifiedAsyncMultiPickerField, CustomWheelPicker |
Bottom-sheet list or grid; multi-column scroll wheels; async variants load items on demand |
| Customizable pickers | UnifiedCustomizablePickerField, UnifiedCustomizableMultiPickerField, UnifiedCustomizableAsyncPickerField, UnifiedCustomizableAsyncMultiPickerField, CustomizableSinglePickerController, CustomizableMultiPickerController |
Controller-driven single or multi selection that also accepts free typed text |
| Form wrappers | UnifiedFormField, UnifiedFormTextField, UnifiedFormSinglePickerField, UnifiedFormMultiPickerField, UnifiedFormAsyncPickerField, UnifiedFormAsyncMultiPickerField, UnifiedFormDateField, UnifiedFormDateRangeField, UnifiedFormTimeOfDayField, UnifiedFormDurationField, UnifiedFormNumberField, UnifiedFormCustomizablePickerField, UnifiedFormCustomizableMultiPickerField, UnifiedFormCustomizableAsyncPickerField, UnifiedFormCustomizableAsyncMultiPickerField |
Form integration: validate, save, reset, validators |
| Form scope | UnifiedFormFieldScope |
Shared AutovalidateMode for all unified form descendants |
| Calendar | showUnifiedFieldsDatePicker, showUnifiedFieldsDatePickerRange, UnifiedFieldsDatePickerSheet, UnifiedFieldsDatePickerGranularity, UnifiedFieldsCalendarKind, UnifiedFieldsDatePickerStyle, UnifiedInputDatePickerStyle |
Single date or range; calendar grid or scroll wheels; Gregorian vs Jalali; themed sheet chrome (0.2.6) |
| Time | UnifiedTimeOfDayField, showUnifiedFieldsTimePicker, UnifiedFieldsTimePickerStyle, UnifiedFieldsTimeGranularity, TimePickerUtils.show |
Material dial (default) or H:M:S wheels with Shamsi digit toggle |
| Duration | UnifiedDurationField, showUnifiedFieldsDurationPicker, UnifiedFieldsDurationColumn, UnifiedFieldsDurationColumnPresets, UnifiedDurationGranularity, UnifiedFieldsDurationPickerStyle |
Wheel picker with fixed granularity or custom column order (year · week · day · hour, …) |
| Chrome helpers | UnifiedBaseTextField, UnifiedFieldShell, UnifiedFieldLabelMode, UnifiedInputDecoration, UnifiedInputDecorationSet, UnifiedInputFieldDefaults, UnifiedInputBrightness, UnifiedInputPalette, UnifiedInputThemeScope, UnifiedInputPhoneStyle, UnifiedPickerSheetStyle, UnifiedBasePickerSheetStyle, UnifiedPickerSheetModalSettings, UnifiedInputPickerHeaderStyle, UnifiedInputMultiPickerCheckboxStyle, UnifiedSuffixIconChrome |
Labels, errors, palettes, per-state borders/fills, theme field defaults, phone/dial chrome, picker sheet chrome + modal flags, global theme scope, picker headers, aligned suffix icons |
| Controllers | UnifiedPickerFieldController, UnifiedMultiPickerFieldController, UnifiedAsyncPickerFieldController, UnifiedDateFieldController, UnifiedTimeOfDayFieldController, UnifiedDurationFieldController, UnifiedNumberFieldController, UnifiedPhoneFieldController, UnifiedFormController |
Listenable value + validation + imperative openPicker / requestFocus |
| Utilities | UnifiedInputPicker, UnifiedFieldsStrings, UnifiedFieldsTypography, UnifiedFieldsContextX, UnifiedColors, UnifiedSheetButton, unifiedPickerDefaultGridDelegate, unifiedPickerItemLabel, unifiedPickerDefaultItemWidget, unifiedPickerResolveListItem, showUnifiedSinglePickerSheet, showUnifiedMultiPickerSheet, showCustomWheelPicker, showUnifiedFieldsPickerBottomSheet, unifiedFormErrorText, unifiedFormPickerOverride, attachUnifiedFieldHandles |
State binding, global UI copy, Persian digits, picker grid/list/wheel helpers, layout helpers, default colors, sheet actions |
| Demo | UnifiedInputsShowcasePage |
Scrollable gallery of widgets + palette toggle |
Core concepts #
One visual language #
Fields share UnifiedInputDecoration (label, placeholder, labelStyle, fieldStyle, placeholderStyle, radii, validation colors, prefix/suffix) and UnifiedInputBrightness (light / dark palette). For colors that should be the same on every field (disabled chrome, required *, validation red, picker sheet background), wrap your app or a screen in UnifiedInputThemeScope — see Global theme.
Per-state decoration (decorationSet) #
Like Material InputDecoration focus/error borders, pass optional layers via decorationSet (or theme-wide fieldDecorationSet). Each layer is a partial UnifiedInputDecoration; unset properties fall back to decoration / palette defaults.
UnifiedTextField(
decoration: const UnifiedInputDecoration(
borderSide: BorderSide(color: Colors.grey),
),
decorationSet: UnifiedInputDecorationSet(
focused: const UnifiedInputDecoration(
borderSide: BorderSide(color: Colors.blue, width: 1.5),
),
error: const UnifiedInputDecoration(
borderSide: BorderSide(color: Colors.red),
validationColor: Colors.red,
),
disabled: const UnifiedInputDecoration(
backgroundColor: Color(0xFFF5F5F5),
),
),
)
States: base, focused, valid, error, locked, disabled, loading, readOnly. The valid layer applies only when you define it and validation has passed with no error.
Two usage modes #
- Standalone — e.g.
UnifiedTextField,UnifiedSinglePickerField: drop into any screen; you own validation if needed. - Under a
Form— useUnifiedForm…variants soGlobalKey<FormState>can callvalidate(),save(), andreset()on the whole form.
label, placeholder, isRequired #
Every field exposes label, placeholder, and isRequired as root-level constructor parameters. They always win over the equivalent values inside UnifiedInputDecoration (which still work as the form-wide default):
UnifiedTextField(
label: 'Email',
placeholder: 'name@example.com',
isRequired: true,
)
Placeholder, label, and focus defaults (0.2.4+) #
Value text style — set on UnifiedInputFieldDefaults (or per field via UnifiedInputDecoration.fieldStyle, UnifiedBaseTextField.style, or style on phone/date fields):
fieldDefaults: const UnifiedInputFieldDefaults(
textStyle: TextStyle(fontSize: 16, color: Colors.black87),
textStylePersian: TextStyle(fontFamily: 'KookFaNum'),
),
Applies to text/number fields, UnifiedPhoneField, UnifiedDateField, and UnifiedDateRangeField. When Persian digits are active (Jalali calendar, usePersianDigits, or global Persian mode), textStylePersian is merged onto textStyle (font family, weight, size, etc.).
Placeholder style — set on decoration or theme; inherits field typography (fontFamily, fontSize, …) unless you override:
UnifiedTextField(
decoration: const UnifiedInputDecoration(
fieldStyle: TextStyle(fontFamily: 'Vazirmatn', fontSize: 16),
// placeholder uses Vazirmatn 16 + theme hint color
placeholderStyle: TextStyle(fontStyle: FontStyle.italic),
),
placeholder: 'Search…',
)
Label style / padding per layout mode — on UnifiedInputFieldDefaults:
fieldDefaults: const UnifiedInputFieldDefaults(
labelInRowStyle: UnifiedInputLabelModeStyle(
labelStyle: TextStyle(fontSize: 13, fontWeight: FontWeight.w600),
labelPadding: EdgeInsets.symmetric(horizontal: 12),
),
labelInColumnStyle: UnifiedInputLabelModeStyle(
labelPadding: EdgeInsets.only(top: 4, bottom: 6),
),
),
Select all on focus — useful for number fields; replace value on first keystroke:
UnifiedNumberField(
controller: quantityController, // e.g. text: '42'
selectTextOnFocus: true,
label: 'Quantity',
)
Or app-wide: UnifiedInputFieldDefaults(selectTextOnFocus: true).
UnifiedFormField<T> #
Low-level shell: one FormField<T> + your builder. Prefer the prebuilt UnifiedForm… widgets; use UnifiedFormField when you need a custom control but the same autovalidate, reset sync, and optional shakeOnError.
Form integration (validate · save · reset) #
Wrap inputs in a Form and keep a key:
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
UnifiedFormTextField(
label: 'Name',
validator: (v) => (v == null || v.isEmpty) ? 'Required' : null,
),
UnifiedFormSinglePickerField<String?>(
label: 'Country',
items: countries,
resetValue: () => null, // on Form reset, clear selection
validator: (v) => v == null ? 'Pick one' : null,
),
FilledButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
}
},
child: const Text('Submit'),
),
],
),
);
}
Reset behavior — UnifiedFormResetValue<T> #
typedef UnifiedFormResetValue<T> = T Function();
| You pass | On FormState.reset |
|---|---|
resetValue: null (default) |
Field value is not forced by reset (baseline tracks live value / controller). |
resetValue: () => '' |
Text / number string resets to empty. |
resetValue: () => null |
Nullable picker / date clears to null (use nullable generic, e.g. UnifiedFormSinglePickerField<MyEnum?>). |
resetValue: () => model.defaultX |
Restores that snapshot (pass a new closure if the snapshot should change). |
| Lists | UnifiedFormMultiPickerField: resetValue: () => const <T>[] or () => myList |
Nullable return type is carried by T on the picker (e.g. RoastLevel?), not by T? on the typedef.
Shake on validation error #
Any UnifiedForm… widget and UnifiedFormField accept shakeOnError (default false). When true, the field runs a short horizontal shake the first time hasError goes from false to true (typical failed validate()).
UnifiedFormTextField(
label: 'Email',
shakeOnError: true,
validator: (v) => v!.contains('@') ? null : 'Invalid',
)
Date & calendar #
Sheet API #
showUnifiedFieldsDatePicker— returnsDateTime?.showUnifiedFieldsDatePickerRange— returnsDateTimeRange?.
Presentation: bottom sheet on smaller widths; centered dialog when context.unifiedFieldsUseDialogLayout is true (see UnifiedFieldsContextX — width ≥ 900).
Granularity (UnifiedFieldsDatePickerGranularity) #
day— full month grid (default).month— pick first day of month in range.year— pick first day of year in range.
Calendars (UnifiedFieldsCalendarKind) #
Users can switch Gregorian vs Jalali (Shamsi) when showCalendarKindToggle is true. Jalali math uses the shamsi_date package.
Widgets #
UnifiedDateField/UnifiedFormDateField— text field + tap to open picker.UnifiedDateRangeField/UnifiedFormDateRangeField— range in one field.
Picker style (UnifiedFieldsDatePickerStyle) #
| Style | UI |
|---|---|
calendar (default) |
Month grid (day granularity) or year/month lists |
wheels |
Scroll wheels left→right: year · month · day (subset per pickerGranularity) |
Both styles support Gregorian / Shamsi when showCalendarKindToggle is true. In Shamsi wheel mode, column headers use سال / ماه / روز (customize via UnifiedFieldsStrings). Set showWeekdayInWheel: false to show only the day numeral; weekday + day use fixed column widths on UnifiedFieldsDateWheelStyle.
Calendar today is shown as a hollow circle (same shape as the selected day fill).
UnifiedDateField(
label: 'Birth date',
pickerStyle: UnifiedFieldsDatePickerStyle.wheels,
showWeekdayInWheel: true,
initialCalendarKind: UnifiedFieldsCalendarKind.jalali,
pickerGranularity: UnifiedFieldsDatePickerGranularity.day,
)
// Same parameters on the Form wrapper:
UnifiedFormDateField(
label: 'Birth date',
pickerStyle: UnifiedFieldsDatePickerStyle.wheels,
wheelStyle: null, // optional; auto-themed when omitted
)
Date ranges still use the calendar sheet (showUnifiedFieldsDatePickerRange).
Wheel chrome is themed automatically (UnifiedFieldsDateWheelStyle.resolve). Pass optional wheelStyle: only when you need custom colors or zoom.
Date picker styling (UnifiedInputDatePickerStyle) #
Set calendar / sheet chrome on UnifiedInputThemeData.datePickerStyle or per field via datePickerStyle: on UnifiedDateField, UnifiedDateRangeField, UnifiedFormDateField, and showUnifiedFieldsDatePicker.
Controls sheet background, title / weekday / day colors and font sizes, range highlight, month-year jump grid, year list, Gregorian/Shamsi toggle, confirm/cancel buttons, optional shamsiTextStyle (merged when the picker is in Jalali mode — use for Persian font family / size), nested wheelStyle for wheel pickers, and yearStripStyle for the horizontal year strip (mouse wheel/drag scroll, optional center zoom + edge fade — falls back to wheelStyle fade/magnification when unset).
UnifiedInputThemeScope(
data: UnifiedInputThemeData(
datePickerStyle: UnifiedInputDatePickerStyle(
sheetBackgroundColor: Color(0xFFF5F5F5),
dayTextColor: Colors.black87,
daySelectedBackgroundColor: Colors.indigo,
daySelectedTextColor: Colors.white,
dayFontSize: 15,
weekdayTextStyle: TextStyle(fontSize: 11, fontWeight: FontWeight.w600),
titleStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
shamsiTextStyle: TextStyle(fontFamily: 'KookFaNum', fontSize: 16),
yearStripStyle: UnifiedFieldsDateYearStripStyle(
magnification: 1.15,
showFade: true,
),
),
),
child: myForm,
)
UnifiedDateField(
label: 'Due date',
datePickerStyle: UnifiedInputDatePickerStyle(
daySelectedBackgroundColor: Colors.teal,
), // merges on top of theme
)
Jalali field text: when initialCalendarKind / active kind is jalali, the field shows Shamsi-formatted text (e.g. ۱۳,مرداد ۱۴۰۳) after pick, not a Gregorian DateFormat. initialCalendarKind: jalali applies to both calendar grid and wheel pickers (0.1.4).
UnifiedDateField(
label: 'Event',
initialCalendarKind: UnifiedFieldsCalendarKind.jalali,
pickerStyle: UnifiedFieldsDatePickerStyle.calendar, // grid respects jalali
)
Time #
UnifiedTimeOfDayField/UnifiedFormTimeOfDayField— display + edit time with unified chrome.showUnifiedFieldsTimePicker— bottom sheet withUnifiedFieldsTimePickerStyle.dial(Material) orwheels(H:M:S scroll wheels).TimePickerUtils.show(context, …)— forwards toshowUnifiedFieldsTimePicker(wheels by default in recent versions; passpickerStyleto use the platform dial).
Picker style (UnifiedFieldsTimePickerStyle) #
| Style | UI |
|---|---|
dial |
Platform showTimePicker |
wheels |
Scroll wheels for hour · minute · (optional) second |
Granularity (UnifiedFieldsTimeGranularity) #
hours— hour wheel onlyhoursMinutes— hour + minute (default for wheels)hoursMinutesSeconds— hour + minute + second
Wheels support Gregorian / Shamsi digit toggle via initialCalendarKind and showCalendarKindToggle (same idea as date/duration wheels).
UnifiedTimeOfDayField(
label: 'Start time',
pickerStyle: UnifiedFieldsTimePickerStyle.wheels,
pickerGranularity: UnifiedFieldsTimeGranularity.hoursMinutesSeconds,
initialCalendarKind: UnifiedFieldsCalendarKind.jalali,
)
Custom wheel picker (CustomWheelPicker) #
Multi-column scroll wheels with the same sheet chrome as date/duration pickers. Columns and values are maps keyed by index (not variadic generics):
CustomWheelPicker(
label: 'Line item',
wheelLayout: CustomWheelPickerWheelLayout.vertical, // or .horizontal
columns: {
0: CustomWheelPickerColumn.typed<int>(
options: [1, 2, 3, 5],
label: 'Qty',
valueToString: (v) => '$v',
),
1: CustomWheelPickerColumn.typed<String>(
options: ['S', 'M', 'L'],
label: 'Size',
),
},
value: {0: 2, 1: 'L'},
onChanged: (map) => setState(() => _selection = map),
wheelStyle: UnifiedFieldsDateWheelStyle(magnification: 1.15),
)
Standalone sheet: showCustomWheelPicker(context: context, columns: …, value: …).
Sheet chrome and modal behavior use the same APIs as list pickers (see Picker sheet chrome below): pickerSheetStyle, pickerSheetModalSettings, and nested basePickerSheetStyle / modalSettings.
Duration #
UnifiedDurationField/UnifiedFormDurationField— tap to open a duration sheet; optional manual edit of the formatted string.showUnifiedFieldsDurationPicker— imperative sheet API (also used internally by the field).
Picker style (UnifiedFieldsDurationPickerStyle) #
| Style | UI |
|---|---|
wheels (default) |
Unified scroll wheels (themed like date/time wheels) |
cupertino |
Legacy Cupertino-style H:M:S wheels |
Granularity vs custom columns #
Use granularity for the classic fixed shapes when you do not need year/month/week:
UnifiedDurationGranularity |
Columns | Display example |
|---|---|---|
hours |
hour | 05 |
hoursMinutes |
hour, minute | 05:30 |
hoursMinutesSeconds |
hour, minute, second | 05:30:45 |
minutesSeconds |
minute, second (minutes may exceed 59) | 90:00 |
Use pickerColumns when you need calendar-style units in a custom order (largest unit first, left → right on the wheel):
UnifiedDurationField(
label: 'Tenure',
pickerColumns: [
UnifiedFieldsDurationColumn.year,
UnifiedFieldsDurationColumn.week,
UnifiedFieldsDurationColumn.day,
UnifiedFieldsDurationColumn.hour,
],
// or: UnifiedFieldsDurationColumnPresets.yearsWeeksDaysHours
initialCalendarKind: UnifiedFieldsCalendarKind.jalali,
min: Duration.zero,
max: const Duration(days: 365 * 10),
)
UnifiedFieldsDurationColumn: year, month, week, day, hour, minute, second.
Presets on UnifiedFieldsDurationColumnPresets: yearsMonthsDays, yearsWeeksDaysHours, hoursMinutesSeconds, minutesSeconds.
When pickerColumns is set, it overrides granularity for both the picker and display format. Wheel ranges: year 0…999, month 0…11, week 0…4 (when used with coarser calendar columns). Totals still compose with year = 365 days, month = 30 days, week = 7 days per unit (standard day/hour/minute/second otherwise); the result is clamped to max on confirm.
Helpers: unifiedFormatDuration, unifiedTryParseDuration, composeUnifiedDuration, decomposeUnifiedDuration, formatUnifiedDurationColumns.
Pickers #
| Widget | Data |
|---|---|
UnifiedSinglePickerField<T> |
T? selection, searchable list, suggestions |
UnifiedMultiPickerField<T> |
List<T>, checkboxes in sheet |
UnifiedAsyncPickerField<T> |
Items from Future<List<T>> Function() |
UnifiedAsyncMultiPickerField<T> |
Same, multi-select |
PickerSheetWidget / MultiPickerSheetWidget are the sheet implementations (scrollable list + search). Every picker field and UnifiedForm*Picker* wrapper supports the same sheet customization:
| Parameter | Purpose |
|---|---|
valueToString |
Display label for field text, search, and default list rows |
itemToWidget |
Custom list row; default uses unifiedPickerResolveListItem → unifiedPickerDefaultItemWidget |
searchBuilder |
Custom searchable text per item (defaults to valueToString / unifiedPickerItemLabel) |
gridItemBuilder |
Custom grid tile; sheet uses GridView instead of a list |
gridDelegate |
Full SliverGridDelegate (max extent, fixed count, spacing, aspect ratio, …). Omit to use unifiedPickerDefaultGridDelegate() |
pickerHeaderStyle / pickerSheetStyle |
Per-field sheet chrome (helpWidget, itemOrder, background, …) |
pickerSheetModalSettings |
Per-field showModalBottomSheet flags (wins over pickerSheetStyle.modalSettings and theme) |
Picker sheet chrome (0.2.6) #
Shared bottom-sheet styling for list pickers, CustomWheelPicker, and related sheets.
UnifiedBasePickerSheetStyle — padding, colors, and radii:
| Property | Effect |
|---|---|
sheetBackgroundColor |
Sheet surface (header gaps + footer) |
sheetBorderRadius |
Modal shape and clip (all corners, including footer) |
contentPadding |
Padding around wheel / list body |
footerPadding |
Cancel / confirm row |
panelPadding |
Inside the wheel panel |
panelBackgroundColor |
Wheel panel fill (e.g. green content area) |
panelBorderRadius / panelBorderColor / panelBorderWidth |
Wheel panel border |
Set on UnifiedInputThemeData.basePickerSheetStyle or UnifiedPickerSheetStyle.basePickerSheetStyle.
UnifiedPickerSheetModalSettings — modal presentation:
| Property | Maps to |
|---|---|
isScrollControlled |
Full-height sheet |
isDismissible |
Tap outside / back |
enableDrag |
Drag-to-dismiss (defaults to isDismissible) |
useSafeArea |
System bottom inset |
showDragHandle |
Material drag handle |
Set on UnifiedInputThemeData.pickerSheetModalSettings, UnifiedPickerSheetStyle.modalSettings, or pickerSheetModalSettings on any picker field.
CustomWheelPicker(
label: 'Configuration',
pickerSheetStyle: UnifiedPickerSheetStyle(
basePickerSheetStyle: UnifiedBasePickerSheetStyle(
sheetBackgroundColor: Colors.blue,
sheetBorderRadius: BorderRadius.circular(16),
panelBackgroundColor: Colors.green,
panelBorderRadius: BorderRadius.circular(12),
contentPadding: EdgeInsets.zero,
),
modalSettings: UnifiedPickerSheetModalSettings(
isDismissible: false,
isScrollControlled: true,
),
),
columns: { /* … */ },
value: {0: 2, 1: 'Large'},
)
Precedence: field pickerSheetModalSettings → pickerSheetStyle.modalSettings → theme → package defaults (isScrollControlled / isDismissible / enableDrag / useSafeArea: true; showDragHandle: false).
Imperative API: showUnifiedFieldsPickerBottomSheet (used internally by list/multi pickers).
List row helpers (0.2.6) #
import 'package:unified_fields/unified_fields.dart';
// Label for field text and search
final label = unifiedPickerItemLabel(country, valueToString: (c) => c.name);
// Default list row when itemToWidget is omitted
itemToWidget: (c) => unifiedPickerDefaultItemWidget(
c,
valueToString: (x) => x.name,
),
// Or let the sheet resolve for you (itemToWidget ?? default widget)
unifiedPickerResolveListItem(
item,
itemToWidget: customBuilder,
valueToString: (x) => x.name,
);
Picker sheet header (0.2.3+ layout, 0.2.6 helpWidget) #
Reorder title, help, close, and clear with itemOrder. Help is a Widget (helpWidget); helpTextStyle from theme merges via DefaultTextStyle:
pickerHeaderStyle: UnifiedInputPickerHeaderStyle(
itemOrder: const [
UnifiedPickerHeaderItem.title,
UnifiedPickerHeaderItem.help,
UnifiedPickerHeaderItem.clear,
UnifiedPickerHeaderItem.close,
],
helpWidget: Text('Pick one option'),
),
Grid (single-select): builder receives (context, index, item, onSelect) — call onSelect to choose and close.
Grid (multi-select): builder receives (context, index, item, isSelected, onSelect) — onSelect toggles selection; user confirms in the sheet footer.
UnifiedMultiPickerField<String>(
label: 'Tags',
values: selected,
items: allTags,
gridDelegate: unifiedPickerDefaultGridDelegate(crossAxisCount: 3, childAspectRatio: 1.2),
gridItemBuilder: (context, index, item, isSelected, onSelect) => FilterChip(
label: Text(item),
selected: isSelected,
onSelected: (_) => onSelect(),
),
)
Form: use UnifiedFormMultiPickerField (or any other UnifiedForm*Picker*) with the same parameters. Imperative open: fieldController.openPicker(context) or pickerController.openPicker(context) on customizable controllers after bindPicker.
Standalone sheets: showUnifiedSinglePickerSheet / showUnifiedMultiPickerSheet.
Customizable APIs (unified_cutomizable_picker_fields.dart — filename keeps the historical typo cutomizable): single or multi selection with CustomizableSinglePickerController / CustomizableMultiPickerController plus async siblings for remote data. Each controller carries either typed text or the selected value(s); the matching UnifiedFormCustomizable… wrappers expose resetValue snapshots so FormState.reset restores both the mode and the payload in one step.
Phone (UnifiedPhoneField) #
International phone input with SVG flags (bundled under assets/flags/countries/), optional dial-code segment, national mask (# = digit), and a default phone suffix icon.
Countries #
Countries are a fixed UnifiedCountry enum (~250 entries) with ISO code, display name, and dial code:
UnifiedCountry.ir.isoCode // IR
UnifiedCountry.ir.dialCode // +98
UnifiedCountries.byIso('DE')
UnifiedCountries.matchDialCode('+98912…')
UnifiedCountries.defaults // all enum values (picker / field default list)
India uses UnifiedCountry.countryIN (in is a reserved Dart name). Regenerate the enum after editing tool/countries.json:
dart run tool/generate_unified_countries.dart
Field usage #
UnifiedPhoneField(
label: 'Mobile',
placeholder: '912 123 4567',
labelMode: UnifiedFieldLabelMode.labelInColumn,
usePersianDigits: true, // or digitCalendarKind: UnifiedFieldsCalendarKind.jalali
fixedCountry: UnifiedCountry.ir, // optional: lock dial code
// showCountryCodeSection: true,
// editableCountryCode: true, // one field: + dial + national
fieldController: UnifiedPhoneFieldController(
initialCountry: UnifiedCountry.ir,
onChanged: (UnifiedPhoneNumber? n) => debugPrint(n?.e164),
),
)
| Mode | Behavior |
|---|---|
| Editable dial code (default when code section is shown) | Single input: + + country code + masked national digits |
Fixed country (fixedCountry) |
Flag + dial label + national digits only |
| Non-editable code section | Dial label (tap opens picker) + national field |
Theme phone chrome via UnifiedInputPhoneStyle / UnifiedInputThemeData.phoneStyle (dial-code box, flag size, invalid-code highlight vs message).
Standalone widgets #
UnifiedCountryWidget(country: UnifiedCountry.ae) // flag only (showName defaults to false)
UnifiedCountryWidget(country: UnifiedCountry.ir, showName: true, showDialCode: true)
UnifiedFlag(code: 'IR', size: 28)
final picked = await showUnifiedPhoneCountryPicker(
context: context,
countries: UnifiedCountries.defaults,
usePersianDigits: true,
);
UnifiedPhoneNumber exposes country, nationalDigits, e164, display, and localizedDisplay / localizedE164 when using Persian digits.
Bindings with UnifiedInputPicker #
UnifiedInputPicker<T> is a ChangeNotifier holding value, optional errorText, and helpers clear, setError, silentSetValue. Pass it as binding: on fields so UI and domain state stay in sync; form wrappers also write back on save/reset when configured.
Form-aware pickers and date/time fields listen to the binding, so clearing from code updates the visible field:
final country = UnifiedInputPicker<String?>();
UnifiedFormSinglePickerField<String?>(
label: 'Country',
binding: country,
items: countries,
);
// Later — UI and FormField both clear:
country.clear();
You can use binding and fieldController together: the binding holds app state; the typed controller adds picker-specific APIs (openPicker, displayText, sheet options).
AppInputControllerwas renamed in 0.1.4; a deprecated typedef points toUnifiedInputPicker.
Field controllers #
BaseUnifiedFieldController<T> (and typed subclasses) mirror TextEditingController for pickers, dates, numbers, and duration. Pass fieldController: on the matching Unified… widget.
| Controller | Field(s) |
|---|---|
UnifiedPickerFieldController<T> |
UnifiedSinglePickerField, UnifiedAsyncPickerField |
UnifiedMultiPickerFieldController<T> |
UnifiedMultiPickerField, UnifiedAsyncMultiPickerField |
UnifiedDateFieldController |
UnifiedDateField |
UnifiedDateRangeFieldController |
UnifiedDateRangeField |
UnifiedTimeOfDayFieldController |
UnifiedTimeOfDayField |
UnifiedDurationFieldController |
UnifiedDurationField |
UnifiedNumberFieldController |
UnifiedNumberField, UnifiedNumericStepField |
When the field is mounted, openPicker(context) (and async openPickerAsync) use the same code path as a tap—no need to pass items / label again. Off-tree or tests: call bindPicker / bindPickerLabel first, or pass items and label explicitly.
requestFocus() on the controller (or on a binding that shares the attached focus node) focuses the wired field.
UnifiedFormController coordinates multiple controllers for validate/save/reset on a custom form layout.
Field states & label layout #
Shared by UnifiedBaseTextField and every field built on it:
| Parameter | Effect |
|---|---|
isDisabled / disabled |
Read-only look; when both placeholder and value exist, both are shown (not hidden like a greyed empty field). |
locked |
Blocks interaction; distinct from disabled styling. |
loading |
Small suffix spinner (replaces dropdown/chevron); field does not use full-field overlay or muted disabled colors. |
interactionBlocked |
Absorbs pointer events without disabled chrome—used for pick-only surfaces (date, async picker while idle). |
labelMode / labelInRow |
UnifiedFieldLabelMode: floatingLabel (default), labelInColumn, or labelInRow (label + body in one bordered row with a vertical divider). Legacy labelInRow: true on decoration maps to row mode. |
Async pickers set loading: true while itemProvider runs; date fields use interactionBlocked: true so the platform picker still opens without disabled: true. UnifiedPhoneField supports the same label modes plus optional height / width.
UI strings (UnifiedFieldsStrings) #
All built-in button and sheet copy (Cancel, Confirm, Clear, Done, Pick, Suggestion, date-picker labels, time hour/minute labels, default duration title) comes from UnifiedFieldsStrings.instance. Override once at startup:
void main() {
UnifiedFieldsStrings.instance = const UnifiedFieldsStrings(
cancel: 'لغو',
confirm: 'تأیید',
clear: 'پاک کردن',
pickPrefix: 'انتخاب',
);
runApp(const MyApp());
}
Multi-picker titles use multiPickerTitle(label) (default "Pick $label"). UnifiedDatePickerStrings is deprecated and reads from the same instance.
Theming & layout #
UnifiedColors— default static palette used by built-in styles (replace over time with your design tokens or override viaUnifiedInputDecoration/Theme).resolveUnifiedDecoration(context, overrides: …, brightness: …)— merges theme + field overrides (used internally by several fields).UnifiedFieldsContextXonBuildContext:unifiedFieldsScreenWidth,unifiedFieldsScreenHeight,unifiedFieldsUseDialogLayout,unifiedFieldsPrimaryColor(prefixed to avoid clashing with app extensions).UnifiedSheetButton— compact primary / outlined actions used inside picker sheets.
Global theme (UnifiedInputThemeScope) #
Only child is required. Pass a UnifiedInputThemeData to set shared chrome for every unified field in that subtree (labels, values, placeholders, required icon, errors, suffix icons, picker sheets).
App-wide (recommended) #
Wrap runApp so settings screens and forms inherit the same rules:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
UnifiedInputThemeScope(
data: const UnifiedInputThemeData(
requiredIconColor: Color(0xFF1565C0),
requiredIconSize: 9,
validationColor: Color(0xFFD32F2F),
disabledFieldOpacity: 0.38,
placeholderOpacityWhenDisabled: 0.38,
pickerSheetBackgroundColor: Color(0xFFF5F7FA),
defaultSuffixIcons: UnifiedInputDefaultSuffixIcons(
date: Icons.calendar_month_outlined,
time: Icons.access_time,
duration: Icons.timelapse_outlined,
picker: Icons.unfold_more,
),
pickerHeaderStyle: UnifiedInputPickerHeaderStyle(
padding: EdgeInsets.fromLTRB(16, 14, 8, 14),
backgroundColor: Colors.white,
helpWidget: Text('Optional hint'),
),
datePickerStyle: UnifiedInputDatePickerStyle(
daySelectedBackgroundColor: Color(0xFF1565C0),
shamsiTextStyle: TextStyle(fontFamily: 'KookFaNum'),
),
multiPickerCheckboxStyle: UnifiedInputMultiPickerCheckboxStyle(
borderRadius: 6,
fillColor: Color(0xFF1565C0),
checkColor: Colors.white,
),
),
child: const MyApp(),
),
);
}
The runnable example/ app uses this pattern in example/lib/main.dart.
Nested scope (one screen or card) #
Inner scopes merge over ancestors — useful for a settings subsection or A/B styling:
UnifiedInputThemeScope(
data: const UnifiedInputThemeData(
requiredIconColor: Colors.orange,
disabledFieldColor: Colors.grey,
disabledFieldOpacity: 0.5,
),
child: Column(
children: [
UnifiedFormTextField(
label: 'Nickname',
isRequired: true, // orange * from this scope
),
UnifiedFormTextField(
label: 'Archived',
isDisabled: true,
resetValue: () => 'Read-only',
),
],
),
)
Common UnifiedInputThemeData fields #
| Field | Effect |
|---|---|
brightnessOverride / paletteOverride |
Force light/dark or a full custom palette |
disabledLabelColor / disabledLabelOpacity |
Label when isDisabled / disabled |
disabledFieldColor / disabledFieldOpacity |
Input text when disabled |
disabledFieldBackgroundOpacity |
Field body fade when disabled |
lockedLabelColor / lockedLabelOpacity |
Label when locked |
lockedFieldColor / lockedFieldOpacity |
Input text when locked |
lockedFieldBackgroundOpacity |
Body fade when locked |
placeholderColor / placeholderOpacity |
Hint when enabled |
placeholderOpacityWhenDisabled |
Hint when disabled |
requiredIcon / requiredIconColor / requiredIconSize |
Required * marker |
validationColor |
Inline errors and error border |
clearButtonColor |
Clear (×) button |
suffixIconColor / suffixIconOpacity |
Lock, password, default picker suffixes |
loadingIndicatorColor |
Loading spinner on fields |
pickerSheetBackgroundColor |
Date/time/duration sheets (else bottomSheetTheme → palette) |
basePickerSheetStyle |
UnifiedBasePickerSheetStyle: sheet/panel padding, radii, colors (list + wheel pickers) |
pickerSheetModalSettings |
UnifiedPickerSheetModalSettings: isScrollControlled, isDismissible, enableDrag, useSafeArea, showDragHandle |
pickerHeaderStyle |
UnifiedInputPickerHeaderStyle: padding, backgroundColor, titleStyle, helpWidget, helpTextStyle, itemOrder, closeButton, clearButton |
multiPickerCheckboxStyle |
UnifiedInputMultiPickerCheckboxStyle: size, borderRadius, fillColor, checkColor, borderColor |
fieldDecorationSet |
Default per-state layers (focused, error, disabled, …) for all fields in the scope |
fieldDefaults |
Default [UnifiedBaseTextField] layout/behavior: labelMode, height, borderRadius, textStyle / textStylePersian, placeholderStyle, per-mode labelInRowStyle / labelInColumnStyle / floatingLabelStyle, selectTextOnFocus, showClearButton, autovalidateMode, … |
datePickerStyle |
[UnifiedInputDatePickerStyle]: sheet background, day/month/year colors, font sizes, toggles, buttons, optional shamsiTextStyle; includes optional wheelStyle |
defaultSuffixIcons |
Default suffix per field type (date, time, duration, picker, …) |
Field-level UnifiedInputDecoration / decorationSet / fieldDefaults and per-widget params still win for one-off overrides.
UnifiedInputThemeScope(
data: UnifiedInputThemeData(
fieldDefaults: const UnifiedInputFieldDefaults(
labelMode: UnifiedFieldLabelMode.labelInColumn,
height: 52,
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
textStylePersian: TextStyle(fontSize: 16, fontFamily: 'KookFaNum'),
placeholderStyle: TextStyle(fontSize: 15, color: Colors.grey),
labelInColumnStyle: UnifiedInputLabelModeStyle(
labelPadding: EdgeInsets.only(bottom: 6, top: 4),
labelStyle: TextStyle(fontWeight: FontWeight.w600),
),
showClearButton: true,
),
fieldDecorationSet: UnifiedInputDecorationSet(
focused: UnifiedInputDecoration(
borderSide: BorderSide(color: Colors.blue, width: 1.5),
),
),
),
child: myForm,
)
Live demos: example/lib/main.dart (app + nested card) and UnifiedInputsShowcasePage (scoped block near the top of the gallery).
Persian digits (UnifiedFieldsTypography) #
Shamsi (Jalali) date pickers show Persian numerals (۰–۹) using the bundled KookFaNum font. Override at startup:
void main() {
UnifiedFieldsTypography.instance = const UnifiedFieldsTypography(
usePersianDigitsGlobally: true, // all unified text / number fields
// persianFontFamily: 'YourAppNumFont', // optional
);
runApp(const MyApp());
}
By default only Shamsi UI uses Persian digits (usePersianDigitsInShamsi: true). Set usePersianDigitsGlobally: true to apply the font and digit mapping to every field (including UnifiedBaseTextField, numeric step fields, duration wheels, and UnifiedPhoneField).
On the phone field, set usePersianDigits: true or digitCalendarKind: UnifiedFieldsCalendarKind.jalali to localize dial codes and national digits (input accepts ۰–۹). Use UnifiedCountry.localizedDialCode / UnifiedPhoneNumber.localizedDisplay outside the field.
Localization #
- Set
UnifiedFieldsStrings.instancefor package-owned copy (pickers, date sheet, duration sheet, time picker hour/minute fallbacks, wheel headers including Shamsi سال/ماه/روز, duration column headers viadurationColumnHeader). - Time picker OK/Cancel use
MaterialLocalizationswhen available. - Clear tooltip on the base text field uses
MaterialLocalizations.deleteButtonTooltip(platform).
Try the built-in demo #
UnifiedInputsShowcasePage is a long-form scroll demo with a brightness / palette toggle. Push it from debug menus or a settings screen:
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const UnifiedInputsShowcasePage(),
),
);
A runnable example app lives in example/ — open it with flutter run from that folder. It wraps the app in UnifiedInputThemeScope, shows a nested-scope demo card on the home form, and opens UnifiedInputsShowcasePage from the app bar (includes a dedicated theme-scope section).
Dependencies #
| Package | Role |
|---|---|
flutter_svg |
Country flag SVGs in UnifiedFlag / phone field |
intl |
Date/number formatting in date fields and picker |
shamsi_date |
Jalali calendar conversion |
collection |
Used by the vendored scrollable list implementation |
Async pickers show loading via the field suffix spinner (loading on UnifiedBaseTextField), not a full-field overlay.
Vendored code #
Under lib/src/scrollable_list/ you’ll find a local copy of a scrollable positioned list implementation (same family as the popular package, kept in-tree so this package stays self-contained).
Architecture sketch #
flowchart TB
subgraph form [Form]
UFS[UnifiedFormFieldScope optional]
UFT[UnifiedFormTextField]
UFS2[UnifiedFormSinglePickerField]
UFD[UnifiedFormDateField]
end
UFS --> UFT
UFS --> UFS2
UFS --> UFD
form --> FF[FormState validate save reset]
Version #
Current release: 0.2.6 (see pubspec.yaml and pub.dev for the latest). Follow semver when upgrading.
Upgrading to 0.2.6 #
From 0.2.5 or earlier:
UnifiedInputDatePickerStyle— setUnifiedInputThemeData.datePickerStyleor per-fielddatePickerStyleonUnifiedDateField,UnifiedDateRangeField,UnifiedFormDateField,UnifiedDateFieldController, andshowUnifiedFieldsDatePicker/showUnifiedFieldsDatePickerRange. Customize sheet background, day/month/year colors, font sizes, toggles, buttons,cellHeight,dayCircleSize, nestedwheelStyle, andyearStripStylefor the horizontal year strip.shamsiTextStyle— optional onUnifiedInputDatePickerStyle; merged when the picker is in Jalali mode (Persian month names / numerals). Use forfontFamily(e.g. KookFaNum) liketextStylePersianon fields.CustomWheelPicker— multi-column wheel field (columns/valuemaps, vertical or horizontal layout). UseCustomWheelPickerColumn.typedper column; sheet viashowCustomWheelPicker.UnifiedBasePickerSheetStyle— theme orpickerSheetStyle.basePickerSheetStylefor sheet background,sheetBorderRadius, content/footer/panel padding, and wheel panel colors.UnifiedPickerSheetModalSettings— theme,pickerSheetStyle.modalSettings, orpickerSheetModalSettingson picker fields forisScrollControlled,isDismissible,enableDrag,useSafeArea,showDragHandle.- Picker list helpers —
unifiedPickerItemLabel,unifiedPickerDefaultItemWidget,unifiedPickerResolveListItem. Sheets respectvalueToStringfor default rows; passvalueToStringon fields andshowUnifiedSinglePickerSheet/showUnifiedMultiPickerSheet. - Breaking: picker
helpText→helpWidget— onUnifiedInputPickerHeaderStyleandUnifiedPickerSheetHeader, replacehelpText: '…'withhelpWidget: Text('…').helpTextStylestill applies. UnifiedNumberField/UnifiedNumericStepField— themefieldDefaults.textStylenow applies (no hardcoded dark text color). Optional per-fieldstyleonUnifiedNumberField.
If you are on 0.2.4 or below, also read Upgrading to 0.2.5 (textStyle on phone/date fields).
Upgrading to 0.2.5 #
textStyle/textStylePersian— now apply toUnifiedPhoneField,UnifiedDateField, andUnifiedDateRangeFieldviafieldDefaults(no per-field hardcoded 14px on dates). Persian mode merges both styles; override one field withstyle:on the widget orUnifiedInputDecoration.fieldStyle.UnifiedDateRangeField— optionalinitialCalendarKindfor Jalali digit font / Persian value style when displaying ranges.
Upgrading to 0.2.4 #
placeholderStyle— onUnifiedInputDecoration,UnifiedBaseTextField, orUnifiedInputFieldDefaults. Placeholders copyfieldStyletypography by default; setplaceholderStyleonly to differ (e.g. italic or lighter color).- Label chrome per mode —
labelInRowStyle,labelInColumnStyle,floatingLabelStyleonUnifiedInputFieldDefaults(UnifiedInputLabelModeStylewithlabelStyle+labelPadding). Per-field:UnifiedInputDecoration.labelPadding/UnifiedBaseTextField.labelPadding. selectTextOnFocus—trueonUnifiedTextField,UnifiedNumberField, form wrappers, orfieldDefaultsso focusing selects the whole value for easy overwrite.
Upgrading to 0.2.3 #
- Picker sheets — override sheet background and header per field with
pickerSheetBackgroundColor,pickerHeaderStyle(e.g.itemOrder,helpWidget), or bundledpickerSheetStyle. Works on standalone pickers andUnifiedForm*Picker*wrappers without changing global theme.
Upgrading to 0.2.2 #
- Number fields — optional
stepButtons,stepButtonPlacement,decrementIcon,incrementIcon, andtextAlign. UseUnifiedInputDecorationprefix/prefixIcon/suffixIconalongside step buttons; setsuffixWidth/suffixHeightonly when you need a fixed suffix box (otherwise custom suffixes size intrinsically).
Upgrading to 0.2.0 #
- Theme field defaults — set
UnifiedInputThemeData.fieldDefaults(labelMode,height, borders,showClearButton,autovalidateMode, …). See Global theme. UnifiedBaseTextField— some params are nullable and inherit from theme;isValid(context)now needs aBuildContext.
Upgrading to 0.1.9 #
- Per-state decoration — optional
decorationSeton any field, orUnifiedInputThemeData.fieldDecorationSetinUnifiedInputThemeScope. Layers:focused,error,valid,locked,disabled,loading,readOnly,base. See Per-state decoration.
Upgrading to 0.1.8 #
- Picker grid / custom list — pass
itemToWidget,gridItemBuilder, andgridDelegateon any picker field orUnifiedForm*Picker*wrapper; see Pickers. Helpers:unifiedPickerDefaultGridDelegate,showUnifiedSinglePickerSheet,showUnifiedMultiPickerSheet. CustomizableSinglePickerController.openPicker/CustomizableMultiPickerController.openPicker— call after the field is mounted, orbindPicker/bindAsyncPickerfirst.- Customizable async/sync pickers: full-field tap opens the sheet when
allowFreeTextis true; usedisabledorisDisabledto block interaction.
Upgrading from 0.1.6 → 0.1.7 #
UnifiedPhoneField,UnifiedCountryenum,UnifiedFlag, andUnifiedCountryWidget— see Phone. Replace anyUnifiedPhoneCountry(...)constructor with enum values (e.g.UnifiedCountry.ir; India:UnifiedCountry.countryIN).UnifiedPhoneCountries→UnifiedCountries.UnifiedFieldLabelModeon fields; legacy decorationlabelInRow: truestill maps to row mode.
Upgrading from earlier 0.1.x #
- Wrap your app (or a screen) in
UnifiedInputThemeScopefor global disabled/placeholder/required/validation colors, picker sheet background, header padding, and multi-picker checkbox styling — see Global theme. - Replace
context.isDesktop/context.widthwithcontext.unifiedFieldsUseDialogLayout/context.unifiedFieldsScreenWidth(old names are deprecated). - Duration wheels default to
UnifiedFieldsDurationPickerStyle.wheels; usepickerColumnsfor year/month/week-style columns. unifiedFormatDuration/unifiedTryParseDurationuse namedgranularity:and optionalpickerColumns:/calendarKind:.- Shamsi dates / phone: set
initialCalendarKind: jalaliorusePersianDigitson the phone field; optionalUnifiedFieldsTypographyfor app-wide Persian digits (KookFaNum font).
License #
See the LICENSE file in this repository.