Form sample
This commit is contained in:
parent
38a86d47f2
commit
23c933e6b1
|
|
@ -41,7 +41,7 @@ void main() {
|
||||||
//LoggingMiddleware.printer(),
|
//LoggingMiddleware.printer(),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
runApp(new InvoiceNinjaApp(store: store));
|
runApp(InvoiceNinjaApp(store: store));
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvoiceNinjaApp extends StatefulWidget {
|
class InvoiceNinjaApp extends StatefulWidget {
|
||||||
|
|
@ -50,16 +50,16 @@ class InvoiceNinjaApp extends StatefulWidget {
|
||||||
InvoiceNinjaApp({Key key, this.store}) : super(key: key);
|
InvoiceNinjaApp({Key key, this.store}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_InvoiceNinjaAppState createState() => new _InvoiceNinjaAppState();
|
_InvoiceNinjaAppState createState() => _InvoiceNinjaAppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
|
class _InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new StoreProvider<AppState>(
|
return StoreProvider<AppState>(
|
||||||
store: widget.store,
|
store: widget.store,
|
||||||
child: new MaterialApp(
|
child: MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
localizationsDelegates: [
|
localizationsDelegates: [
|
||||||
const AppLocalizationsDelegate(),
|
const AppLocalizationsDelegate(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,323 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
|
import 'package:redux/redux.dart';
|
||||||
|
import 'package:redux_logging/redux_logging.dart';
|
||||||
|
|
||||||
|
// Sample entity classes
|
||||||
|
class ClientEntity {
|
||||||
|
ClientEntity({this.name, this.contacts});
|
||||||
|
String name;
|
||||||
|
List<ContactEntity> contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContactEntity {
|
||||||
|
ContactEntity({this.email});
|
||||||
|
String email;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redux classes
|
||||||
|
class AppState {
|
||||||
|
ClientEntity client;
|
||||||
|
AppState(this.client);
|
||||||
|
|
||||||
|
AppState.init()
|
||||||
|
: client = ClientEntity(
|
||||||
|
name: 'Acme Client',
|
||||||
|
contacts: [ContactEntity(email: 'test@example.com')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateClient {
|
||||||
|
final String name;
|
||||||
|
UpdateClient(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddContact {}
|
||||||
|
|
||||||
|
class UpdateContact {
|
||||||
|
final ContactEntity oldContact;
|
||||||
|
final ContactEntity newContact;
|
||||||
|
UpdateContact({this.oldContact, this.newContact});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeleteContact {
|
||||||
|
final String email;
|
||||||
|
DeleteContact(this.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppState reducer(AppState state, action) {
|
||||||
|
// In an app you'd most like want to
|
||||||
|
// use built_value to rebuild the state
|
||||||
|
if (action is UpdateClient) {
|
||||||
|
return AppState(ClientEntity(
|
||||||
|
name: action.name,
|
||||||
|
contacts: state.client.contacts,
|
||||||
|
));
|
||||||
|
} else if (action is AddContact) {
|
||||||
|
return AppState(ClientEntity(
|
||||||
|
name: state.client.name,
|
||||||
|
contacts: []
|
||||||
|
..addAll(state.client.contacts)
|
||||||
|
..add(ContactEntity())));
|
||||||
|
} else if (action is DeleteContact) {
|
||||||
|
return AppState(ClientEntity(
|
||||||
|
name: state.client.name,
|
||||||
|
contacts: state.client.contacts.where((contact) => contact.email != action.email).toList(),
|
||||||
|
));
|
||||||
|
} else if (action is UpdateContact) {
|
||||||
|
return AppState(ClientEntity(
|
||||||
|
name: state.client.name,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final store =
|
||||||
|
Store<AppState>(reducer, initialState: AppState.init(), middleware: [
|
||||||
|
LoggingMiddleware.printer(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
runApp(MyApp(store: store));
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends StatefulWidget {
|
||||||
|
final Store<AppState> store;
|
||||||
|
|
||||||
|
MyApp({Key key, this.store}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MyAppState createState() => _MyAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
|
||||||
|
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
TabController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = TabController(vsync: this, length: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StoreProvider<AppState>(
|
||||||
|
store: widget.store,
|
||||||
|
child: MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Client Form'),
|
||||||
|
actions: <Widget>[
|
||||||
|
StoreBuilder(
|
||||||
|
builder: (BuildContext context, Store<AppState> store) {
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(Icons.cloud_upload),
|
||||||
|
onPressed: () {
|
||||||
|
if (!_formKey.currentState.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_formKey.currentState.save();
|
||||||
|
|
||||||
|
// Do something with the client...
|
||||||
|
print('Client name: ' + store.state.client.name);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: _controller,
|
||||||
|
tabs: [
|
||||||
|
Tab(
|
||||||
|
text: 'Details',
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
text: 'Contacts',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: TabBarView(
|
||||||
|
controller: _controller,
|
||||||
|
children: <Widget>[
|
||||||
|
ClientPage(),
|
||||||
|
ContactsPage(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClientPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_ClientPageState createState() => new _ClientPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientPageState extends State<ClientPage> {
|
||||||
|
final _nameController = new TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_nameController.addListener(_onChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nameController.removeListener(_onChanged);
|
||||||
|
_nameController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onChanged() {
|
||||||
|
var name = _nameController.text.trim();
|
||||||
|
StoreProvider.of<AppState>(context).dispatch(UpdateClient(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StoreBuilder(builder: (BuildContext context, Store<AppState> store) {
|
||||||
|
_nameController.text = store.state.client.name;
|
||||||
|
|
||||||
|
return FormCard(
|
||||||
|
children: <Widget>[
|
||||||
|
TextFormField(
|
||||||
|
controller: _nameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Name',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContactsPage extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StoreBuilder(builder: (BuildContext context, Store<AppState> store) {
|
||||||
|
var contacts = store.state.client.contacts
|
||||||
|
.map((contact) => ContactForm(contact))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
children: []
|
||||||
|
..addAll(contacts)
|
||||||
|
..add(Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: RaisedButton(
|
||||||
|
elevation: 4.0,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
textColor: Theme.of(context).secondaryHeaderColor,
|
||||||
|
child: Text('ADD CONTACT'),
|
||||||
|
onPressed: () {
|
||||||
|
store.dispatch(AddContact());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContactForm extends StatefulWidget {
|
||||||
|
ContactForm(this.contact);
|
||||||
|
final ContactEntity contact;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ContactFormState createState() => new _ContactFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContactFormState extends State<ContactForm> {
|
||||||
|
final _emailController = new TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_emailController.addListener(_onChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_emailController.removeListener(_onChanged);
|
||||||
|
_emailController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onChanged() {
|
||||||
|
var name = _emailController.text.trim();
|
||||||
|
//StoreProvider.of<AppState>(context).dispatch(UpdateClient(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var store = StoreProvider.of<AppState>(context);
|
||||||
|
|
||||||
|
return FormCard(
|
||||||
|
children: <Widget>[
|
||||||
|
TextFormField(
|
||||||
|
initialValue: widget.contact.email,
|
||||||
|
//onSaved: (value) => _email = value.trim(),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Email',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 12.0),
|
||||||
|
child: FlatButton(
|
||||||
|
onPressed: () => store.dispatch(DeleteContact(widget.contact.email)),
|
||||||
|
child: Text(
|
||||||
|
'Delete',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper widget to make the form look a bit nicer
|
||||||
|
class FormCard extends StatelessWidget {
|
||||||
|
FormCard({this.children});
|
||||||
|
final List<Widget> children;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 12.0, top: 12.0, right: 12.0),
|
||||||
|
child: Card(
|
||||||
|
elevation: 2.0,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 12.0, right: 12.0, top: 12.0, bottom: 18.0),
|
||||||
|
child: Column(
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue