Refactor
This commit is contained in:
parent
a40d7946d7
commit
013f8e29f9
|
|
@ -46,10 +46,28 @@ class EditClient implements PersistUI {
|
|||
EditClient({this.client, this.context});
|
||||
}
|
||||
|
||||
class UpdateClient implements PersistUI {
|
||||
final ClientEntity client;
|
||||
UpdateClient(this.client);
|
||||
}
|
||||
|
||||
class AddContact {}
|
||||
|
||||
class UpdateContact {
|
||||
final int index;
|
||||
final ContactEntity contact;
|
||||
UpdateContact({this.index, this.contact});
|
||||
}
|
||||
|
||||
class DeleteContact {
|
||||
final int index;
|
||||
DeleteContact(this.index);
|
||||
}
|
||||
|
||||
class SaveClientRequest implements StartLoading {
|
||||
final Completer completer;
|
||||
final ClientEntity client;
|
||||
SaveClientRequest(this.completer, this.client);
|
||||
SaveClientRequest({this.completer, this.client});
|
||||
}
|
||||
|
||||
class SaveClientSuccess implements StopLoading, PersistData {
|
||||
|
|
|
|||
|
|
@ -17,12 +17,34 @@ final editingReducer = combineReducers<ClientEntity>([
|
|||
TypedReducer<ClientEntity, AddClientSuccess>(_updateEditing),
|
||||
TypedReducer<ClientEntity, ViewClient>(_updateEditing),
|
||||
TypedReducer<ClientEntity, EditClient>(_updateEditing),
|
||||
TypedReducer<ClientEntity, UpdateClient>(_updateEditing),
|
||||
TypedReducer<ClientEntity, AddContact>(_addContact),
|
||||
TypedReducer<ClientEntity, DeleteContact>(_removeContact),
|
||||
TypedReducer<ClientEntity, UpdateContact>(_updateContact),
|
||||
]);
|
||||
|
||||
ClientEntity _updateEditing(ClientEntity client, action) {
|
||||
return action.client;
|
||||
}
|
||||
|
||||
ClientEntity _addContact(ClientEntity client, AddContact action) {
|
||||
return client.rebuild((b) => b
|
||||
..contacts.add(ContactEntity())
|
||||
);
|
||||
}
|
||||
|
||||
ClientEntity _removeContact(ClientEntity client, DeleteContact action) {
|
||||
return client.rebuild((b) => b
|
||||
..contacts.removeAt(action.index)
|
||||
);
|
||||
}
|
||||
|
||||
ClientEntity _updateContact(ClientEntity client, UpdateContact action) {
|
||||
return client.rebuild((b) => b
|
||||
..contacts[action.index] = action.contact
|
||||
);
|
||||
}
|
||||
|
||||
final clientListReducer = combineReducers<ListUIState>([
|
||||
TypedReducer<ListUIState, SortClients>(_sortClients),
|
||||
TypedReducer<ListUIState, FilterClientsByState>(_filterClientsByState),
|
||||
|
|
|
|||
|
|
@ -46,10 +46,28 @@ class EditInvoice implements PersistUI {
|
|||
EditInvoice({this.invoice, this.context});
|
||||
}
|
||||
|
||||
class UpdateInvoice implements PersistUI {
|
||||
final InvoiceEntity invoice;
|
||||
UpdateInvoice(this.invoice);
|
||||
}
|
||||
|
||||
class AddInvoiceItem {}
|
||||
|
||||
class UpdateInvoiceItem {
|
||||
final int index;
|
||||
final InvoiceItemEntity invoiceItem;
|
||||
UpdateInvoiceItem({this.index, this.invoiceItem});
|
||||
}
|
||||
|
||||
class DeleteInvoiceItem {
|
||||
final int index;
|
||||
DeleteInvoiceItem(this.index);
|
||||
}
|
||||
|
||||
class SaveInvoiceRequest implements StartLoading {
|
||||
final Completer completer;
|
||||
final InvoiceEntity invoice;
|
||||
SaveInvoiceRequest(this.completer, this.invoice);
|
||||
SaveInvoiceRequest({this.completer, this.invoice});
|
||||
}
|
||||
|
||||
class SaveInvoiceSuccess implements StopLoading, PersistData {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ final editingReducer = combineReducers<InvoiceEntity>([
|
|||
TypedReducer<InvoiceEntity, AddInvoiceSuccess>(_updateEditing),
|
||||
TypedReducer<InvoiceEntity, ViewInvoice>(_updateEditing),
|
||||
TypedReducer<InvoiceEntity, EditInvoice>(_updateEditing),
|
||||
TypedReducer<InvoiceEntity, UpdateInvoice>(_updateEditing),
|
||||
]);
|
||||
|
||||
InvoiceEntity _updateEditing(InvoiceEntity client, action) {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ class UpdateProduct implements PersistUI {
|
|||
|
||||
class SaveProductRequest implements StartLoading {
|
||||
final Completer completer;
|
||||
SaveProductRequest(this.completer);
|
||||
final ProductEntity product;
|
||||
SaveProductRequest({this.product, this.completer});
|
||||
}
|
||||
|
||||
class SaveProductSuccess implements StopLoading, PersistData {
|
||||
|
|
|
|||
|
|
@ -100,9 +100,9 @@ Middleware<AppState> _saveProduct(ProductRepository repository) {
|
|||
return (Store<AppState> store, action, NextDispatcher next) {
|
||||
repository
|
||||
.saveData(
|
||||
store.state.selectedCompany, store.state.authState, store.state.productUIState.selected)
|
||||
store.state.selectedCompany, store.state.authState, action.product)
|
||||
.then((product) {
|
||||
if (store.state.productUIState.selected.isNew()) {
|
||||
if (action.product.isNew()) {
|
||||
store.dispatch(AddProductSuccess(product));
|
||||
} else {
|
||||
store.dispatch(SaveProductSuccess(product));
|
||||
|
|
|
|||
|
|
@ -54,8 +54,14 @@ class ClientScreen extends StatelessWidget {
|
|||
floatingActionButton: FloatingActionButton(
|
||||
backgroundColor: Theme.of(context).primaryColorDark,
|
||||
onPressed: () {
|
||||
// TODO create factory
|
||||
var client = ClientEntity()
|
||||
.rebuild((b) => b..contacts.replace([ContactEntity()]));
|
||||
.rebuild((b) => b..contacts.replace([ContactEntity().rebuild((b) => b
|
||||
..firstName = ''
|
||||
..lastName = ''
|
||||
..email = ''
|
||||
..phone = ''
|
||||
)]));
|
||||
store.dispatch(EditClient(client: client, context: context));
|
||||
},
|
||||
child: Icon(Icons.add, color: Colors.white,),
|
||||
|
|
|
|||
|
|
@ -23,16 +23,6 @@ class _ClientEditState extends State<ClientEdit>
|
|||
with SingleTickerProviderStateMixin {
|
||||
TabController _controller;
|
||||
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
static final GlobalKey<ClientEditDetailsState> _detailsKey =
|
||||
GlobalKey<ClientEditDetailsState>();
|
||||
/*
|
||||
static final GlobalKey<ClientEditBillingAddressState> _billingAddressKey =
|
||||
GlobalKey<ClientEditBillingAddressState>();
|
||||
static final GlobalKey<ClientEditShippingAddressState> _shippingAddressKey =
|
||||
GlobalKey<ClientEditShippingAddressState>();
|
||||
*/
|
||||
static final GlobalKey<ClientEditContactsState> _contactsKey =
|
||||
GlobalKey<ClientEditContactsState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -49,32 +39,12 @@ class _ClientEditState extends State<ClientEdit>
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var localization = AppLocalization.of(context);
|
||||
var client = widget.viewModel.client;
|
||||
|
||||
List<Widget> editors = [
|
||||
ClientEditDetails(
|
||||
client: client,
|
||||
key: _detailsKey,
|
||||
),
|
||||
ClientEditContacts(
|
||||
client: client,
|
||||
key: _contactsKey,
|
||||
),
|
||||
/*
|
||||
ClientEditBillingAddress(
|
||||
client: client,
|
||||
key: _billingAddressKey,
|
||||
),
|
||||
ClientEditShippingAddress(
|
||||
client: client,
|
||||
key: _shippingAddressKey,
|
||||
),
|
||||
*/
|
||||
];
|
||||
var viewModel = widget.viewModel;
|
||||
var client = viewModel.client;
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
widget.viewModel.onBackClicked();
|
||||
viewModel.onBackClicked();
|
||||
return true;
|
||||
},
|
||||
child: Scaffold(
|
||||
|
|
@ -84,19 +54,13 @@ class _ClientEditState extends State<ClientEdit>
|
|||
: client.displayName), // Text(localizations.clientDetails),
|
||||
actions: <Widget>[
|
||||
SaveIconButton(
|
||||
isLoading: widget.viewModel.isLoading,
|
||||
isLoading: viewModel.isLoading,
|
||||
onPressed: () {
|
||||
if (! _formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_formKey.currentState.save();
|
||||
|
||||
var detailsState = _detailsKey.currentState;
|
||||
//var billingAddressState = _billingAddressKey.currentState;
|
||||
//var shippingAddressState = _shippingAddressKey.currentState;
|
||||
var contactState = _contactsKey.currentState;
|
||||
|
||||
/*
|
||||
ClientEntity client = widget.viewModel.client.rebuild((b) => b
|
||||
..name = detailsState.name
|
||||
..idNumber = detailsState.idNumber
|
||||
|
|
@ -117,8 +81,9 @@ class _ClientEditState extends State<ClientEdit>
|
|||
*/
|
||||
..contacts.replace(
|
||||
contactState?.getContacts() ?? widget.viewModel.client.contacts));
|
||||
*/
|
||||
|
||||
widget.viewModel.onSaveClicked(context, client);
|
||||
viewModel.onSaveClicked(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
|
|
@ -147,7 +112,22 @@ class _ClientEditState extends State<ClientEdit>
|
|||
key: _formKey,
|
||||
child: TabBarView(
|
||||
controller: _controller,
|
||||
children: editors,
|
||||
children: <Widget>[
|
||||
ClientEditDetails(
|
||||
viewModel: widget.viewModel,
|
||||
),
|
||||
ClientEditContacts(
|
||||
viewModel: widget.viewModel,
|
||||
),
|
||||
/*
|
||||
ClientEditBillingAddress(
|
||||
client: client,
|
||||
),
|
||||
ClientEditShippingAddress(
|
||||
client: client,
|
||||
),
|
||||
*/
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,79 +1,120 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:invoiceninja/data/models/models.dart';
|
||||
import 'package:invoiceninja/ui/app/form_card.dart';
|
||||
import 'package:invoiceninja/utils/localization.dart';
|
||||
|
||||
import '../../app/form_card.dart';
|
||||
|
||||
|
||||
class ClientEditBillingAddress extends StatefulWidget {
|
||||
ClientEditBillingAddress({
|
||||
Key key,
|
||||
@required this.client,
|
||||
@required this.onChanged,
|
||||
}) : super(key: key);
|
||||
|
||||
final ClientEntity client;
|
||||
final Function(ClientEntity) onChanged;
|
||||
|
||||
@override
|
||||
ClientEditBillingAddressState createState() =>
|
||||
new ClientEditBillingAddressState();
|
||||
}
|
||||
|
||||
class ClientEditBillingAddressState extends State<ClientEditBillingAddress>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
String address1;
|
||||
String address2;
|
||||
String city;
|
||||
String state;
|
||||
String postalCode;
|
||||
class ClientEditBillingAddressState extends State<ClientEditBillingAddress> {
|
||||
|
||||
final _address1Controller = TextEditingController();
|
||||
final _address2Controller = TextEditingController();
|
||||
final _cityController = TextEditingController();
|
||||
final _stateController = TextEditingController();
|
||||
final _postalCodeController = TextEditingController();
|
||||
|
||||
var _controllers = [];
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
void didChangeDependencies() {
|
||||
|
||||
_controllers = [
|
||||
_address1Controller,
|
||||
_address2Controller,
|
||||
_cityController,
|
||||
_stateController,
|
||||
_postalCodeController,
|
||||
];
|
||||
|
||||
_controllers.forEach((controller) => controller.removeListener(_onChanged));
|
||||
|
||||
var client = widget.client;
|
||||
_address1Controller.text = client.address1;
|
||||
_address2Controller.text = client.address2;
|
||||
_cityController.text = client.city;
|
||||
_stateController.text = client.state;
|
||||
_postalCodeController.text = client.postalCode;
|
||||
|
||||
_controllers.forEach((controller) => controller.addListener(_onChanged));
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controllers.forEach((controller) {
|
||||
controller.removeListener(_onChanged);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onChanged() {
|
||||
var client = widget.client.rebuild((b) => b
|
||||
..address1 = _address1Controller.text.trim()
|
||||
..address2 = _address2Controller.text.trim()
|
||||
..city = _cityController.text.trim()
|
||||
..state = _stateController.text.trim()
|
||||
..postalCode = _postalCodeController.text.trim()
|
||||
);
|
||||
if (client != widget.client) {
|
||||
widget.onChanged(client);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var localization = AppLocalization.of(context);
|
||||
var client = widget.client;
|
||||
|
||||
return ListView(shrinkWrap: true, children: <Widget>[
|
||||
FormCard(
|
||||
children: <Widget>[
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => address1 = value.trim(),
|
||||
initialValue: client.address1,
|
||||
controller: _address1Controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.address1,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => address2 = value.trim(),
|
||||
initialValue: client.address2,
|
||||
controller: _address2Controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.address2,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => city = value.trim(),
|
||||
initialValue: client.city,
|
||||
controller: _cityController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.city,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => state = value.trim(),
|
||||
initialValue: client.state,
|
||||
controller: _stateController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.state,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => postalCode = value.trim(),
|
||||
initialValue: client.postalCode,
|
||||
controller: _postalCodeController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.postalCode,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,93 +1,42 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:invoiceninja/data/models/models.dart';
|
||||
import 'package:invoiceninja/ui/app/form_card.dart';
|
||||
import 'package:invoiceninja/ui/client/edit/client_edit_vm.dart';
|
||||
import 'package:invoiceninja/utils/localization.dart';
|
||||
|
||||
import '../../app/form_card.dart';
|
||||
|
||||
class ClientEditContacts extends StatefulWidget {
|
||||
class ClientEditContacts extends StatelessWidget {
|
||||
ClientEditContacts({
|
||||
Key key,
|
||||
@required this.client,
|
||||
@required this.viewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
final ClientEntity client;
|
||||
|
||||
@override
|
||||
ClientEditContactsState createState() => new ClientEditContactsState();
|
||||
}
|
||||
|
||||
class ClientEditContactsState extends State<ClientEditContacts>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
List<ContactEntity> contacts;
|
||||
List<GlobalKey<ContactEditDetailsState>> contactKeys;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var client = widget.client;
|
||||
contacts = client.contacts.toList();
|
||||
contactKeys = client.contacts
|
||||
.map((contact) => GlobalKey<ContactEditDetailsState>())
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<ContactEntity> getContacts() {
|
||||
List<ContactEntity> contacts = [];
|
||||
contactKeys.forEach((contactKey) {
|
||||
if (contactKey.currentState != null) {
|
||||
contacts.add(contactKey.currentState.getContact());
|
||||
}
|
||||
});
|
||||
return contacts;
|
||||
}
|
||||
|
||||
_onAddPressed() {
|
||||
setState(() {
|
||||
contacts.add(ContactEntity());
|
||||
contactKeys.add(GlobalKey<ContactEditDetailsState>());
|
||||
});
|
||||
}
|
||||
|
||||
_onRemovePressed(GlobalKey<ContactEditDetailsState> key) {
|
||||
setState(() {
|
||||
var index = contactKeys.indexOf(key);
|
||||
contactKeys.removeAt(index);
|
||||
contacts.removeAt(index);
|
||||
});
|
||||
}
|
||||
final ClientEditVM viewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var localization = AppLocalization.of(context);
|
||||
List<Widget> items = [];
|
||||
|
||||
for (var i = 0; i < contacts.length; i++) {
|
||||
var contact = contacts[i];
|
||||
var contactKey = contactKeys[i];
|
||||
items.add(ContactEditDetails(
|
||||
var client = viewModel.client;
|
||||
var contacts = client.contacts.map((contact) => ContactEditDetails(
|
||||
viewModel: viewModel,
|
||||
isRemoveVisible: client.contacts.length > 1,
|
||||
contact: contact,
|
||||
key: contactKey,
|
||||
onRemovePressed: (key) => _onRemovePressed(key),
|
||||
isRemoveVisible: contacts.length > 1,
|
||||
));
|
||||
}
|
||||
|
||||
items.add(Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 4.0),
|
||||
child: RaisedButton(
|
||||
elevation: 4.0,
|
||||
color: Theme.of(context).primaryColor,
|
||||
child: Text(localization.addContact.toUpperCase()),
|
||||
onPressed: _onAddPressed,
|
||||
),
|
||||
));
|
||||
index: client.contacts.indexOf(contact)));
|
||||
|
||||
return ListView(
|
||||
children: items,
|
||||
children: []
|
||||
..addAll(client.contacts.map((contact) => Container()))
|
||||
..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(localization.addContact.toUpperCase()),
|
||||
onPressed: viewModel.onAddContactClicked,
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,13 +44,15 @@ class ClientEditContactsState extends State<ClientEditContacts>
|
|||
class ContactEditDetails extends StatefulWidget {
|
||||
ContactEditDetails({
|
||||
Key key,
|
||||
@required this.index,
|
||||
@required this.contact,
|
||||
@required this.onRemovePressed,
|
||||
@required this.viewModel,
|
||||
@required this.isRemoveVisible,
|
||||
}) : super(key: key);
|
||||
|
||||
final int index;
|
||||
final ContactEntity contact;
|
||||
final Function(GlobalKey<ContactEditDetailsState>) onRemovePressed;
|
||||
final ClientEditVM viewModel;
|
||||
final bool isRemoveVisible;
|
||||
|
||||
@override
|
||||
|
|
@ -109,17 +60,55 @@ class ContactEditDetails extends StatefulWidget {
|
|||
}
|
||||
|
||||
class ContactEditDetailsState extends State<ContactEditDetails> {
|
||||
String _firstName;
|
||||
String _lastName;
|
||||
String _email;
|
||||
String _phone;
|
||||
final _firstNameController = TextEditingController();
|
||||
final _lastNameController = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
final _phoneController = TextEditingController();
|
||||
|
||||
ContactEntity getContact() {
|
||||
return widget.contact.rebuild((b) => b
|
||||
..firstName = _firstName
|
||||
..lastName = _lastName
|
||||
..email = _email
|
||||
..phone = _phone);
|
||||
var _controllers = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_controllers = [
|
||||
_firstNameController,
|
||||
_lastNameController,
|
||||
_emailController,
|
||||
_phoneController,
|
||||
];
|
||||
|
||||
_controllers.forEach((controller) => controller.removeListener(_onChanged));
|
||||
|
||||
var contact = widget.contact;
|
||||
_firstNameController.text = contact.firstName;
|
||||
_lastNameController.text = contact.lastName;
|
||||
_emailController.text = contact.email;
|
||||
_phoneController.text = contact.phone;
|
||||
|
||||
_controllers.forEach((controller) => controller.addListener(_onChanged));
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controllers.forEach((controller) {
|
||||
controller.removeListener(_onChanged);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onChanged() {
|
||||
var contact = widget.contact.rebuild((b) => b
|
||||
..firstName = _firstNameController.text.trim()
|
||||
..lastName = _lastNameController.text.trim()
|
||||
..email = _emailController.text.trim()
|
||||
..phone = _phoneController.text.trim());
|
||||
|
||||
if (contact != widget.contact) {
|
||||
widget.viewModel.onChangedContact(contact, widget.index);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -141,7 +130,7 @@ class ContactEditDetailsState extends State<ContactEditDetails> {
|
|||
new FlatButton(
|
||||
child: Text(localization.ok.toUpperCase()),
|
||||
onPressed: () {
|
||||
widget.onRemovePressed(widget.key);
|
||||
widget.viewModel.onRemoveContactPressed(widget.index);
|
||||
Navigator.pop(context);
|
||||
})
|
||||
],
|
||||
|
|
@ -153,34 +142,32 @@ class ContactEditDetailsState extends State<ContactEditDetails> {
|
|||
children: <Widget>[
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
initialValue: widget.contact.firstName,
|
||||
onSaved: (value) => _firstName = value.trim(),
|
||||
controller: _firstNameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.firstName,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
initialValue: widget.contact.lastName,
|
||||
onSaved: (value) => _lastName = value.trim(),
|
||||
controller: _lastNameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.lastName,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
initialValue: widget.contact.email,
|
||||
onSaved: (value) => _email = value.trim(),
|
||||
controller: _emailController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.email,
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (value) => value.isNotEmpty && ! value.contains('@') ? localization.emailIsInvalid : null,
|
||||
validator: (value) => value.isNotEmpty && !value.contains('@')
|
||||
? localization.emailIsInvalid
|
||||
: null,
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
initialValue: widget.contact.phone,
|
||||
onSaved: (value) => _phone = value.trim(),
|
||||
controller: _phoneController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.phone,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,36 +1,83 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:invoiceninja/data/models/models.dart';
|
||||
import 'package:invoiceninja/ui/client/edit/client_edit_vm.dart';
|
||||
import 'package:invoiceninja/utils/localization.dart';
|
||||
import 'package:invoiceninja/ui/app/form_card.dart';
|
||||
|
||||
class ClientEditDetails extends StatefulWidget {
|
||||
ClientEditDetails({
|
||||
Key key,
|
||||
@required this.client,
|
||||
@required this.viewModel,
|
||||
}) : super(key: key);
|
||||
|
||||
final ClientEntity client;
|
||||
final ClientEditVM viewModel;
|
||||
|
||||
@override
|
||||
ClientEditDetailsState createState() => new ClientEditDetailsState();
|
||||
}
|
||||
|
||||
class ClientEditDetailsState extends State<ClientEditDetails>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
String name;
|
||||
String idNumber;
|
||||
String vatNumber;
|
||||
String website;
|
||||
String phone;
|
||||
class ClientEditDetailsState extends State<ClientEditDetails> {
|
||||
|
||||
final _nameController = TextEditingController();
|
||||
final _idNumberController = TextEditingController();
|
||||
final _vatNumberController = TextEditingController();
|
||||
final _websiteController = TextEditingController();
|
||||
final _phoneController = TextEditingController();
|
||||
|
||||
var _controllers = [];
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
void didChangeDependencies() {
|
||||
List<TextEditingController> _controllers = [
|
||||
_nameController,
|
||||
_idNumberController,
|
||||
_vatNumberController,
|
||||
_websiteController,
|
||||
_phoneController,
|
||||
];
|
||||
|
||||
_controllers.forEach((controller) => controller.removeListener(_onChanged));
|
||||
|
||||
var client = widget.viewModel.client;
|
||||
_nameController.text = client.name;
|
||||
_idNumberController.text = client.idNumber;
|
||||
_vatNumberController.text = client.vatNumber;
|
||||
_websiteController.text = client.website;
|
||||
_phoneController.text = client.workPhone;
|
||||
|
||||
_controllers.forEach((controller) => controller.addListener(_onChanged));
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controllers.forEach((controller) {
|
||||
controller.removeListener(_onChanged);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onChanged() {
|
||||
var viewModel = widget.viewModel;
|
||||
var client = viewModel.client.rebuild((b) => b
|
||||
..name = _nameController.text.trim()
|
||||
..idNumber = _idNumberController.text.trim()
|
||||
..vatNumber = _vatNumberController.text.trim()
|
||||
..website = _websiteController.text.trim()
|
||||
..workPhone = _phoneController.text.trim()
|
||||
);
|
||||
if (client != viewModel.client) {
|
||||
viewModel.onChanged(client);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var localization = AppLocalization.of(context);
|
||||
var client = widget.client;
|
||||
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
|
|
@ -39,32 +86,27 @@ class ClientEditDetailsState extends State<ClientEditDetails>
|
|||
children: <Widget>[
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => name = value.trim(),
|
||||
initialValue: client.name,
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.name,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => idNumber = value.trim(),
|
||||
initialValue: client.idNumber,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.idNumber,
|
||||
controller: _idNumberController,
|
||||
decoration: InputDecoration(labelText: localization.idNumber,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => vatNumber = value.trim(),
|
||||
initialValue: client.vatNumber,
|
||||
controller: _vatNumberController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.vatNumber,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => website = value.trim(),
|
||||
initialValue: client.website,
|
||||
controller: _websiteController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.website,
|
||||
),
|
||||
|
|
@ -72,8 +114,7 @@ class ClientEditDetailsState extends State<ClientEditDetails>
|
|||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => phone = value.trim(),
|
||||
initialValue: client.workPhone,
|
||||
controller: _phoneController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.phone,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,77 +8,120 @@ class ClientEditShippingAddress extends StatefulWidget {
|
|||
ClientEditShippingAddress({
|
||||
Key key,
|
||||
@required this.client,
|
||||
@required this.onChanged,
|
||||
}) : super(key: key);
|
||||
|
||||
final ClientEntity client;
|
||||
final Function(ClientEntity) onChanged;
|
||||
|
||||
@override
|
||||
ClientEditShippingAddressState createState() =>
|
||||
new ClientEditShippingAddressState();
|
||||
}
|
||||
|
||||
class ClientEditShippingAddressState extends State<ClientEditShippingAddress>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
String shippingAddress1;
|
||||
String shippingAddress2;
|
||||
String shippingCity;
|
||||
String shippingState;
|
||||
String shippingPostalCode;
|
||||
class ClientEditShippingAddressState extends State<ClientEditShippingAddress> {
|
||||
|
||||
final _shippingAddress1Controller = TextEditingController();
|
||||
final _shippingAddress2Controller = TextEditingController();
|
||||
final _shippingCityController = TextEditingController();
|
||||
final _shippingStateController = TextEditingController();
|
||||
final _shippingPostalCodeController = TextEditingController();
|
||||
|
||||
var _controllers = [];
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
void didChangeDependencies() {
|
||||
|
||||
_controllers = [
|
||||
_shippingAddress1Controller,
|
||||
_shippingAddress2Controller,
|
||||
_shippingCityController,
|
||||
_shippingStateController,
|
||||
_shippingPostalCodeController,
|
||||
];
|
||||
|
||||
_controllers.forEach((controller) => controller.removeListener(_onChanged));
|
||||
|
||||
var client = widget.client;
|
||||
_shippingAddress1Controller.text = client.shippingAddress1;
|
||||
_shippingAddress2Controller.text = client.shippingAddress2;
|
||||
_shippingCityController.text = client.shippingCity;
|
||||
_shippingStateController.text = client.shippingState;
|
||||
_shippingPostalCodeController.text = client.shippingPostalCode;
|
||||
|
||||
_controllers.forEach((controller) => controller.addListener(_onChanged));
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controllers.forEach((controller) {
|
||||
controller.removeListener(_onChanged);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onChanged() {
|
||||
var client = widget.client.rebuild((b) => b
|
||||
..shippingAddress1 = _shippingAddress1Controller.text.trim()
|
||||
..shippingAddress2 = _shippingAddress2Controller.text.trim()
|
||||
..shippingCity = _shippingCityController.text.trim()
|
||||
..shippingState = _shippingStateController.text.trim()
|
||||
..shippingPostalCode = _shippingPostalCodeController.text.trim()
|
||||
);
|
||||
if (client != widget.client) {
|
||||
widget.onChanged(client);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var localization = AppLocalization.of(context);
|
||||
var client = widget.client;
|
||||
|
||||
return ListView(children: <Widget>[
|
||||
return ListView(shrinkWrap: true, children: <Widget>[
|
||||
FormCard(
|
||||
children: <Widget>[
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => shippingAddress1 = value.trim(),
|
||||
initialValue: client.shippingAddress1,
|
||||
controller: _shippingAddress1Controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.address1,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => shippingAddress2 = value.trim(),
|
||||
initialValue: client.shippingAddress2,
|
||||
controller: _shippingAddress2Controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.address2,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => shippingCity = value.trim(),
|
||||
initialValue: client.shippingCity,
|
||||
controller: _shippingCityController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.city,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => shippingState = value.trim(),
|
||||
initialValue: client.shippingState,
|
||||
controller: _shippingStateController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.state,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => shippingPostalCode = value.trim(),
|
||||
initialValue: client.shippingPostalCode,
|
||||
controller: _shippingPostalCodeController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.postalCode,
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,20 @@ class ClientEditScreen extends StatelessWidget {
|
|||
class ClientEditVM {
|
||||
final bool isLoading;
|
||||
final ClientEntity client;
|
||||
final Function(BuildContext, ClientEntity) onSaveClicked;
|
||||
final Function(ClientEntity) onChanged;
|
||||
final Function() onAddContactClicked;
|
||||
final Function(int) onRemoveContactPressed;
|
||||
final Function(ContactEntity, int) onChangedContact;
|
||||
final Function(BuildContext) onSaveClicked;
|
||||
final Function onBackClicked;
|
||||
|
||||
ClientEditVM({
|
||||
@required this.isLoading,
|
||||
@required this.client,
|
||||
@required this.onAddContactClicked,
|
||||
@required this.onRemoveContactPressed,
|
||||
@required this.onChangedContact,
|
||||
@required this.onChanged,
|
||||
@required this.onSaveClicked,
|
||||
@required this.onBackClicked,
|
||||
});
|
||||
|
|
@ -49,17 +57,28 @@ class ClientEditVM {
|
|||
return ClientEditVM(
|
||||
client: client,
|
||||
isLoading: store.state.isLoading,
|
||||
onBackClicked: () {
|
||||
store.dispatch(UpdateCurrentRoute(ClientScreen.route));
|
||||
onBackClicked: () =>
|
||||
store.dispatch(UpdateCurrentRoute(ClientScreen.route)),
|
||||
onAddContactClicked: () => store.dispatch(AddContact()),
|
||||
onRemoveContactPressed: (index) => store.dispatch(DeleteContact(index)),
|
||||
onChangedContact: (contact, index) {
|
||||
print('== ON CHANGED');
|
||||
print(store.state.clientUIState.selected);
|
||||
print(contact);
|
||||
store.dispatch(UpdateContact(contact: contact, index: index));
|
||||
},
|
||||
onSaveClicked: (BuildContext context, ClientEntity client) {
|
||||
onChanged: (ClientEntity client) =>
|
||||
store.dispatch(UpdateClient(client)),
|
||||
onSaveClicked: (BuildContext context) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
store.dispatch(SaveClientRequest(completer, client));
|
||||
store.dispatch(
|
||||
SaveClientRequest(completer: completer, client: client));
|
||||
return completer.future.then((_) {
|
||||
if (client.isNew()) {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => ClientViewScreen()));
|
||||
Navigator
|
||||
.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => ClientViewScreen()));
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class _ClientViewState extends State<ClientView>
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.viewModel.client
|
||||
.displayName), // Text(localizations.clientDetails),
|
||||
.displayName ?? ''), // Text(localizations.clientDetails),
|
||||
bottom: TabBar(
|
||||
controller: _controller,
|
||||
//isScrollable: true,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class ClientOverview extends StatelessWidget {
|
|||
height: 6.0,
|
||||
),
|
||||
Text(
|
||||
client.paidToDate.toStringAsFixed(2),
|
||||
client.paidToDate?.toStringAsFixed(2) ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 26.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -51,7 +51,7 @@ class ClientOverview extends StatelessWidget {
|
|||
height: 6.0,
|
||||
),
|
||||
Text(
|
||||
client.balance.toStringAsFixed(2),
|
||||
client.balance?.toStringAsFixed(2) ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 26.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class ClientViewVM {
|
|||
},
|
||||
onSaveClicked: (BuildContext context, ClientEntity client) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
store.dispatch(SaveClientRequest(completer, client));
|
||||
store.dispatch(SaveClientRequest(completer: completer, client: client));
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: SnackBarRow(
|
||||
|
|
|
|||
|
|
@ -22,10 +22,6 @@ class _InvoiceEditState extends State<InvoiceEdit>
|
|||
with SingleTickerProviderStateMixin {
|
||||
TabController _controller;
|
||||
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
static final GlobalKey<InvoiceEditDetailsState> _detailsKey =
|
||||
GlobalKey<InvoiceEditDetailsState>();
|
||||
static final GlobalKey<InvoiceEditItemsState> _itemsKey =
|
||||
GlobalKey<InvoiceEditItemsState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -48,13 +44,11 @@ class _InvoiceEditState extends State<InvoiceEdit>
|
|||
List<Widget> editors = [
|
||||
InvoiceEditDetails(
|
||||
invoice: invoice,
|
||||
key: _detailsKey,
|
||||
clientList: viewModel.clientList,
|
||||
clientMap: viewModel.clientMap,
|
||||
),
|
||||
InvoiceEditItems(
|
||||
invoice: invoice,
|
||||
key: _itemsKey,
|
||||
),
|
||||
];
|
||||
|
||||
|
|
@ -87,7 +81,7 @@ class _InvoiceEditState extends State<InvoiceEdit>
|
|||
itemState?.getItems() ?? widget.viewModel.invoice.items));
|
||||
*/
|
||||
|
||||
widget.viewModel.onSaveClicked(context, invoice);
|
||||
widget.viewModel.onSaveClicked(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
|
|
|
|||
|
|
@ -11,30 +11,70 @@ class InvoiceEditDetails extends StatefulWidget {
|
|||
@required this.invoice,
|
||||
@required this.clientList,
|
||||
@required this.clientMap,
|
||||
@required this.onChanged,
|
||||
}) : super(key: key);
|
||||
|
||||
final InvoiceEntity invoice;
|
||||
final List<int> clientList;
|
||||
final BuiltMap<int, ClientEntity> clientMap;
|
||||
final Function(InvoiceEntity) onChanged;
|
||||
|
||||
@override
|
||||
InvoiceEditDetailsState createState() => new InvoiceEditDetailsState();
|
||||
}
|
||||
|
||||
class InvoiceEditDetailsState extends State<InvoiceEditDetails>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
int clientId;
|
||||
String invoiceDate;
|
||||
String dueDate;
|
||||
double partial;
|
||||
String partialDate;
|
||||
String invoiceNumber;
|
||||
String poNumber;
|
||||
double discount;
|
||||
bool isAmountDiscount;
|
||||
class InvoiceEditDetailsState extends State<InvoiceEditDetails> {
|
||||
|
||||
final _invoiceNumberController = TextEditingController();
|
||||
final _poNumberController = TextEditingController();
|
||||
final _discountController = TextEditingController();
|
||||
final _partialController = TextEditingController();
|
||||
|
||||
var _controllers = [];
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
void didChangeDependencies() {
|
||||
|
||||
_controllers = [
|
||||
_invoiceNumberController,
|
||||
_poNumberController,
|
||||
_discountController,
|
||||
_partialController,
|
||||
];
|
||||
|
||||
_controllers.forEach((controller) => controller.removeListener(_onChanged));
|
||||
|
||||
var invoice = widget.invoice;
|
||||
_invoiceNumberController.text = invoice.invoiceNumber;
|
||||
_poNumberController.text = invoice.poNumber;
|
||||
_discountController.text = invoice.discount?.toStringAsFixed(2) ?? '';
|
||||
_partialController.text = invoice.partial?.toStringAsFixed(2) ?? '';
|
||||
|
||||
_controllers.forEach((controller) => controller.addListener(_onChanged));
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controllers.forEach((controller) {
|
||||
controller.removeListener(_onChanged);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onChanged() {
|
||||
var invoice = widget.invoice.rebuild((b) => b
|
||||
..poNumber = _poNumberController.text.trim()
|
||||
..discount = double.tryParse(_discountController.text) ?? 0.0
|
||||
..partial = double.tryParse(_partialController.text) ?? 0.0
|
||||
);
|
||||
if (invoice != widget.invoice) {
|
||||
widget.onChanged(invoice);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -52,24 +92,21 @@ class InvoiceEditDetailsState extends State<InvoiceEditDetails>
|
|||
entityMap: widget.clientMap,
|
||||
) : TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => invoiceNumber = value.trim(),
|
||||
initialValue: invoice.invoiceNumber,
|
||||
controller: _invoiceNumberController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.invoiceNumber,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => poNumber = value.trim(),
|
||||
initialValue: invoice.poNumber,
|
||||
controller: _poNumberController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.poNumber,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => discount = double.tryParse(value) ?? 0.0,
|
||||
initialValue: invoice.discount?.toStringAsFixed(2),
|
||||
controller: _discountController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.discount,
|
||||
),
|
||||
|
|
@ -77,8 +114,7 @@ class InvoiceEditDetailsState extends State<InvoiceEditDetails>
|
|||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
onSaved: (value) => partial = double.tryParse(value) ?? 0.0,
|
||||
initialValue: invoice.partial?.toStringAsFixed(2),
|
||||
controller: _partialController,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.partial,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,91 +3,19 @@ import 'package:invoiceninja/data/models/models.dart';
|
|||
import 'package:invoiceninja/utils/localization.dart';
|
||||
import 'package:invoiceninja/ui/app/form_card.dart';
|
||||
|
||||
class InvoiceEditItems extends StatefulWidget {
|
||||
class InvoiceEditItems extends StatelessWidget {
|
||||
InvoiceEditItems({
|
||||
Key key,
|
||||
@required this.invoice,
|
||||
@required this.onChanged,
|
||||
}) : super(key: key);
|
||||
|
||||
final InvoiceEntity invoice;
|
||||
|
||||
@override
|
||||
InvoiceEditItemsState createState() => new InvoiceEditItemsState();
|
||||
}
|
||||
|
||||
class InvoiceEditItemsState extends State<InvoiceEditItems>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
List<InvoiceItemEntity> invoiceItems;
|
||||
List<GlobalKey<ItemEditDetailsState>> invoiceItemKeys;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var invoice = widget.invoice;
|
||||
invoiceItems = invoice.invoiceItems.toList();
|
||||
invoiceItemKeys = invoice.invoiceItems
|
||||
.map((invoiceItem) => GlobalKey<ItemEditDetailsState>())
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<InvoiceItemEntity> getItems() {
|
||||
List<InvoiceItemEntity> invoiceItems = [];
|
||||
invoiceItemKeys.forEach((invoiceItemKey) {
|
||||
if (invoiceItemKey.currentState != null) {
|
||||
invoiceItems.add(invoiceItemKey.currentState.getItem());
|
||||
}
|
||||
});
|
||||
return invoiceItems;
|
||||
}
|
||||
|
||||
_onAddPressed() {
|
||||
setState(() {
|
||||
invoiceItems.add(InvoiceItemEntity());
|
||||
invoiceItemKeys.add(GlobalKey<ItemEditDetailsState>());
|
||||
});
|
||||
}
|
||||
|
||||
_onRemovePressed(GlobalKey<ItemEditDetailsState> key) {
|
||||
setState(() {
|
||||
var index = invoiceItemKeys.indexOf(key);
|
||||
invoiceItemKeys.removeAt(index);
|
||||
invoiceItems.removeAt(index);
|
||||
});
|
||||
}
|
||||
final Function(InvoiceEntity) onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var localization = AppLocalization.of(context);
|
||||
List<Widget> widgets = [];
|
||||
|
||||
for (var i = 0; i < invoiceItems.length; i++) {
|
||||
var invoiceItem = invoiceItems[i];
|
||||
var invoiceItemKey = invoiceItemKeys[i];
|
||||
widgets.add(ItemEditDetails(
|
||||
invoiceItem: invoiceItem,
|
||||
key: invoiceItemKey,
|
||||
onRemovePressed: (key) => _onRemovePressed(key),
|
||||
isRemoveVisible: invoiceItems.length > 1,
|
||||
));
|
||||
}
|
||||
|
||||
widgets.add(Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 4.0),
|
||||
child: RaisedButton(
|
||||
elevation: 4.0,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Theme.of(context).secondaryHeaderColor,
|
||||
child: Text(localization.addItem.toUpperCase()),
|
||||
onPressed: _onAddPressed,
|
||||
),
|
||||
));
|
||||
|
||||
return ListView(
|
||||
children: widgets,
|
||||
);
|
||||
return new Container();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,10 +36,54 @@ class ItemEditDetails extends StatefulWidget {
|
|||
}
|
||||
|
||||
class ItemEditDetailsState extends State<ItemEditDetails> {
|
||||
String _productKey;
|
||||
String _notes;
|
||||
double _cost;
|
||||
double _qty;
|
||||
|
||||
final _productKeyController = TextEditingController();
|
||||
final _notesController = TextEditingController();
|
||||
final _costController = TextEditingController();
|
||||
final _qtyController = TextEditingController();
|
||||
|
||||
var _controllers = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
|
||||
_controllers = [
|
||||
_productKeyController,
|
||||
_notesController,
|
||||
_costController,
|
||||
_qtyController,
|
||||
];
|
||||
|
||||
_controllers.forEach((controller) => controller.removeListener(_onChanged));
|
||||
|
||||
//var client = widget.client;
|
||||
//_nameController.text = client.name;
|
||||
|
||||
_controllers.forEach((controller) => controller.addListener(_onChanged));
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controllers.forEach((controller) {
|
||||
controller.removeListener(_onChanged);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_onChanged() {
|
||||
/*
|
||||
var client = widget.client.rebuild((b) => b
|
||||
..name = _nameController.text.trim()
|
||||
);
|
||||
if (client != widget.client) {
|
||||
widget.onChanged(client);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
InvoiceItemEntity getItem() {
|
||||
return widget.invoiceItem.rebuild((b) => b
|
||||
|
|
@ -150,33 +122,25 @@ class ItemEditDetailsState extends State<ItemEditDetails> {
|
|||
children: <Widget>[
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
initialValue: widget.invoiceItem.productKey,
|
||||
onSaved: (value) => _productKey = value.trim(),
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.product,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
initialValue: widget.invoiceItem.notes,
|
||||
maxLines: 4,
|
||||
onSaved: (value) => _notes = value.trim(),
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.description,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
initialValue: widget.invoiceItem.cost?.toStringAsFixed(2),
|
||||
onSaved: (value) => _cost = double.tryParse(value) ?? 0.0,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.unitCost,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
autocorrect: false,
|
||||
initialValue: widget.invoiceItem.qty?.toStringAsFixed(2),
|
||||
onSaved: (value) => _qty = double.tryParse(value) ?? 0.0,
|
||||
decoration: InputDecoration(
|
||||
labelText: localization.quantity,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -38,23 +38,21 @@ class InvoiceEditVM {
|
|||
final InvoiceEntity invoice;
|
||||
final List<int> clientList;
|
||||
final BuiltMap<int, ClientEntity> clientMap;
|
||||
final Function onDelete;
|
||||
final Function(BuildContext, InvoiceEntity) onSaveClicked;
|
||||
final Function(InvoiceEntity) onChanged;
|
||||
final Function(BuildContext) onSaveClicked;
|
||||
final Function(BuildContext, EntityAction) onActionSelected;
|
||||
final Function onBackClicked;
|
||||
final bool isLoading;
|
||||
final bool isDirty;
|
||||
|
||||
InvoiceEditVM({
|
||||
@required this.invoice,
|
||||
@required this.clientList,
|
||||
@required this.clientMap,
|
||||
@required this.onDelete,
|
||||
@required this.onChanged,
|
||||
@required this.onSaveClicked,
|
||||
@required this.onBackClicked,
|
||||
@required this.onActionSelected,
|
||||
@required this.isLoading,
|
||||
@required this.isDirty,
|
||||
});
|
||||
|
||||
factory InvoiceEditVM.fromStore(Store<AppState> store) {
|
||||
|
|
@ -62,53 +60,55 @@ class InvoiceEditVM {
|
|||
final invoice = state.invoiceUIState.selected;
|
||||
|
||||
return InvoiceEditVM(
|
||||
isLoading: state.isLoading,
|
||||
isDirty: invoice.isNew(),
|
||||
invoice: invoice,
|
||||
clientList: memoizedActiveClientList(state.clientState.map, state.clientState.list),
|
||||
clientMap: state.clientState.map,
|
||||
onDelete: () => false,
|
||||
isLoading: state.isLoading,
|
||||
invoice: invoice,
|
||||
clientList: memoizedActiveClientList(
|
||||
state.clientState.map, state.clientState.list),
|
||||
clientMap: state.clientState.map,
|
||||
onBackClicked: () {
|
||||
store.dispatch(UpdateCurrentRoute(InvoiceScreen.route));
|
||||
},
|
||||
onSaveClicked: (BuildContext context, InvoiceEntity invoice) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
store.dispatch(SaveInvoiceRequest(completer, invoice));
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: SnackBarRow(
|
||||
message: invoice.isNew()
|
||||
? AppLocalization.of(context).successfullyCreatedInvoice
|
||||
: AppLocalization.of(context).successfullyUpdatedInvoice,
|
||||
),
|
||||
duration: Duration(seconds: 3)));
|
||||
onChanged: (InvoiceEntity invoice) {
|
||||
store.dispatch(UpdateInvoice(invoice));
|
||||
},
|
||||
onSaveClicked: (BuildContext context) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
store.dispatch(
|
||||
SaveInvoiceRequest(completer: completer, invoice: invoice));
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: SnackBarRow(
|
||||
message: invoice.isNew()
|
||||
? AppLocalization.of(context).successfullyCreatedInvoice
|
||||
: AppLocalization.of(context).successfullyUpdatedInvoice,
|
||||
),
|
||||
duration: Duration(seconds: 3)));
|
||||
});
|
||||
},
|
||||
onActionSelected: (BuildContext context, EntityAction action) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
var message = '';
|
||||
switch (action) {
|
||||
case EntityAction.archive:
|
||||
store.dispatch(ArchiveInvoiceRequest(completer, invoice.id));
|
||||
message = AppLocalization.of(context).successfullyArchivedInvoice;
|
||||
break;
|
||||
case EntityAction.delete:
|
||||
store.dispatch(DeleteInvoiceRequest(completer, invoice.id));
|
||||
message = AppLocalization.of(context).successfullyDeletedInvoice;
|
||||
break;
|
||||
case EntityAction.restore:
|
||||
store.dispatch(RestoreInvoiceRequest(completer, invoice.id));
|
||||
message = AppLocalization.of(context).successfullyRestoredInvoice;
|
||||
break;
|
||||
}
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: SnackBarRow(
|
||||
message: message,
|
||||
),
|
||||
duration: Duration(seconds: 3)));
|
||||
});
|
||||
});
|
||||
},
|
||||
onActionSelected: (BuildContext context, EntityAction action) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
var message = '';
|
||||
switch (action) {
|
||||
case EntityAction.archive:
|
||||
store.dispatch(ArchiveInvoiceRequest(completer, invoice.id));
|
||||
message = AppLocalization.of(context).successfullyArchivedInvoice;
|
||||
break;
|
||||
case EntityAction.delete:
|
||||
store.dispatch(DeleteInvoiceRequest(completer, invoice.id));
|
||||
message = AppLocalization.of(context).successfullyDeletedInvoice;
|
||||
break;
|
||||
case EntityAction.restore:
|
||||
store.dispatch(RestoreInvoiceRequest(completer, invoice.id));
|
||||
message = AppLocalization.of(context).successfullyRestoredInvoice;
|
||||
break;
|
||||
}
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: SnackBarRow(
|
||||
message: message,
|
||||
),
|
||||
duration: Duration(seconds: 3)));
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,34 +25,35 @@ class _ProductEditState extends State<ProductEdit> {
|
|||
final _notesController = TextEditingController();
|
||||
final _costController = TextEditingController();
|
||||
|
||||
var _controllers = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_productKeyController.removeListener(_onChanged);
|
||||
_notesController.removeListener(_onChanged);
|
||||
_costController.removeListener(_onChanged);
|
||||
|
||||
_controllers = [
|
||||
_productKeyController,
|
||||
_notesController,
|
||||
_costController,
|
||||
];
|
||||
|
||||
_controllers.forEach((controller) => controller.removeListener(_onChanged));
|
||||
|
||||
var product = widget.viewModel.product;
|
||||
|
||||
_productKeyController.text = product.productKey;
|
||||
_notesController.text = product.notes;
|
||||
_costController.text = product.cost?.toStringAsFixed(2) ?? '';
|
||||
|
||||
_productKeyController.addListener(_onChanged);
|
||||
_notesController.addListener(_onChanged);
|
||||
_costController.addListener(_onChanged);
|
||||
_controllers.forEach((controller) => controller.addListener(_onChanged));
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_productKeyController.removeListener(_onChanged);
|
||||
_notesController.removeListener(_onChanged);
|
||||
_costController.removeListener(_onChanged);
|
||||
|
||||
_productKeyController.dispose();
|
||||
_notesController.dispose();
|
||||
_costController.dispose();
|
||||
_controllers.forEach((controller) {
|
||||
controller.removeListener(_onChanged);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class ProductEditVM {
|
|||
},
|
||||
onSaveClicked: (BuildContext context) {
|
||||
final Completer<Null> completer = new Completer<Null>();
|
||||
store.dispatch(SaveProductRequest(completer));
|
||||
store.dispatch(SaveProductRequest(completer: completer, product: product));
|
||||
return completer.future.then((_) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
content: SnackBarRow(
|
||||
|
|
|
|||
Loading…
Reference in New Issue