flutterx_forms 1.4.6 copy "flutterx_forms: ^1.4.6" to clipboard
flutterx_forms: ^1.4.6 copied to clipboard

Create, validate and manage all aspects of your forms in a new simple way. :D :D :D

example/lib/main.dart

import 'package:cross_file/cross_file.dart';
import 'package:flutter/material.dart' hide DialogRoute;
import 'package:flutterx_application/flutterx_application.dart';
import 'package:flutterx_application/flutterx_ui.dart';
import 'package:flutterx_forms/flutterx_forms.dart';

class Labels extends LabelInterface {
  const Labels() : super(locale: const Locale('it', 'IT'));
}

void main() => runApplication(
  name: 'Flutterx Forms Demo',
  initialize: (context) => FormsExample.route,
  locale: () => LocaleData(labels: {const Labels()}, onLabel: (_) {}),
  routes: {FormsExample.route},
  theme: ThemeData(primarySwatch: Colors.green).copyWith(
    outlinedButtonTheme: OutlinedButtonThemeData(
      style: OutlinedButton.styleFrom(
        iconSize: 20,
        shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
      ),
    ),
    inputDecorationTheme: InputDecorationTheme(
      border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(8))),
      focusedBorder: OutlineInputBorder(
        borderRadius: const BorderRadius.all(Radius.circular(12)),
        borderSide: BorderSide(color: Colors.deepPurple.shade400, width: 3),
      ),
    ),
  ),
);

class FormsExample extends StatefulWidget {
  static final ActivityRoute<void> route = ActivityRoute(
    location: '/index',
    builder: (context, args) => const FormsExample._(),
  );

  const FormsExample._();

  @override
  State<FormsExample> createState() => _FormsExampleState();
}

class _FormsExampleState extends State<FormsExample> with FormController {
  late final XFormField<String> _name = field();
  late final XFormField<String> _surname = field();
  late final XFormField<XFile?> _file = field();
  late final XFormField<List<XFile>> _files = field();
  late final XFormField<int?> _age = field();
  late final XFormField<double?> _x = field();
  late final XFormField<XFile> _id = field();
  late final XFormField<List<SampleOption>> _sampleOptions = field();
  late final XFormField<List<SampleOption>> _sampleOptions2 = field();
  late final XFormField<List<String>> _tags = field();
  late final XFormField<List<DateAndTime>> _dates = field();
  bool _densify = false;
  bool _enabled = true;

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    return Scaffold(
      appBar: AppBar(title: const Text('Forms example')),
      body: ResponsiveScreenContent(
        child: Card(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(m12),
            child: buildForm(
              children: [
                Row(
                  children: [
                    ElevatedButton(
                      onPressed: () => setState(() => _densify = !_densify),
                      child: Text('Densify: $_densify'),
                    ),
                    ElevatedButton(
                      onPressed: () => setState(() => _enabled = !_enabled),
                      child: Text('Enabled: $_enabled'),
                    ),
                  ],
                ),
                const SizedBox(height: m24),
                SizedBox.square(
                  dimension: 92,
                  child: CustomContentInputDecorator(
                    backgroundColor: colorScheme.surfaceContainerLow,
                    decoration: InputDecoration(labelText: 'Picture', enabled: _enabled, isDense: _densify),
                    child: SizedBox.expand(
                      child: InkWell(onTap: () {}, child: const Icon(Icons.add, size: 32)),
                    ),
                  ),
                ),
                const SizedBox(height: m12),
                XFileFormField(
                  field: _file,
                  enabled: _enabled,
                  decoration: InputDecoration(labelText: 'File', hintText: 'Drag & Drop file here', isDense: _densify),
                  onChanged: print,
                  validator: validateFirst([
                    nonNull(() => 'must not be null'),
                    (file) {
                      print('AA ${file?.mimeType}');
                      return matchesMime(file!.mimeType, {'image/*'}) ? null : 'Expected an image';
                    },
                  ]),
                ),
                const SizedBox(height: m12),
                XFilesFormField(
                  field: _files,
                  enabled: _enabled,
                  decoration: InputDecoration(
                    labelText: 'Files',
                    hintText: 'Drag & Drop files here',
                    isDense: _densify,
                  ),
                  onChanged: print,
                  validator: listNonEmpty(() => 'must non be empty'),
                ),
                const SizedBox(height: m12),
                XTextFormField(
                  field: _name,
                  validator: nonEmpty(() => 'Field should not be empty'),
                  textInputAction: TextInputAction.next,
                  enabled: _enabled,
                  initialValue: 'Paolo',
                  keyboardType: TextInputType.text,
                  textCapitalization: TextCapitalization.words,
                  style: const TextStyle(fontSize: 16),
                  decoration: InputDecoration(isDense: _densify, labelText: 'Nome', hintText: 'Giuseppe'),
                ),
                const SizedBox(height: m12),
                XTextFormField(
                  field: _surname,
                  enabled: _enabled,
                  initialValue: 'Bitta',
                  validator: nonEmpty(() => 'Field should not be empty'),
                  textInputAction: TextInputAction.done,
                  keyboardType: TextInputType.text,
                  textCapitalization: TextCapitalization.words,
                  style: const TextStyle(fontSize: 16),
                  decoration: InputDecoration(isDense: _densify, labelText: 'Surname', hintText: 'Simone'),
                ),
                const SizedBox(height: m12),
                XIntFormField(
                  field: _age,
                  enabled: _enabled,
                  initialValue: 34,
                  validator: validateFirst([
                    nonNull(() => 'Field should not be null'),
                    gte(18, () => 'Field must ve >=18'),
                  ]),
                  textInputAction: TextInputAction.next,
                  decoration: InputDecoration(isDense: _densify, labelText: 'Età', hintText: '18'),
                ),
                const SizedBox(height: m12),
                XDoubleFormField(
                  field: _x,
                  min: -2.24,
                  enabled: _enabled,
                  max: 56,
                  initialValue: 56,
                  validator: validateFirst([
                    nonNull(() => 'Field should not be null'),
                    gte(3, () => 'Field must ve >=3'),
                  ]),
                  textInputAction: TextInputAction.done,
                  arrows: const NumberFieldArrows(step: .5),
                  decoration: InputDecoration(isDense: _densify, labelText: 'xyz', hintText: '0.4'),
                ),
                const SizedBox(height: m12),
                XSetFormField<SampleOption>(
                  field: _sampleOptions,
                  items: SampleOption.values,
                  enabled: _enabled,
                  decoration: InputDecoration(labelText: 'Options', isDense: _densify),
                  initialValue: const [SampleOption.option2],
                  validator: (value) => (value?.length ?? 0) < 2 ? 'Min 2 elements' : null,
                  emptyBuilder: (_) => const Padding(padding: EdgeInsets.all(8), child: Text('No item')),
                  itemInitialValue: (items, _) => items.first,
                  addButtonText: 'Add option',
                  firstItemSpacing: 12,
                  removeButtonPadding: EdgeInsetsGeometry.directional(start: 4, top: _densify ? 0 : 2),
                  itemBuilder: (state, field, initialValue, index, items) => XDropdownMenuFormField<SampleOption>(
                    field: field,
                    items: items,
                    enabled: state.widget.enabled,
                    initialValue: initialValue,
                    decoration: InputDecoration(
                      labelText: 'Options ${index + 1}',
                      suffixIcon: const Icon(Icons.arrow_drop_down),
                      isDense: _densify,
                    ),
                    validator: (item) {
                      final expected = SampleOption.values[index];
                      return item == expected ? null : 'Option $expected is only valid answer';
                    },
                    menuEntryBuilder: menuEntryByLabel((item) => item.name),
                  ),
                ),
                const SizedBox(height: m12),
                XTextListFormField(
                  field: _tags,
                  enabled: _enabled,
                  decoration: InputDecoration(isDense: _densify, labelText: 'Tags'),
                  validator: listUnique(() => 'Contains duplicate'),
                ),
                const SizedBox(height: m12),
                XListFormField<DateAndTime>(
                  field: _dates,
                  addButtonText: 'Add date',
                  enabled: _enabled,
                  emptyBuilder: (_) => const Padding(padding: EdgeInsets.all(8), child: Text('No item')),
                  itemInitialValue: (values, index) => DateAndTime(date: DateTime.now(), time: TimeOfDay.now()),
                  removeButtonPadding: EdgeInsets.only(left: 4, top: _densify ? 26 : 30),
                  itemBuilder: (state, field, initialValues, index) => ProxyFormField<DateAndTime>(
                    field: field,
                    enabled: state.widget.enabled,
                    decoration: InputDecoration(
                      labelText: 'Data ${index + 1}',
                      isDense: _densify,
                      contentPadding: const EdgeInsets.only(left: 12, top: 8, bottom: 8),
                      errorBorder: SingleSideInputBorder(
                        side: GeometrySide.left,
                        borderSide: BorderSide(color: colorScheme.error, width: 4),
                      ),
                      enabledBorder: const SingleSideInputBorder(
                        side: GeometrySide.left,
                        borderSide: BorderSide(width: 4),
                      ),
                      disabledBorder: const SingleSideInputBorder(
                        side: GeometrySide.left,
                        borderSide: BorderSide(color: Colors.black45, width: 4),
                      ),
                    ),
                    initialValue: initialValues[index],
                    computeFieldData: (fields) => DateAndTime(date: fields['date'], time: fields['time']),
                    builder: (state, fields) => Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const SizedBox(height: m8),
                        XDateTimeFormField(
                          field: fields.field('date'),
                          initialValue: DateTime.now(),
                          enabled: state.widget.enabled,
                          selectableDayPredicate: (date) => date.day.isEven,
                          decoration: InputDecoration(isDense: _densify, labelText: 'Date'),
                          validator: validateFirst([
                            nonNull(() => 'Date required'),
                            (date) => date!.day % 5 == 0 ? null : 'Must be multiple of 5',
                          ]),
                        ),
                        const SizedBox(height: m12),
                        XTimeFormField(
                          field: fields.field('time'),
                          enabled: state.widget.enabled,
                          initialValue: TimeOfDay.now(),
                          decoration: InputDecoration(isDense: _densify, labelText: 'Hour'),
                          validator: validateFirst([nonNull(() => 'Hour required')]),
                        ),
                      ],
                    ),
                  ),
                ),
                const SizedBox(height: m12),
                XListTabsFormField<SampleOption>(
                  field: _sampleOptions2,
                  enabled: _enabled,
                  decoration: InputDecoration(
                    isDense: _densify,
                    contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: -3),
                  ),
                  physics: null,
                  initialValue: const [SampleOption.option2],
                  itemInitialValue: (items, _) => items.firstOrNull ?? SampleOption.option1,
                  minLength: 1,
                  itemLabelBuilder: (fieldId, index) => 'Field ${index + 1}',
                  itemBuilder: (state, field, values, index) => Padding(
                    padding: const EdgeInsets.all(m12),
                    child: ProxyFormField<SampleOption>(
                      field: field,
                      enabled: state.widget.enabled,
                      initialValue: values[index],
                      computeFieldData: (fields) => fields['test'],
                      builder: (state, fields) => XDropdownMenuFormField<SampleOption>(
                        field: fields.field('test'),
                        items: SampleOption.values,
                        enabled: state.widget.enabled,
                        initialValue: values[index],
                        decoration: InputDecoration(
                          labelText: 'Options ${index + 1}',
                          suffixIcon: const Icon(Icons.arrow_drop_down),
                          isDense: _densify,
                        ),
                        validator: (item) {
                          final expected = SampleOption.values[index];
                          return item == expected ? null : 'Option $expected is only valid answer';
                        },
                        menuEntryBuilder: menuEntryByLabel((item) => item.name),
                      ),
                    ),
                  ),
                ),
                XListTabsFormFieldWidget(
                  enabled: _enabled,
                  fields: const ['Field 1', 'Field 2'],
                  itemLabelBuilder: (fieldId, index) => fieldId,
                  itemBuilder: (context, fieldId, index) =>
                      Padding(padding: const EdgeInsets.all(8), child: Text('Hello $fieldId')),
                ),
                const SizedBox(height: 500),
              ],
            ),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(onPressed: submitForm, child: const Icon(Icons.add)),
    );
  }

  @override
  void onFormSubmit() {
    DialogRoute(
      location: '/result',
      builder: (context, args) => AlertDialog(
        actions: const [AlertDialogAction.positive()],
        content: Text(
          'Result: \n'
          '    name: ${_name.value}\n'
          '    surname: ${_surname.value}\n'
          '    options: ${_sampleOptions.value}\n'
          '    age: ${_age.valueOrNull}\n'
          '    id: ${_id.valueOrNull?.name}\n',
        ),
      ),
    ).open(context);
  }
}

enum SampleOption { option1, option2, option3 }

@immutable
class DateAndTime {
  final DateTime? date;
  final TimeOfDay? time;

  const DateAndTime({required this.date, required this.time});
}
2
likes
0
points
1.17k
downloads

Publisher

unverified uploader

Weekly Downloads

Create, validate and manage all aspects of your forms in a new simple way. :D :D :D

Repository (GitLab)
View/report issues

License

unknown (license)

Dependencies

collection, cross_file, file_selector, flutter, flutterx_utils, intl, mime, web

More

Packages that depend on flutterx_forms