flutterx_forms 1.5.2 copy "flutterx_forms: ^1.5.2" to clipboard
flutterx_forms: ^1.5.2 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';
import 'package:flutterx_utils/flutterx_utils.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<bool> _bool = field();
  late final XFormField<bool> _bool1 = 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();
  late final XFormField<List<String>> _texts = 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'),
                    ),
                    ElevatedButton(onPressed: resetForm, child: const Text('Reset')),
                  ],
                ),
                const SizedBox(height: m24),
                XListFormField<String>(
                  field: _texts,
                  addButtonText: 'Add date',
                  enabled: _enabled,
                  reorderable: true,
                  emptyBuilder: (_) => const Padding(padding: EdgeInsets.all(8), child: Text('No item')),
                  itemInitialValue: (values, index) => 'Value ${index + 1}',
                  removeButtonPadding: const EdgeInsets.only(left: 4, top: 4),
                  itemBuilder: (state, field, initialValues, index) =>
                      XTextFormField(field: field, initialValue: initialValues[index]),
                ),
                const SizedBox(height: m24),
                SizedBox(
                  width: 92,
                  height: 92,
                  child: CustomContentInputDecorator(
                    decoration: InputDecoration(labelText: 'Picture', enabled: _enabled, isDense: _densify),
                    child: SizedBox(
                      width: 92,
                      height: 92,
                      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),
                XBoolFormField.checkbox(
                  field: _bool,
                  enabled: _enabled,
                  titleText: 'Check this',
                  decoration: InputDecoration(
                    labelText: 'Check',
                    prefixIcon: const Icon(Icons.emergency),
                    prefixIconConstraints: const BoxConstraints(maxHeight: 24, minWidth: 48),
                    isDense: _densify,
                  ),
                  validator: (value) => value ? null : 'Must accept terms & conditions',
                ),
                const SizedBox(height: m12),
                XBoolFormField.switchbox(
                  field: _bool1,
                  enabled: _enabled,
                  titleText: 'Check this',
                  decoration: InputDecoration(labelText: 'Check', isDense: _densify),
                  validator: (value) => value ? null : 'Must accept terms & conditions',
                ),
                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,
                  computeErrorText: (errors) => 'Errors: ${errors.map((error) => error.errorText).join('\n')}',
                  decoration: InputDecoration(labelText: 'Options', isDense: _densify),
                  initialValue: const [SampleOption.option2],
                  validator: (value) => value.length < 2 ? 'Min 2 elements' : null,
                  emptyBuilder: (_) => const Padding(padding: EdgeInsets.all(8), child: Text('No item')),
                  itemInitialValue: (items, _) => items.first,
                  addButtonText: 'Add option',
                  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.start,
                        borderSide: BorderSide(color: colorScheme.error, width: 4),
                      ),
                      enabledBorder: const SingleSideInputBorder(
                        side: GeometrySide.start,
                        borderSide: BorderSide(width: 4),
                      ),
                      disabledBorder: const SingleSideInputBorder(
                        side: GeometrySide.start,
                        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, labelText: 'Campi'),
                  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['test0'],
                      builder: (state, fields) => Column(
                        children: List<Widget>.generate(
                          index + 1,
                          (i) => XDropdownMenuFormField<SampleOption>(
                            field: fields.field('test$i'),
                            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),
                          ),
                        ).interpolate(const SizedBox(height: m12)).toList(),
                      ),
                    ),
                  ),
                ),
                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});
}

class _MouseDebugPointer extends StatefulWidget {
  final double size;
  final Color color;

  const _MouseDebugPointer({super.key, this.size = 1, this.color = Colors.red});

  @override
  State<_MouseDebugPointer> createState() => _MouseDebugPointerState();
}

class _MouseDebugPointerState extends State<_MouseDebugPointer> {
  Offset _position = Offset.zero;

  @override
  Widget build(BuildContext context) => CustomPaint(
    painter: _CursorDotPainter((event) => setState(() => _position = event), _position, widget.size, widget.color),
    child: const SizedBox.expand(),
  );
}

class _CursorDotPainter extends CustomPainter {
  final Offset position;
  final double radius;
  final Color color;
  final ValueChanged<Offset> onPointerHover;

  _CursorDotPainter(this.onPointerHover, this.position, this.radius, this.color);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = color;
    canvas.drawCircle(position, radius, paint);
  }

  @override
  bool shouldRepaint(covariant _CursorDotPainter oldDelegate) =>
      oldDelegate.position != position || oldDelegate.radius != radius || oldDelegate.color != color;

  @override
  bool? hitTest(Offset position) {
    onPointerHover(position);
    return true;
  }
}
2
likes
145
points
450
downloads

Publisher

unverified uploader

Weekly Downloads

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

Homepage

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

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

More

Packages that depend on flutterx_forms