This commit is contained in:
Hillel Coren 2020-02-05 10:14:17 +02:00
parent c4100c5c74
commit 96e0bc2d67
7 changed files with 634 additions and 156 deletions

View File

@ -34,6 +34,8 @@ import 'package:invoiceninja_flutter/ui/app/screen_imports.dart';
import 'package:invoiceninja_flutter/ui/auth/init_screen.dart';
import 'package:invoiceninja_flutter/ui/auth/lock_screen.dart';
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
import 'package:invoiceninja_flutter/ui/payment/refund/payment_refund.dart';
import 'package:invoiceninja_flutter/ui/payment/refund/payment_refund_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/settings_screen_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/tax_settings_vm.dart';
import 'package:invoiceninja_flutter/utils/colors.dart';
@ -333,6 +335,7 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
PaymentScreen.route: (context) => PaymentScreenBuilder(),
PaymentViewScreen.route: (context) => PaymentViewScreen(),
PaymentEditScreen.route: (context) => PaymentEditScreen(),
PaymentRefundScreen.route: (context) => PaymentRefundScreen(),
QuoteScreen.route: (context) => QuoteScreenBuilder(),
QuoteViewScreen.route: (context) => QuoteViewScreen(),
QuoteEditScreen.route: (context) => QuoteEditScreen(),
@ -341,7 +344,6 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
UserScreen.route: (context) => UserScreenBuilder(),
UserViewScreen.route: (context) => UserViewScreen(),
UserEditScreen.route: (context) => UserEditScreen(),
GroupSettingsScreen.route: (context) => GroupScreenBuilder(),
GroupViewScreen.route: (context) => GroupViewScreen(),
GroupEditScreen.route: (context) => GroupEditScreen(),

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:built_collection/built_collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
@ -42,6 +43,20 @@ class EditPayment extends AbstractNavigatorAction
final bool force;
}
class RefundPayment extends AbstractNavigatorAction
implements PersistUI, PersistPrefs {
RefundPayment(
{@required this.payment,
@required NavigatorState navigator,
this.completer,
this.force = false})
: super(navigator: navigator);
final PaymentEntity payment;
final Completer completer;
final bool force;
}
class UpdatePayment implements PersistUI {
UpdatePayment(this.payment);
@ -280,7 +295,10 @@ void handlePaymentAction(
editEntity(context: context, entity: payment);
break;
case EntityAction.refund:
// TODO ....
store.dispatch(RefundPayment(
navigator: Navigator.of(context),
payment: payment,
));
break;
case EntityAction.sendEmail:
store.dispatch(EmailPaymentRequest(

View File

@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart';
import 'package:invoiceninja_flutter/redux/app/app_middleware.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
import 'package:invoiceninja_flutter/ui/payment/refund/payment_refund_vm.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
@ -20,6 +21,7 @@ List<Middleware<AppState>> createStorePaymentsMiddleware([
final viewPaymentList = _viewPaymentList();
final viewPayment = _viewPayment();
final editPayment = _editPayment();
final refundPayment = _refundPayment();
final loadPayments = _loadPayments(repository);
//final loadPayment = _loadPayment(repository);
final savePayment = _savePayment(repository);
@ -32,6 +34,7 @@ List<Middleware<AppState>> createStorePaymentsMiddleware([
TypedMiddleware<AppState, ViewPaymentList>(viewPaymentList),
TypedMiddleware<AppState, ViewPayment>(viewPayment),
TypedMiddleware<AppState, EditPayment>(editPayment),
TypedMiddleware<AppState, RefundPayment>(refundPayment),
TypedMiddleware<AppState, LoadPayments>(loadPayments),
//TypedMiddleware<AppState, LoadPayment>(loadPayment),
TypedMiddleware<AppState, SavePaymentRequest>(savePayment),
@ -61,6 +64,25 @@ Middleware<AppState> _editPayment() {
};
}
Middleware<AppState> _refundPayment() {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as RefundPayment;
if (!action.force &&
hasChanges(store: store, context: action.context, action: action)) {
return;
}
next(action);
store.dispatch(UpdateCurrentRoute(PaymentRefundScreen.route));
if (isMobile(action.context)) {
action.navigator.pushNamed(PaymentRefundScreen.route);
}
};
}
Middleware<AppState> _viewPayment() {
return (Store<AppState> store, dynamic action, NextDispatcher next) async {
if (!action.force &&

View File

@ -9,6 +9,7 @@ import 'package:invoiceninja_flutter/ui/app/history_drawer_vm.dart';
import 'package:invoiceninja_flutter/ui/app/menu_drawer_vm.dart';
import 'package:invoiceninja_flutter/ui/app/help_text.dart';
import 'package:invoiceninja_flutter/ui/app/screen_imports.dart';
import 'package:invoiceninja_flutter/ui/payment/refund/payment_refund_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/settings_screen_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/tax_settings_vm.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
@ -23,157 +24,159 @@ class MainScreen extends StatelessWidget {
return StoreBuilder(
onInit: (Store<AppState> store) => store.dispatch(LoadClients()),
builder: (BuildContext context, Store<AppState> store) {
final uiState = store.state.uiState;
final prefState = store.state.prefState;
final mainRoute = '/' + uiState.mainRoute;
final subRoute = '/' + uiState.subRoute;
Widget screen = BlankScreen();
final uiState = store.state.uiState;
final prefState = store.state.prefState;
final mainRoute = '/' + uiState.mainRoute;
final subRoute = '/' + uiState.subRoute;
Widget screen = BlankScreen();
if ([
InvoiceScreen.route,
QuoteScreen.route,
].contains(mainRoute) &&
subRoute == '/edit' &&
prefState.isDesktop) {
switch (mainRoute) {
case InvoiceScreen.route:
screen = InvoiceEditScreen();
break;
case QuoteScreen.route:
screen = QuoteEditScreen();
break;
}
} else {
switch (mainRoute) {
case DashboardScreenBuilder.route:
screen = Row(
children: <Widget>[
Expanded(
child: DashboardScreenBuilder(),
flex: 5,
),
if (prefState.showHistory) ...[
_CustomDivider(),
HistoryDrawerBuilder(),
],
if ([
InvoiceScreen.route,
QuoteScreen.route,
].contains(mainRoute) &&
subRoute == '/edit' &&
prefState.isDesktop) {
switch (mainRoute) {
case InvoiceScreen.route:
screen = InvoiceEditScreen();
break;
case QuoteScreen.route:
screen = QuoteEditScreen();
break;
}
} else {
switch (mainRoute) {
case DashboardScreenBuilder.route:
screen = Row(
children: <Widget>[
Expanded(
child: DashboardScreenBuilder(),
flex: 5,
),
if (prefState.showHistory) ...[
_CustomDivider(),
HistoryDrawerBuilder(),
],
],
);
break;
case ClientScreen.route:
screen = EntityScreens(
entityType: EntityType.client,
listWidget: ClientScreenBuilder(),
viewWidget: ClientViewScreen(),
editWidget: ClientEditScreen());
break;
case ProductScreen.route:
screen = EntityScreens(
entityType: EntityType.product,
listWidget: ProductScreenBuilder(),
viewWidget: ProductViewScreen(),
editWidget: ProductEditScreen(),
);
break;
case InvoiceScreen.route:
screen = EntityScreens(
entityType: EntityType.invoice,
listWidget: InvoiceScreenBuilder(),
viewWidget: InvoiceViewScreen(),
editWidget: InvoiceEditScreen(),
emailWidget: InvoiceEmailScreen(),
);
break;
case PaymentScreen.route:
screen = EntityScreens(
entityType: EntityType.payment,
listWidget: PaymentScreenBuilder(),
viewWidget: PaymentViewScreen(),
editWidget: PaymentEditScreen(),
refundWidget: PaymentRefundScreen(),
);
break;
case QuoteScreen.route:
screen = EntityScreens(
entityType: EntityType.quote,
listWidget: QuoteScreenBuilder(),
viewWidget: QuoteViewScreen(),
editWidget: QuoteEditScreen(),
);
break;
case ProjectScreen.route:
screen = EntityScreens(
entityType: EntityType.project,
listWidget: ProjectScreenBuilder(),
viewWidget: ProjectViewScreen(),
editWidget: ProjectEditScreen(),
);
break;
case TaskScreen.route:
screen = EntityScreens(
entityType: EntityType.task,
listWidget: TaskScreenBuilder(),
viewWidget: TaskViewScreen(),
editWidget: TaskEditScreen(),
);
break;
case VendorScreen.route:
screen = EntityScreens(
entityType: EntityType.vendor,
listWidget: VendorScreenBuilder(),
viewWidget: VendorViewScreen(),
editWidget: VendorEditScreen(),
);
break;
case ExpenseScreen.route:
screen = EntityScreens(
entityType: EntityType.expense,
listWidget: ExpenseScreenBuilder(),
viewWidget: ExpenseViewScreen(),
editWidget: ExpenseEditScreen(),
);
break;
case SettingsScreen.route:
screen = SettingsScreens();
break;
}
}
return WillPopScope(
onWillPop: () async {
final state = store.state;
final historyList = state.historyList;
final notViewingEntity = state.uiState.isEditing ||
state.uiState.isInSettings ||
(historyList[0].entityType.toString() !=
state.uiState.mainRoute);
if (historyList.isEmpty ||
historyList.length == 1 && !notViewingEntity) {
return false;
}
final history = historyList[notViewingEntity ? 0 : 1];
if (!notViewingEntity) {
store.dispatch(PopLastHistory());
}
viewEntityById(
entityType: history.entityType,
entityId: history.id,
context: context,
);
return false;
},
child: Row(children: <Widget>[
if (prefState.showMenu) ...[
MenuDrawerBuilder(),
_CustomDivider(),
],
);
break;
case ClientScreen.route:
screen = EntityScreens(
entityType: EntityType.client,
listWidget: ClientScreenBuilder(),
viewWidget: ClientViewScreen(),
editWidget: ClientEditScreen());
break;
case ProductScreen.route:
screen = EntityScreens(
entityType: EntityType.product,
listWidget: ProductScreenBuilder(),
viewWidget: ProductViewScreen(),
editWidget: ProductEditScreen(),
);
break;
case InvoiceScreen.route:
screen = EntityScreens(
entityType: EntityType.invoice,
listWidget: InvoiceScreenBuilder(),
viewWidget: InvoiceViewScreen(),
editWidget: InvoiceEditScreen(),
emailWidget: InvoiceEmailScreen(),
);
break;
case PaymentScreen.route:
screen = EntityScreens(
entityType: EntityType.payment,
listWidget: PaymentScreenBuilder(),
viewWidget: PaymentViewScreen(),
editWidget: PaymentEditScreen(),
);
break;
case QuoteScreen.route:
screen = EntityScreens(
entityType: EntityType.quote,
listWidget: QuoteScreenBuilder(),
viewWidget: QuoteViewScreen(),
editWidget: QuoteEditScreen(),
);
break;
case ProjectScreen.route:
screen = EntityScreens(
entityType: EntityType.project,
listWidget: ProjectScreenBuilder(),
viewWidget: ProjectViewScreen(),
editWidget: ProjectEditScreen(),
);
break;
case TaskScreen.route:
screen = EntityScreens(
entityType: EntityType.task,
listWidget: TaskScreenBuilder(),
viewWidget: TaskViewScreen(),
editWidget: TaskEditScreen(),
);
break;
case VendorScreen.route:
screen = EntityScreens(
entityType: EntityType.vendor,
listWidget: VendorScreenBuilder(),
viewWidget: VendorViewScreen(),
editWidget: VendorEditScreen(),
);
break;
case ExpenseScreen.route:
screen = EntityScreens(
entityType: EntityType.expense,
listWidget: ExpenseScreenBuilder(),
viewWidget: ExpenseViewScreen(),
editWidget: ExpenseEditScreen(),
);
break;
case SettingsScreen.route:
screen = SettingsScreens();
break;
}
}
return WillPopScope(
onWillPop: () async {
final state = store.state;
final historyList = state.historyList;
final notViewingEntity = state.uiState.isEditing ||
state.uiState.isInSettings ||
(historyList[0].entityType.toString() != state.uiState.mainRoute);
if (historyList.isEmpty ||
historyList.length == 1 && !notViewingEntity) {
return false;
}
final history = historyList[notViewingEntity ? 0 : 1];
if (!notViewingEntity) {
store.dispatch(PopLastHistory());
}
viewEntityById(
entityType: history.entityType,
entityId: history.id,
context: context,
Expanded(child: screen),
]),
);
return false;
},
child: Row(children: <Widget>[
if (prefState.showMenu) ...[
MenuDrawerBuilder(),
_CustomDivider(),
],
Expanded(child: screen),
]),
);
});
});
}
}
@ -305,12 +308,14 @@ class EntityScreens extends StatelessWidget {
@required this.viewWidget,
@required this.entityType,
this.emailWidget,
this.refundWidget,
});
final Widget listWidget;
final Widget viewWidget;
final Widget editWidget;
final Widget emailWidget;
final Widget refundWidget;
final EntityType entityType;
@override
@ -347,12 +352,14 @@ class EntityScreens extends StatelessWidget {
flex: previewFlex,
child: subRoute == 'email'
? emailWidget
: subRoute == 'edit'
? editWidget
: (entityUIState.selectedId ?? '').isNotEmpty
? viewWidget
: BlankScreen(
AppLocalization.of(context).noRecordSelected),
: subRoute == 'refund'
? refundWidget
: subRoute == 'edit'
? editWidget
: (entityUIState.selectedId ?? '').isNotEmpty
? viewWidget
: BlankScreen(
AppLocalization.of(context).noRecordSelected),
),
if (prefState.showHistory) ...[
_CustomDivider(),

View File

@ -0,0 +1,291 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
import 'package:invoiceninja_flutter/data/models/payment_model.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart';
import 'package:invoiceninja_flutter/redux/client/client_selectors.dart';
import 'package:invoiceninja_flutter/redux/static/static_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/app/forms/date_picker.dart';
import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart';
import 'package:invoiceninja_flutter/ui/payment/edit/payment_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart';
import 'package:invoiceninja_flutter/ui/payment/refund/payment_refund_vm.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/ui/app/entity_dropdown.dart';
class PaymentRefund extends StatefulWidget {
const PaymentRefund({
Key key,
@required this.viewModel,
}) : super(key: key);
final PaymentRefundVM viewModel;
@override
_PaymentRefundState createState() => _PaymentRefundState();
}
class _PaymentRefundState extends State<PaymentRefund> {
static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_paymentRefund');
final _amountController = TextEditingController();
List<TextEditingController> _controllers = [];
final _debouncer = Debouncer();
bool autoValidate = false;
@override
void didChangeDependencies() {
_controllers = [
_amountController,
];
_controllers.forEach((controller) => controller.removeListener(_onChanged));
final payment = widget.viewModel.payment;
_amountController.text = formatNumber(payment.amount, context,
formatNumberType: FormatNumberType.input);
_controllers.forEach((controller) => controller.addListener(_onChanged));
super.didChangeDependencies();
}
@override
void dispose() {
_controllers.forEach((controller) {
controller.removeListener(_onChanged);
controller.dispose();
});
super.dispose();
}
void _onChanged() {
_debouncer.run(() {
final payment = widget.viewModel.payment
.rebuild((b) => b..amount = parseDouble(_amountController.text));
if (payment != widget.viewModel.payment) {
widget.viewModel.onChanged(payment);
}
});
}
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
final payment = viewModel.payment;
final localization = AppLocalization.of(context);
final paymentables = payment.invoices.toList();
if (paymentables.where((paymentable) => paymentable.isEmpty).isEmpty) {
paymentables.add(PaymentableEntity());
}
return EditScaffold(
entity: payment,
title: localization.refund,
saveLabel: localization.refund,
onCancelPressed: (context) => viewModel.onCancelPressed(context),
onSavePressed: (context) {
final bool isValid = _formKey.currentState.validate();
setState(() {
autoValidate = !isValid;
});
if (!isValid) {
return;
}
viewModel.onSavePressed(context);
},
body: Form(
key: _formKey,
child: ListView(
key: ValueKey(viewModel.payment.id),
children: <Widget>[
FormCard(
children: <Widget>[
DecoratedFormField(
controller: _amountController,
autocorrect: false,
keyboardType: TextInputType.numberWithOptions(decimal: true),
label: localization.amount,
),
for (var index = 0; index < paymentables.length; index++)
PaymentableEditor(
key: ValueKey(
'__paymentable_${index}_${paymentables[index].id}__'),
viewModel: viewModel,
paymentable: paymentables[index],
index: index,
onChanged: () {},
),
DatePicker(
validator: (String val) => val.trim().isEmpty
? AppLocalization.of(context).pleaseSelectADate
: null,
autoValidate: autoValidate,
labelText: localization.refundDate,
selectedDate: payment.date,
onSelected: (date) {
viewModel.onChanged(payment.rebuild((b) => b..date = date));
},
),
],
),
payment.isNew
? FormCard(children: <Widget>[
SwitchListTile(
activeColor: Theme.of(context).accentColor,
title: Text(localization.sendEmail),
value: viewModel.prefState.emailPayment,
subtitle: Text(localization.emailReceipt),
onChanged: (value) => viewModel.onEmailChanged(value),
),
])
: Container(),
],
),
),
);
}
}
class PaymentableEditor extends StatefulWidget {
const PaymentableEditor({
Key key,
@required this.viewModel,
@required this.paymentable,
@required this.onChanged,
@required this.index,
}) : super(key: key);
final PaymentRefundVM viewModel;
final PaymentableEntity paymentable;
final Function onChanged;
final int index;
@override
_PaymentableEditorState createState() => _PaymentableEditorState();
}
class _PaymentableEditorState extends State<PaymentableEditor> {
final _amountController = TextEditingController();
String _invoiceId = '';
List<TextEditingController> _controllers = [];
@override
void didChangeDependencies() {
_controllers = [
_amountController,
];
_controllers.forEach((controller) => controller.removeListener(_onChanged));
_amountController.text = formatNumber(widget.paymentable.amount, context,
formatNumberType: FormatNumberType.input);
_controllers.forEach((controller) => controller.addListener(_onChanged));
super.didChangeDependencies();
}
@override
void dispose() {
_controllers.forEach((controller) {
controller.removeListener(_onChanged);
controller.dispose();
});
super.dispose();
}
void _onChanged([String clientId]) {
final paymentable = widget.paymentable.rebuild((b) => b
..invoiceId = _invoiceId
..amount = parseDouble(_amountController.text));
if (paymentable == widget.paymentable || paymentable.isEmpty) {
return;
}
PaymentEntity payment;
if (widget.index == widget.viewModel.payment.invoices.length) {
payment =
widget.viewModel.payment.rebuild((b) => b..invoices.add(paymentable));
} else {
payment = widget.viewModel.payment
.rebuild((b) => b..invoices[widget.index] = paymentable);
}
if (clientId != null) {
payment = payment.rebuild((b) => b..clientId = clientId);
}
widget.viewModel.onChanged(payment);
}
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
final payment = viewModel.payment;
final paymentable = widget.paymentable;
final localization = AppLocalization.of(context);
return Row(
children: <Widget>[
Expanded(
child: EntityDropdown(
key: Key('__invoice_${payment.clientId}__'),
entityType: EntityType.invoice,
labelText: AppLocalization.of(context).invoice,
entityId: paymentable.invoiceId,
entityList: memoizedDropdownInvoiceList(
widget.viewModel.invoiceMap,
widget.viewModel.clientMap,
widget.viewModel.invoiceList,
payment.clientId),
onSelected: (selected) {
final invoice = selected as InvoiceEntity;
_amountController.text = formatNumber(invoice.balance, context,
formatNumberType: FormatNumberType.input);
_invoiceId = invoice.id;
_onChanged(invoice.clientId);
},
),
),
SizedBox(
width: kTableColumnGap,
),
Expanded(
child: DecoratedFormField(
controller: _amountController,
label: localization.applied,
),
),
SizedBox(
width: kTableColumnGap,
),
IconButton(
icon: Icon(Icons.clear),
tooltip: localization.remove,
onPressed: paymentable.isEmpty
? null
: () {
viewModel.onChanged(payment
.rebuild((b) => b..invoices.removeAt(widget.index)));
},
),
],
);
}
}

View File

@ -0,0 +1,135 @@
import 'dart:async';
import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/client_model.dart';
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/static/static_state.dart';
import 'package:invoiceninja_flutter/redux/ui/pref_state.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
import 'package:invoiceninja_flutter/ui/payment/refund/payment_refund.dart';
import 'package:invoiceninja_flutter/ui/payment/view/payment_view_vm.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart';
import 'package:invoiceninja_flutter/data/models/payment_model.dart';
import 'package:invoiceninja_flutter/ui/payment/edit/payment_edit.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:shared_preferences/shared_preferences.dart';
class PaymentRefundScreen extends StatelessWidget {
const PaymentRefundScreen({Key key}) : super(key: key);
static const String route = '/payment/refund';
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, PaymentRefundVM>(
converter: (Store<AppState> store) {
return PaymentRefundVM.fromStore(store);
},
builder: (context, viewModel) {
return PaymentRefund(
viewModel: viewModel,
key: ValueKey(viewModel.payment.id),
);
},
);
}
}
class PaymentRefundVM {
PaymentRefundVM({
@required this.payment,
@required this.origPayment,
@required this.onChanged,
@required this.onSavePressed,
@required this.onEmailChanged,
@required this.prefState,
@required this.invoiceMap,
@required this.invoiceList,
@required this.clientMap,
@required this.clientList,
@required this.staticState,
@required this.onCancelPressed,
@required this.isSaving,
@required this.isDirty,
});
factory PaymentRefundVM.fromStore(Store<AppState> store) {
final state = store.state;
final payment = state.paymentUIState.editing;
return PaymentRefundVM(
isSaving: state.isSaving,
isDirty: payment.isNew,
origPayment: state.paymentState.map[payment.id],
payment: payment,
prefState: state.prefState,
staticState: state.staticState,
invoiceMap: state.invoiceState.map,
invoiceList: state.invoiceState.list,
clientMap: state.clientState.map,
clientList: state.clientState.list,
onChanged: (PaymentEntity payment) {
store.dispatch(UpdatePayment(payment));
},
onEmailChanged: (value) async {
if (payment.isOld) {
return;
}
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(kSharedPrefEmailPayment, value);
store.dispatch(UserSettingsChanged(emailPayment: value));
},
onCancelPressed: (BuildContext context) {
createEntity(context: context, entity: PaymentEntity(), force: true);
store.dispatch(UpdateCurrentRoute(state.uiState.previousRoute));
},
onSavePressed: (BuildContext context) {
final Completer<PaymentEntity> completer = Completer<PaymentEntity>();
store.dispatch(
SavePaymentRequest(completer: completer, payment: payment));
return completer.future.then((savedPayment) {
if (isMobile(context)) {
store.dispatch(UpdateCurrentRoute(PaymentViewScreen.route));
if (payment.isNew) {
Navigator.of(context)
.pushReplacementNamed(PaymentViewScreen.route);
} else {
Navigator.of(context).pop(savedPayment);
}
} else {
viewEntity(context: context, entity: savedPayment, force: true);
}
}).catchError((Object error) {
showDialog<ErrorDialog>(
context: context,
builder: (BuildContext context) {
return ErrorDialog(error);
});
});
},
);
}
final PaymentEntity payment;
final PaymentEntity origPayment;
final Function(PaymentEntity) onChanged;
final Function(BuildContext) onSavePressed;
final Function(BuildContext) onCancelPressed;
final Function(bool) onEmailChanged;
final BuiltMap<String, InvoiceEntity> invoiceMap;
final PrefState prefState;
final BuiltList<String> invoiceList;
final BuiltMap<String, ClientEntity> clientMap;
final BuiltList<String> clientList;
final StaticState staticState;
final bool isSaving;
final bool isDirty;
}

View File

@ -15,6 +15,7 @@ mixin LocalizationsProvider on LocaleCodeAware {
static final Map<String, Map<String, String>> _localizedValues = {
'en': {
'refund': 'Refund',
'refund_date': 'Refund Date',
'filtered_by': 'Filtered by :value',
'contact_email': 'Email',
'multiselect': 'Multiselect',
@ -15948,6 +15949,8 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get refund => _localizedValues[localeCode]['refund'];
String get refundDate => _localizedValues[localeCode]['refund_date'];
String lookup(String key) {
final lookupKey = toSnakeCase(key);
return _localizedValues[localeCode][lookupKey] ??