mirror of
https://github.com/Iconica-Development/flutter_address_form.git
synced 2025-05-19 18:53:47 +02:00
Functionally working Adressform
This commit is contained in:
parent
e00b961414
commit
1107729c79
5 changed files with 229 additions and 147 deletions
|
@ -25,19 +25,61 @@ class MyApp extends StatelessWidget {
|
|||
// is not restarted.
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const AddressFormExample(),
|
||||
home: AddressFormExample(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressFormExample extends StatelessWidget {
|
||||
const AddressFormExample({Key? key}) : super(key: key);
|
||||
AddressFormExample({Key? key}) : super(key: key);
|
||||
|
||||
final RegExp zipcodeRegExp = RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: AddressForm(),
|
||||
body: AddressForm(
|
||||
zipCodeValidator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
if (!zipcodeRegExp.hasMatch(text)) {
|
||||
return 'Invalid zipcode';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
housenumberValidator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
if (text.length >= 3 || int.tryParse(text) == null) {
|
||||
return 'Invalid number';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
suffixValidator: (text) {
|
||||
if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) {
|
||||
return 'Invalid prefix';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
streetValidator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
cityValidator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
controller: AddressController(onAutoComplete: (address) {
|
||||
return address;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_address_form/src/models/address.dart';
|
||||
|
||||
class AddressController extends ChangeNotifier {
|
||||
Address address = const Address();
|
||||
|
||||
final TextEditingController zipcode = TextEditingController();
|
||||
final TextEditingController houseNumber = TextEditingController();
|
||||
final TextEditingController suffix = TextEditingController();
|
||||
final TextEditingController street = TextEditingController();
|
||||
final TextEditingController city = TextEditingController();
|
||||
|
||||
final Function()? onChangeInputCallback;
|
||||
|
||||
AddressController(this.onChangeInputCallback);
|
||||
|
||||
Address get getAddress => address;
|
||||
|
||||
void setAddress(
|
||||
String zipcode,
|
||||
String street,
|
||||
int housenumber,
|
||||
String suffix,
|
||||
String city,
|
||||
) {
|
||||
address = address.copyWith(
|
||||
zipcode: zipcode,
|
||||
street: street,
|
||||
housenumber: housenumber,
|
||||
suffix: suffix,
|
||||
city: city,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void onChangeInput() {
|
||||
onChangeInputCallback?.call();
|
||||
}
|
||||
}
|
|
@ -1,111 +1,83 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_address_form/src/address_controller.dart';
|
||||
import 'package:flutter_address_form/src/models/address.dart';
|
||||
|
||||
class AddressForm extends StatefulWidget {
|
||||
const AddressForm({Key? key}) : super(key: key);
|
||||
import 'package:flutter_address_form/src/models/address_model.dart';
|
||||
|
||||
@override
|
||||
State<AddressForm> createState() => _AddressFormState();
|
||||
}
|
||||
|
||||
class _AddressFormState extends State<AddressForm> {
|
||||
final AddressController _addressController = AddressController(() {});
|
||||
|
||||
final RegExp _zipcodeRegExp = RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_addressController;
|
||||
super.initState();
|
||||
class AddressForm extends StatelessWidget {
|
||||
AddressForm({
|
||||
Key? key,
|
||||
this.zipCodeLabel = const Text('Zipcode'),
|
||||
this.housenumberLabel = const Text('Housenumber'),
|
||||
this.suffixLabel = const Text('Suffix'),
|
||||
this.streetLabel = const Text('Street'),
|
||||
this.cityLabel = const Text('City'),
|
||||
required this.zipCodeValidator,
|
||||
required this.housenumberValidator,
|
||||
required this.suffixValidator,
|
||||
required this.streetValidator,
|
||||
required this.cityValidator,
|
||||
AddressController? controller,
|
||||
}) {
|
||||
_addressController =
|
||||
controller ?? AddressController(onAutoComplete: (model) => model);
|
||||
}
|
||||
|
||||
final Widget zipCodeLabel;
|
||||
final Widget housenumberLabel;
|
||||
final Widget suffixLabel;
|
||||
final Widget streetLabel;
|
||||
final Widget cityLabel;
|
||||
|
||||
final String? Function(String) zipCodeValidator;
|
||||
final String? Function(String) housenumberValidator;
|
||||
final String? Function(String) suffixValidator;
|
||||
final String? Function(String) streetValidator;
|
||||
final String? Function(String) cityValidator;
|
||||
|
||||
late final AddressController _addressController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
AddressFormTextField(
|
||||
controller: _addressController.zipcode,
|
||||
validator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
if (!_zipcodeRegExp.hasMatch(text)) {
|
||||
return 'Invalid zipcode';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
label: const Text('Postcode'),
|
||||
controller: _addressController._zipcodeController,
|
||||
validator: zipCodeValidator,
|
||||
label: zipCodeLabel,
|
||||
),
|
||||
Flexible(
|
||||
child: Row(
|
||||
children: [
|
||||
AddressFormTextField(
|
||||
controller: _addressController.houseNumber,
|
||||
validator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
if (text.length >= 3 || int.tryParse(text) == null) {
|
||||
return 'Invalid number';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
label: const Text('Huisnummer'),
|
||||
controller: _addressController._housenumberController,
|
||||
validator: housenumberValidator,
|
||||
label: housenumberLabel,
|
||||
),
|
||||
AddressFormTextField(
|
||||
controller: _addressController.suffix,
|
||||
validator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
if (RegExp(r'/^[a-z]*$/').hasMatch(text) &&
|
||||
text.length != 1) {
|
||||
return 'Invalid prefix';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
label: const Text('Toevoeging'),
|
||||
controller: _addressController._suffixController,
|
||||
validator: suffixValidator,
|
||||
label: suffixLabel,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AddressFormTextField(
|
||||
controller: _addressController.street,
|
||||
validator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
label: const Text('Straatnaam'),
|
||||
controller: _addressController._streetController,
|
||||
validator: streetValidator,
|
||||
label: streetLabel,
|
||||
),
|
||||
AddressFormTextField(
|
||||
controller: _addressController.city,
|
||||
validator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
label: const Text('Woonplaats'),
|
||||
controller: _addressController._cityController,
|
||||
validator: cityValidator,
|
||||
label: cityLabel,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
child: Text('Test'),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class AddressFormTextField extends StatefulWidget {
|
||||
class AddressFormTextField extends StatelessWidget {
|
||||
final Widget label;
|
||||
final TextEditingController controller;
|
||||
final String? Function(String) validator;
|
||||
|
@ -116,33 +88,20 @@ class AddressFormTextField extends StatefulWidget {
|
|||
required this.validator,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AddressFormTextField> createState() => _AddressFormTextFieldState();
|
||||
}
|
||||
|
||||
class _AddressFormTextFieldState extends State<AddressFormTextField> {
|
||||
String? get _errorText {
|
||||
final text = widget.controller.value.text;
|
||||
return widget.validator(text);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
String? get _errorText => validator(controller.value.text);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: widget.controller,
|
||||
valueListenable: controller,
|
||||
builder: (context, value, _) {
|
||||
return Flexible(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(10),
|
||||
child: TextField(
|
||||
controller: widget.controller,
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
label: widget.label,
|
||||
label: label,
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _errorText),
|
||||
),
|
||||
|
@ -151,3 +110,77 @@ class _AddressFormTextFieldState extends State<AddressFormTextField> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
class AddressController extends ChangeNotifier {
|
||||
final AddressModel? initialValue;
|
||||
final FutureOr<AddressModel> Function(AddressModel)? onAutoComplete;
|
||||
|
||||
AddressController({this.initialValue, this.onAutoComplete}) {
|
||||
_model = initialValue ??
|
||||
const AddressModel(
|
||||
zipcode: null,
|
||||
street: null,
|
||||
housenumber: null,
|
||||
suffix: null,
|
||||
city: null,
|
||||
);
|
||||
|
||||
_zipcodeController.addListener(_update);
|
||||
_streetController.addListener(_update);
|
||||
_housenumberController.addListener(_update);
|
||||
_suffixController.addListener(_update);
|
||||
_cityController.addListener(_update);
|
||||
}
|
||||
|
||||
late AddressModel _model;
|
||||
|
||||
late final _zipcodeController =
|
||||
TextEditingController(text: initialValue?.zipcode);
|
||||
late final _streetController =
|
||||
TextEditingController(text: initialValue?.street);
|
||||
late final _housenumberController =
|
||||
TextEditingController(text: initialValue?.housenumber.toString());
|
||||
late final _suffixController =
|
||||
TextEditingController(text: initialValue?.suffix);
|
||||
late final _cityController = TextEditingController(text: initialValue?.city);
|
||||
|
||||
AddressModel get model => _model;
|
||||
|
||||
void _update() async {
|
||||
AddressModel updatedModel = _model.copyWith(
|
||||
zipcode: _zipcodeController.text,
|
||||
street: _streetController.text,
|
||||
housenumber: int.tryParse(_housenumberController.text),
|
||||
suffix: _suffixController.text,
|
||||
city: _cityController.text);
|
||||
_model = await onAutoComplete?.call(updatedModel) ?? updatedModel;
|
||||
|
||||
if (_model.zipcode != updatedModel.zipcode) {
|
||||
_zipcodeController.text = _model.zipcode ?? '';
|
||||
}
|
||||
if (_model.street != updatedModel.street) {
|
||||
_streetController.text = _model.street ?? '';
|
||||
}
|
||||
if (_model.housenumber != updatedModel.housenumber) {
|
||||
_housenumberController.text = _model.housenumber?.toString() ?? '';
|
||||
}
|
||||
if (_model.suffix != updatedModel.suffix) {
|
||||
_suffixController.text = _model.suffix ?? '';
|
||||
}
|
||||
if (_model.city != updatedModel.city) {
|
||||
_cityController.text = _model.city ?? '';
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_zipcodeController.dispose();
|
||||
_streetController.dispose();
|
||||
_housenumberController.dispose();
|
||||
_suffixController.dispose();
|
||||
_cityController.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:flutter/material.dart' show immutable;
|
||||
|
||||
@immutable
|
||||
class Address {
|
||||
const Address({
|
||||
class AddressModel {
|
||||
const AddressModel({
|
||||
this.zipcode,
|
||||
this.street,
|
||||
this.housenumber,
|
||||
|
@ -16,14 +16,14 @@ class Address {
|
|||
final String? suffix;
|
||||
final String? city;
|
||||
|
||||
Address copyWith({
|
||||
AddressModel copyWith({
|
||||
String? zipcode,
|
||||
String? street,
|
||||
int? housenumber,
|
||||
String? suffix,
|
||||
String? city,
|
||||
}) =>
|
||||
Address(
|
||||
AddressModel(
|
||||
zipcode: zipcode ?? this.zipcode,
|
||||
street: street ?? this.street,
|
||||
housenumber: housenumber ?? this.housenumber,
|
|
@ -1,12 +1,59 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:flutter_address_form/flutter_address_form.dart';
|
||||
|
||||
void main() {
|
||||
test('adds one to input values', () {
|
||||
final calculator = Calculator();
|
||||
expect(calculator.addOne(2), 3);
|
||||
expect(calculator.addOne(-7), -6);
|
||||
expect(calculator.addOne(0), 1);
|
||||
testWidgets('Render App with AddressForm Widget', (tester) async {
|
||||
final RegExp zipcodeRegExp = RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$');
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: AddressForm(
|
||||
zipCodeValidator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
if (!zipcodeRegExp.hasMatch(text)) {
|
||||
return 'Invalid zipcode';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
housenumberValidator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
if (text.length >= 3 || int.tryParse(text) == null) {
|
||||
return 'Invalid number';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
suffixValidator: (text) {
|
||||
if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) {
|
||||
return 'Invalid prefix';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
streetValidator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
cityValidator: (text) {
|
||||
if (text.isEmpty) {
|
||||
return 'Can\'t be empty';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
controller: AddressController(onAutoComplete: (address) {
|
||||
return address;
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue