flutterx_forms 1.4.5
flutterx_forms: ^1.4.5 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<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),
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});
}