This commit is contained in:
Hillel Coren 2019-12-02 18:07:55 +02:00
parent e0a1d69bf6
commit b214daccd8
11 changed files with 387 additions and 264 deletions

View File

@ -4,20 +4,27 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class AppForm extends StatelessWidget { class AppForm extends StatelessWidget {
const AppForm({ const AppForm({
@required this.children, this.children,
this.child,
@required this.formKey, @required this.formKey,
@required this.focusNode,
}); });
final GlobalKey<FormState> formKey; final GlobalKey<FormState> formKey;
final List<Widget> children; final List<Widget> children;
final Widget child;
final FocusScopeNode focusNode;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Form( return FocusScope(
key: formKey, node: focusNode,
child: ListView( child: Form(
shrinkWrap: true, key: formKey,
children: children, child: child ?? ListView(
shrinkWrap: true,
children: children,
),
), ),
); );
} }

View File

@ -4,6 +4,7 @@ import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart'; import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/app/forms/app_dropdown_button.dart'; import 'package:invoiceninja_flutter/ui/app/forms/app_dropdown_button.dart';
import 'package:invoiceninja_flutter/ui/app/forms/app_form.dart';
import 'package:invoiceninja_flutter/ui/app/forms/client_picker.dart'; import 'package:invoiceninja_flutter/ui/app/forms/client_picker.dart';
import 'package:invoiceninja_flutter/ui/app/forms/custom_field.dart'; import 'package:invoiceninja_flutter/ui/app/forms/custom_field.dart';
import 'package:invoiceninja_flutter/ui/app/forms/custom_surcharges.dart'; import 'package:invoiceninja_flutter/ui/app/forms/custom_surcharges.dart';
@ -38,6 +39,10 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
TabController _tabController; TabController _tabController;
FocusNode _focusNode;
static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_invoicesEdit');
final _invoiceNumberController = TextEditingController(); final _invoiceNumberController = TextEditingController();
final _poNumberController = TextEditingController(); final _poNumberController = TextEditingController();
final _discountController = TextEditingController(); final _discountController = TextEditingController();
@ -63,6 +68,7 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
void initState() { void initState() {
super.initState(); super.initState();
_focusNode = FocusScopeNode();
_tabController = TabController(vsync: this, length: 4); _tabController = TabController(vsync: this, length: 4);
} }
@ -111,7 +117,7 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
_surcharge4Controller.text = formatNumber(invoice.customSurcharge4, context, _surcharge4Controller.text = formatNumber(invoice.customSurcharge4, context,
formatNumberType: FormatNumberType.input); formatNumberType: FormatNumberType.input);
_designController.text = _designController.text =
invoice.designId != null ? kInvoiceDesigns[invoice.designId] : ''; invoice.designId != null ? kInvoiceDesigns[invoice.designId] : '';
_publicNotesController.text = invoice.publicNotes; _publicNotesController.text = invoice.publicNotes;
_privateNotesController.text = invoice.privateNotes; _privateNotesController.text = invoice.privateNotes;
_termsController.text = invoice.terms; _termsController.text = invoice.terms;
@ -125,6 +131,7 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
@override @override
void dispose() { void dispose() {
_focusNode.dispose();
_tabController.dispose(); _tabController.dispose();
_controllers.forEach((controller) { _controllers.forEach((controller) {
controller.removeListener(_onChanged); controller.removeListener(_onChanged);
@ -136,7 +143,8 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
void _onChanged() { void _onChanged() {
_debouncer.run(() { _debouncer.run(() {
final invoice = widget.viewModel.invoice.rebuild((b) => b final invoice = widget.viewModel.invoice.rebuild((b) =>
b
..number = widget.viewModel.invoice.isNew ..number = widget.viewModel.invoice.isNew
? '' ? ''
: _invoiceNumberController.text.trim() : _invoiceNumberController.text.trim()
@ -168,273 +176,299 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
final invoice = viewModel.invoice; final invoice = viewModel.invoice;
final company = viewModel.company; final company = viewModel.company;
return ListView( return AppForm(
children: <Widget>[ formKey: _formKey,
Row( focusNode: _focusNode,
crossAxisAlignment: CrossAxisAlignment.start, child: ListView(
mainAxisSize: MainAxisSize.max, children: <Widget>[
children: <Widget>[ Row(
Expanded( crossAxisAlignment: CrossAxisAlignment.start,
child: FormCard( mainAxisSize: MainAxisSize.max,
padding: const EdgeInsets.only( children: <Widget>[
top: kMobileDialogPadding, Expanded(
right: kMobileDialogPadding / 2, child: FormCard(
bottom: kMobileDialogPadding, padding: const EdgeInsets.only(
left: kMobileDialogPadding), top: kMobileDialogPadding,
children: <Widget>[ right: kMobileDialogPadding / 2,
if (invoice.isNew) bottom: kMobileDialogPadding,
ClientPicker( left: kMobileDialogPadding),
clientId: invoice.clientId, children: <Widget>[
clientState: viewModel.state.clientState, if (invoice.isNew)
onSelected: (client) => ClientPicker(
viewModel.onClientChanged(invoice, client), clientId: invoice.clientId,
onAddPressed: (completer) => clientState: viewModel.state.clientState,
viewModel.onAddClientPressed(context, completer), onSelected: (client) =>
viewModel.onClientChanged(invoice, client),
onAddPressed: (completer) =>
viewModel.onAddClientPressed(context, completer),
),
UserPicker(
userId: invoice.assignedUserId,
onChanged: (userId) =>
viewModel.onChanged(
invoice.rebuild((
b) => b..assignedUserId = userId)),
), ),
UserPicker( SizedBox(
userId: invoice.assignedUserId, height: 100,
onChanged: (userId) => viewModel.onChanged( child: InvoiceEditContactsScreen(),
invoice.rebuild((b) => b..assignedUserId = userId)), ),
), ],
SizedBox( ),
height: 100,
child: InvoiceEditContactsScreen(),
),
],
), ),
), Expanded(
Expanded( child: FormCard(
child: FormCard( padding: const EdgeInsets.only(
padding: const EdgeInsets.only( top: kMobileDialogPadding,
top: kMobileDialogPadding, right: kMobileDialogPadding / 2,
right: kMobileDialogPadding / 2, bottom: kMobileDialogPadding,
bottom: kMobileDialogPadding, left: kMobileDialogPadding / 2),
left: kMobileDialogPadding / 2), children: <Widget>[
children: <Widget>[
DatePicker(
validator: (String val) => val.trim().isEmpty
? AppLocalization.of(context).pleaseSelectADate
: null,
labelText: widget.isQuote
? localization.quoteDate
: localization.invoiceDate,
selectedDate: invoice.date,
onSelected: (date) {
viewModel
.onChanged(invoice.rebuild((b) => b..date = date));
},
),
DatePicker(
labelText: widget.isQuote
? localization.validUntil
: localization.dueDate,
selectedDate: invoice.dueDate,
onSelected: (date) {
viewModel
.onChanged(invoice.rebuild((b) => b..dueDate = date));
},
),
DecoratedFormField(
label: localization.partialDeposit,
controller: _partialController,
keyboardType:
TextInputType.numberWithOptions(decimal: true),
),
if (invoice.partial != null && invoice.partial > 0)
DatePicker( DatePicker(
labelText: localization.partialDueDate, validator: (String val) =>
selectedDate: invoice.partialDueDate, val
.trim()
.isEmpty
? AppLocalization
.of(context)
.pleaseSelectADate
: null,
labelText: widget.isQuote
? localization.quoteDate
: localization.invoiceDate,
selectedDate: invoice.date,
onSelected: (date) { onSelected: (date) {
viewModel.onChanged( viewModel
invoice.rebuild((b) => b..partialDueDate = date)); .onChanged(invoice.rebuild((b) => b..date = date));
}, },
), ),
CustomField( DatePicker(
controller: _custom1Controller, labelText: widget.isQuote
field: CustomFieldType.invoice1, ? localization.validUntil
value: invoice.customValue1, : localization.dueDate,
), selectedDate: invoice.dueDate,
CustomField( onSelected: (date) {
controller: _custom2Controller, viewModel
field: CustomFieldType.invoice2, .onChanged(
value: invoice.customValue2, invoice.rebuild((b) => b..dueDate = date));
), },
], ),
DecoratedFormField(
label: localization.partialDeposit,
controller: _partialController,
keyboardType:
TextInputType.numberWithOptions(decimal: true),
),
if (invoice.partial != null && invoice.partial > 0)
DatePicker(
labelText: localization.partialDueDate,
selectedDate: invoice.partialDueDate,
onSelected: (date) {
viewModel.onChanged(
invoice.rebuild((b) => b..partialDueDate = date));
},
),
CustomField(
controller: _custom1Controller,
field: CustomFieldType.invoice1,
value: invoice.customValue1,
),
CustomField(
controller: _custom2Controller,
field: CustomFieldType.invoice2,
value: invoice.customValue2,
),
],
),
), ),
), Expanded(
Expanded( child: FormCard(
child: FormCard( padding: const EdgeInsets.only(
padding: const EdgeInsets.only( top: kMobileDialogPadding,
top: kMobileDialogPadding, right: kMobileDialogPadding,
right: kMobileDialogPadding, bottom: kMobileDialogPadding,
bottom: kMobileDialogPadding, left: kMobileDialogPadding / 2),
left: kMobileDialogPadding / 2), children: <Widget>[
children: <Widget>[ DecoratedFormField(
DecoratedFormField( controller: _invoiceNumberController,
controller: _invoiceNumberController, label: widget.isQuote
label: widget.isQuote ? localization.quoteNumber
? localization.quoteNumber : localization.invoiceNumber,
: localization.invoiceNumber, validator: (String val) =>
validator: (String val) => val.trim().isEmpty && val
invoice.isOld .trim()
? AppLocalization.of(context).pleaseEnterAnInvoiceNumber .isEmpty &&
: null, invoice.isOld
), ? AppLocalization
DecoratedFormField( .of(context)
label: localization.poNumber, .pleaseEnterAnInvoiceNumber
controller: _poNumberController, : null,
), ),
DiscountField( DecoratedFormField(
controller: _discountController, label: localization.poNumber,
value: invoice.discount, controller: _poNumberController,
isAmountDiscount: invoice.isAmountDiscount, ),
onTypeChanged: (value) => viewModel.onChanged( DiscountField(
invoice.rebuild((b) => b..isAmountDiscount = value)), controller: _discountController,
), value: invoice.discount,
CustomField( isAmountDiscount: invoice.isAmountDiscount,
controller: _custom2Controller, onTypeChanged: (value) =>
field: CustomFieldType.invoice2, viewModel.onChanged(
value: invoice.customValue2, invoice.rebuild((
), b) => b..isAmountDiscount = value)),
CustomField( ),
controller: _custom4Controller, CustomField(
field: CustomFieldType.invoice4, controller: _custom2Controller,
value: invoice.customValue4, field: CustomFieldType.invoice2,
), value: invoice.customValue2,
], ),
CustomField(
controller: _custom4Controller,
field: CustomFieldType.invoice4,
value: invoice.customValue4,
),
],
),
), ),
), ],
], ),
), widget.isQuote ? QuoteEditItemsScreen() : InvoiceEditItemsScreen(),
widget.isQuote ? QuoteEditItemsScreen() : InvoiceEditItemsScreen(), Row(
Row( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
children: <Widget>[ Expanded(
Expanded( flex: 2,
flex: 2, child: FormCard(
child: FormCard( padding: const EdgeInsets.only(
padding: const EdgeInsets.only( top: kMobileDialogPadding,
top: kMobileDialogPadding, right: kMobileDialogPadding / 2,
right: kMobileDialogPadding / 2, bottom: kMobileDialogPadding,
bottom: kMobileDialogPadding, left: kMobileDialogPadding),
left: kMobileDialogPadding), children: <Widget>[
children: <Widget>[ TabBar(
TabBar(
controller: _tabController,
tabs: [
Tab(text: localization.publicNotes),
Tab(text: localization.privateNotes),
Tab(text: localization.terms),
Tab(text: localization.footer),
],
),
SizedBox(
height: 100,
child: TabBarView(
controller: _tabController, controller: _tabController,
children: <Widget>[ tabs: [
DecoratedFormField( Tab(text: localization.publicNotes),
maxLines: 4, Tab(text: localization.privateNotes),
controller: _publicNotesController, Tab(text: localization.terms),
keyboardType: TextInputType.multiline, Tab(text: localization.footer),
label: '',
),
DecoratedFormField(
maxLines: 4,
controller: _privateNotesController,
keyboardType: TextInputType.multiline,
label: '',
),
DecoratedFormField(
maxLines: 4,
controller: _termsController,
keyboardType: TextInputType.multiline,
label: '',
),
DecoratedFormField(
maxLines: 4,
controller: _footerController,
keyboardType: TextInputType.multiline,
label: '',
),
], ],
), ),
), SizedBox(
], height: 100,
child: TabBarView(
controller: _tabController,
children: <Widget>[
DecoratedFormField(
maxLines: 4,
controller: _publicNotesController,
keyboardType: TextInputType.multiline,
label: '',
),
DecoratedFormField(
maxLines: 4,
controller: _privateNotesController,
keyboardType: TextInputType.multiline,
label: '',
),
DecoratedFormField(
maxLines: 4,
controller: _termsController,
keyboardType: TextInputType.multiline,
label: '',
),
DecoratedFormField(
maxLines: 4,
controller: _footerController,
keyboardType: TextInputType.multiline,
label: '',
),
],
),
),
],
),
), ),
), Expanded(
Expanded( flex: 1,
flex: 1, child: FormCard(
child: FormCard( padding: const EdgeInsets.only(
padding: const EdgeInsets.only( top: kMobileDialogPadding,
top: kMobileDialogPadding, right: kMobileDialogPadding,
right: kMobileDialogPadding, bottom: kMobileDialogPadding,
bottom: kMobileDialogPadding, left: kMobileDialogPadding / 2),
left: kMobileDialogPadding / 2), children: <Widget>[
children: <Widget>[ /*
/* DecoratedFormField(
DecoratedFormField( controller: null,
controller: null, enabled: false,
enabled: false, initialValue: '10',
initialValue: '10', label: localization.subtotal,
label: localization.subtotal,
),
*/
CustomSurcharges(
surcharge1Controller: _surcharge1Controller,
surcharge2Controller: _surcharge2Controller,
surcharge3Controller: _surcharge3Controller,
surcharge4Controller: _surcharge4Controller,
),
if (company.settings.enableFirstInvoiceTaxRate)
TaxRateDropdown(
onSelected: (taxRate) =>
viewModel.onChanged(invoice.applyTax(taxRate)),
labelText: localization.tax,
initialTaxName: invoice.taxName1,
initialTaxRate: invoice.taxRate1,
), ),
if (company.settings.enableSecondInvoiceTaxRate) */
TaxRateDropdown( CustomSurcharges(
onSelected: (taxRate) => viewModel surcharge1Controller: _surcharge1Controller,
.onChanged(invoice.applyTax(taxRate, isSecond: true)), surcharge2Controller: _surcharge2Controller,
labelText: localization.tax, surcharge3Controller: _surcharge3Controller,
initialTaxName: invoice.taxName2, surcharge4Controller: _surcharge4Controller,
initialTaxRate: invoice.taxRate2,
), ),
if (company.settings.enableThirdInvoiceTaxRate) if (company.settings.enableFirstInvoiceTaxRate)
TaxRateDropdown( TaxRateDropdown(
onSelected: (taxRate) => viewModel onSelected: (taxRate) =>
.onChanged(invoice.applyTax(taxRate, isThird: true)), viewModel.onChanged(invoice.applyTax(taxRate)),
labelText: localization.tax, labelText: localization.tax,
initialTaxName: invoice.taxName3, initialTaxName: invoice.taxName1,
initialTaxRate: invoice.taxRate3, initialTaxRate: invoice.taxRate1,
),
if (company.settings.enableSecondInvoiceTaxRate)
TaxRateDropdown(
onSelected: (taxRate) =>
viewModel
.onChanged(
invoice.applyTax(taxRate, isSecond: true)),
labelText: localization.tax,
initialTaxName: invoice.taxName2,
initialTaxRate: invoice.taxRate2,
),
if (company.settings.enableThirdInvoiceTaxRate)
TaxRateDropdown(
onSelected: (taxRate) =>
viewModel
.onChanged(
invoice.applyTax(taxRate, isThird: true)),
labelText: localization.tax,
initialTaxName: invoice.taxName3,
initialTaxRate: invoice.taxRate3,
),
CustomSurcharges(
surcharge1Controller: _surcharge1Controller,
surcharge2Controller: _surcharge2Controller,
surcharge3Controller: _surcharge3Controller,
surcharge4Controller: _surcharge4Controller,
isAfterTaxes: true,
), ),
CustomSurcharges( AppDropdownButton(
surcharge1Controller: _surcharge1Controller, labelText: localization.design,
surcharge2Controller: _surcharge2Controller, value: invoice.designId,
surcharge3Controller: _surcharge3Controller, onChanged: (dynamic value) =>
surcharge4Controller: _surcharge4Controller, viewModel
isAfterTaxes: true, .onChanged(
), invoice.rebuild((b) => b..designId = value)),
AppDropdownButton( items: company.invoiceDesignIds
labelText: localization.design, .map((designId) =>
value: invoice.designId, DropdownMenuItem<String>(
onChanged: (dynamic value) => viewModel value: designId,
.onChanged(invoice.rebuild((b) => b..designId = value)), child: Text(kInvoiceDesigns[designId]),
items: company.invoiceDesignIds ))
.map((designId) => DropdownMenuItem<String>( .toList(),
value: designId, ),
child: Text(kInvoiceDesigns[designId]), ],
)) ),
.toList(),
),
],
), ),
), ],
], ),
), ],
], ),
); );
} }
} }

View File

@ -8,7 +8,9 @@ import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart';
class InvoiceEditItemsDesktop extends StatefulWidget { class InvoiceEditItemsDesktop extends StatefulWidget {
const InvoiceEditItemsDesktop({this.viewModel}); const InvoiceEditItemsDesktop({
this.viewModel,
});
final EntityEditItemsVM viewModel; final EntityEditItemsVM viewModel;
@ -20,6 +22,32 @@ class InvoiceEditItemsDesktop extends StatefulWidget {
class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> { class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
int _updatedAt; int _updatedAt;
/*
final Map<int, FocusNode> _focusNodes = {};
@override
void didChangeDependencies() {
_focusNodes.values.forEach((node) => node.dispose());
final lineItems = widget.viewModel.invoice.lineItems;
for (var index = 0; index < lineItems.length; index++) {
_focusNodes[index] = FocusNode()
..addListener(() => _onFocusChange(index));
}
super.didChangeDependencies();
}
@override
void dispose() {
_focusNodes.values.forEach((node) => node.dispose());
super.dispose();
}
void _onFocusChange(int index) {
setState(() {});
}
*/
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
@ -96,6 +124,8 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
lineItems[index].rebuild((b) => b..notes = value), index), lineItems[index].rebuild((b) => b..notes = value), index),
minLines: 1, minLines: 1,
maxLines: 6, maxLines: 6,
//maxLines: _focusNodes[index].hasFocus ? 6 : 1,
//focusNode: _focusNodes[index],
), ),
), ),
Padding( Padding(

View File

@ -9,7 +9,9 @@ import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class InvoiceEditItemsScreen extends StatelessWidget { class InvoiceEditItemsScreen extends StatelessWidget {
const InvoiceEditItemsScreen({Key key}) : super(key: key); const InvoiceEditItemsScreen({
Key key,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -10,7 +10,9 @@ import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class QuoteEditItemsScreen extends StatelessWidget { class QuoteEditItemsScreen extends StatelessWidget {
const QuoteEditItemsScreen({Key key}) : super(key: key); const QuoteEditItemsScreen({
Key key,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -33,11 +33,19 @@ class _LocalizationSettingsState extends State<LocalizationSettings> {
bool autoValidate = false; bool autoValidate = false;
final _firstNameController = TextEditingController(); final _firstNameController = TextEditingController();
FocusScopeNode _focusNode;
List<TextEditingController> _controllers = []; List<TextEditingController> _controllers = [];
@override
void initState() {
super.initState();
_focusNode = FocusScopeNode();
}
@override @override
void dispose() { void dispose() {
_focusNode.dispose();
_controllers.forEach((dynamic controller) { _controllers.forEach((dynamic controller) {
controller.removeListener(_onChanged); controller.removeListener(_onChanged);
controller.dispose(); controller.dispose();
@ -78,6 +86,7 @@ class _LocalizationSettingsState extends State<LocalizationSettings> {
onSavePressed: viewModel.onSavePressed, onSavePressed: viewModel.onSavePressed,
body: AppForm( body: AppForm(
formKey: _formKey, formKey: _formKey,
focusNode: _focusNode,
children: <Widget>[ children: <Widget>[
FormCard( FormCard(
children: <Widget>[ children: <Widget>[

View File

@ -21,6 +21,19 @@ class ProductSettings extends StatefulWidget {
class _ProductSettingsState extends State<ProductSettings> { class _ProductSettingsState extends State<ProductSettings> {
static final GlobalKey<FormState> _formKey = static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_productSettings'); GlobalKey<FormState>(debugLabel: '_productSettings');
FocusScopeNode _focusNode;
@override
void initState() {
super.initState();
_focusNode = FocusScopeNode();
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -33,6 +46,7 @@ class _ProductSettingsState extends State<ProductSettings> {
onSavePressed: viewModel.onSavePressed, onSavePressed: viewModel.onSavePressed,
body: AppForm( body: AppForm(
formKey: _formKey, formKey: _formKey,
focusNode: _focusNode,
children: <Widget>[ children: <Widget>[
FormCard( FormCard(
children: <Widget>[ children: <Widget>[

View File

@ -26,6 +26,19 @@ class TaxSettings extends StatefulWidget {
class _TaxSettingsState extends State<TaxSettings> { class _TaxSettingsState extends State<TaxSettings> {
static final GlobalKey<FormState> _formKey = static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_taxSettings'); GlobalKey<FormState>(debugLabel: '_taxSettings');
FocusScopeNode _focusNode;
@override
void initState() {
super.initState();
_focusNode = FocusScopeNode();
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -39,6 +52,7 @@ class _TaxSettingsState extends State<TaxSettings> {
onSavePressed: viewModel.onSavePressed, onSavePressed: viewModel.onSavePressed,
body: AppForm( body: AppForm(
formKey: _formKey, formKey: _formKey,
focusNode: _focusNode,
children: <Widget>[ children: <Widget>[
FormCard( FormCard(
children: <Widget>[ children: <Widget>[

View File

@ -23,7 +23,7 @@ class UserDetails extends StatefulWidget {
class _UserDetailsState extends State<UserDetails> { class _UserDetailsState extends State<UserDetails> {
static final GlobalKey<FormState> _formKey = static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_userDetails'); GlobalKey<FormState>(debugLabel: '_userDetails');
FocusScopeNode _focusNode;
bool autoValidate = false; bool autoValidate = false;
final _firstNameController = TextEditingController(); final _firstNameController = TextEditingController();
@ -34,8 +34,15 @@ class _UserDetailsState extends State<UserDetails> {
List<TextEditingController> _controllers = []; List<TextEditingController> _controllers = [];
final _debouncer = Debouncer(); final _debouncer = Debouncer();
@override
void initState() {
super.initState();
_focusNode = FocusScopeNode();
}
@override @override
void dispose() { void dispose() {
_focusNode.dispose();
_controllers.forEach((dynamic controller) { _controllers.forEach((dynamic controller) {
controller.removeListener(_onChanged); controller.removeListener(_onChanged);
controller.dispose(); controller.dispose();
@ -89,6 +96,7 @@ class _UserDetailsState extends State<UserDetails> {
title: localization.userDetails, title: localization.userDetails,
onSavePressed: viewModel.onSavePressed, onSavePressed: viewModel.onSavePressed,
body: AppForm( body: AppForm(
focusNode: _focusNode,
formKey: _formKey, formKey: _formKey,
children: <Widget>[ children: <Widget>[
FormCard( FormCard(

View File

@ -25,6 +25,7 @@ class _TaxRateEditState extends State<TaxRateEdit> {
static final GlobalKey<FormState> _formKey = static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_taxRateEdit'); GlobalKey<FormState>(debugLabel: '_taxRateEdit');
FocusScopeNode _focusNode;
bool autoValidate = false; bool autoValidate = false;
final _nameController = TextEditingController(); final _nameController = TextEditingController();
@ -85,6 +86,7 @@ class _TaxRateEditState extends State<TaxRateEdit> {
onSavePressed: viewModel.onSavePressed, onSavePressed: viewModel.onSavePressed,
onCancelPressed: viewModel.onCancelPressed, onCancelPressed: viewModel.onCancelPressed,
body: AppForm( body: AppForm(
focusNode: _focusNode,
formKey: _formKey, formKey: _formKey,
children: <Widget>[ children: <Widget>[
FormCard( FormCard(

View File

@ -29,7 +29,7 @@ class _UserEditState extends State<UserEdit> {
static final GlobalKey<FormState> _formKey = static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_userEdit'); GlobalKey<FormState>(debugLabel: '_userEdit');
final _debouncer = Debouncer(); final _debouncer = Debouncer();
FocusScopeNode _focusNode;
bool autoValidate = false; bool autoValidate = false;
final _firstNameController = TextEditingController(); final _firstNameController = TextEditingController();
@ -143,6 +143,7 @@ class _UserEditState extends State<UserEdit> {
], ],
), ),
body: AppForm( body: AppForm(
focusNode: _focusNode,
formKey: _formKey, formKey: _formKey,
children: <Widget>[ children: <Widget>[
FormCard( FormCard(