Transactions
This commit is contained in:
parent
0dccbd19f5
commit
1497c2382b
|
|
@ -52,18 +52,15 @@ import 'package:invoiceninja_flutter/redux/ui/pref_state.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/user/user_middleware.dart';
|
import 'package:invoiceninja_flutter/redux/user/user_middleware.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/vendor/vendor_middleware.dart';
|
import 'package:invoiceninja_flutter/redux/vendor/vendor_middleware.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/webhook/webhook_middleware.dart';
|
import 'package:invoiceninja_flutter/redux/webhook/webhook_middleware.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/transaction/transaction_middleware.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/bank_account/bank_account_middleware.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_middleware.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
// STARTER: import - do not remove comment
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/utils/web_stub.dart'
|
import 'package:invoiceninja_flutter/utils/web_stub.dart'
|
||||||
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
|
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
|
||||||
|
|
||||||
// STARTER: import - do not remove comment
|
|
||||||
import 'package:invoiceninja_flutter/redux/transaction/transaction_middleware.dart';
|
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/bank_account/bank_account_middleware.dart';
|
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_middleware.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
void main({bool isTesting = false}) async {
|
void main({bool isTesting = false}) async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,21 @@ class ViewBankAccount implements PersistUI, PersistPrefs {
|
||||||
final bool force;
|
final bool force;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EditBankAccount implements PersistUI, PersistPrefs {
|
||||||
|
EditBankAccount(
|
||||||
|
{@required this.bankAccount, this.completer, this.force = false});
|
||||||
|
|
||||||
|
final BankAccountEntity bankAccount;
|
||||||
|
final Completer completer;
|
||||||
|
final bool force;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateBankAccount implements PersistUI {
|
||||||
|
UpdateBankAccount(this.bankAccount);
|
||||||
|
|
||||||
|
final BankAccountEntity bankAccount;
|
||||||
|
}
|
||||||
|
|
||||||
class LoadBankAccount {
|
class LoadBankAccount {
|
||||||
LoadBankAccount({this.completer, this.bankAccountId});
|
LoadBankAccount({this.completer, this.bankAccountId});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,10 @@ Reducer<String> selectedIdReducer = combineReducers([
|
||||||
final editingReducer = combineReducers<BankAccountEntity>([
|
final editingReducer = combineReducers<BankAccountEntity>([
|
||||||
TypedReducer<BankAccountEntity, SaveBankAccountSuccess>(_updateEditing),
|
TypedReducer<BankAccountEntity, SaveBankAccountSuccess>(_updateEditing),
|
||||||
TypedReducer<BankAccountEntity, AddBankAccountSuccess>(_updateEditing),
|
TypedReducer<BankAccountEntity, AddBankAccountSuccess>(_updateEditing),
|
||||||
|
TypedReducer<BankAccountEntity, EditBankAccount>(_updateEditing),
|
||||||
|
TypedReducer<BankAccountEntity, UpdateBankAccount>((bankAccount, action) {
|
||||||
|
return action.bankAccount.rebuild((b) => b..isChanged = true);
|
||||||
|
}),
|
||||||
TypedReducer<BankAccountEntity, RestoreBankAccountsSuccess>(
|
TypedReducer<BankAccountEntity, RestoreBankAccountsSuccess>(
|
||||||
(bankAccounts, action) {
|
(bankAccounts, action) {
|
||||||
return action.bankAccounts[0];
|
return action.bankAccounts[0];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Flutter imports:
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// Project imports:
|
||||||
|
import 'package:invoiceninja_flutter/data/models/entities.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/forms/app_form.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/forms/custom_field.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/invoice/tax_rate_dropdown.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/bank_account/edit/bank_account_edit_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/completers.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/formatting.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||||
|
|
||||||
|
class BankAccountEdit extends StatefulWidget {
|
||||||
|
const BankAccountEdit({
|
||||||
|
Key key,
|
||||||
|
@required this.viewModel,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final BankAccountEditVM viewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_BankAccountEditState createState() => _BankAccountEditState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BankAccountEditState extends State<BankAccountEdit> {
|
||||||
|
static final GlobalKey<FormState> _formKey =
|
||||||
|
GlobalKey<FormState>(debugLabel: '_bankAccountEdit');
|
||||||
|
final FocusScopeNode _focusNode = FocusScopeNode();
|
||||||
|
bool _autoValidate = false;
|
||||||
|
|
||||||
|
final _nameController = TextEditingController();
|
||||||
|
|
||||||
|
List<TextEditingController> _controllers = [];
|
||||||
|
final _debouncer = Debouncer();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
_controllers = [
|
||||||
|
_nameController,
|
||||||
|
];
|
||||||
|
|
||||||
|
_controllers
|
||||||
|
.forEach((dynamic controller) => controller.removeListener(_onChanged));
|
||||||
|
|
||||||
|
final bankAccount = widget.viewModel.bankAccount;
|
||||||
|
_nameController.text = bankAccount.name;
|
||||||
|
|
||||||
|
_controllers
|
||||||
|
.forEach((dynamic controller) => controller.addListener(_onChanged));
|
||||||
|
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controllers.forEach((dynamic controller) {
|
||||||
|
controller.removeListener(_onChanged);
|
||||||
|
controller.dispose();
|
||||||
|
});
|
||||||
|
_focusNode.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onChanged() {
|
||||||
|
final bankAccount = widget.viewModel.bankAccount
|
||||||
|
.rebuild((b) => b..name = _nameController.text.trim());
|
||||||
|
if (bankAccount != widget.viewModel.bankAccount) {
|
||||||
|
_debouncer.run(() {
|
||||||
|
widget.viewModel.onChanged(bankAccount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final localization = AppLocalization.of(context);
|
||||||
|
final viewModel = widget.viewModel;
|
||||||
|
final bankAccount = viewModel.bankAccount;
|
||||||
|
final company = viewModel.company;
|
||||||
|
|
||||||
|
return EditScaffold(
|
||||||
|
entity: bankAccount,
|
||||||
|
title: viewModel.bankAccount.isNew
|
||||||
|
? localization.newBankAccount
|
||||||
|
: localization.editBankAccount,
|
||||||
|
onCancelPressed: (context) => viewModel.onCancelPressed(context),
|
||||||
|
onSavePressed: (context) {
|
||||||
|
final bool isValid = _formKey.currentState.validate();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_autoValidate = !isValid;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.onSavePressed(context);
|
||||||
|
},
|
||||||
|
body: AppForm(
|
||||||
|
formKey: _formKey,
|
||||||
|
focusNode: _focusNode,
|
||||||
|
child: ScrollableListView(
|
||||||
|
key: ValueKey(
|
||||||
|
'__bankAccount_${bankAccount.id}_${bankAccount.updatedAt}__'),
|
||||||
|
children: <Widget>[
|
||||||
|
FormCard(
|
||||||
|
isLast: true,
|
||||||
|
children: <Widget>[
|
||||||
|
DecoratedFormField(
|
||||||
|
autofocus: true,
|
||||||
|
label: localization.name,
|
||||||
|
controller: _nameController,
|
||||||
|
validator: (val) => val.isEmpty || val.trim().isEmpty
|
||||||
|
? localization.pleaseEnterAName
|
||||||
|
: null,
|
||||||
|
autovalidate: _autoValidate,
|
||||||
|
onSavePressed: viewModel.onSavePressed,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
// Dart imports:
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
// Flutter imports:
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// Package imports:
|
||||||
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
|
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/bank_account/bank_account_actions.dart';
|
||||||
|
import 'package:redux/redux.dart';
|
||||||
|
|
||||||
|
// Project imports:
|
||||||
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||||
|
import 'package:invoiceninja_flutter/main_app.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/app/app_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/bank_account/edit/bank_account_edit.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/bank_account/view/bank_account_view_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/completers.dart';
|
||||||
|
|
||||||
|
class BankAccountEditScreen extends StatelessWidget {
|
||||||
|
const BankAccountEditScreen({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
static const String route = '/bankAccount/edit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StoreConnector<AppState, BankAccountEditVM>(
|
||||||
|
converter: (Store<AppState> store) {
|
||||||
|
return BankAccountEditVM.fromStore(store);
|
||||||
|
},
|
||||||
|
builder: (context, vm) {
|
||||||
|
return BankAccountEdit(
|
||||||
|
viewModel: vm,
|
||||||
|
key: ValueKey(vm.bankAccount.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BankAccountEditVM {
|
||||||
|
BankAccountEditVM({
|
||||||
|
@required this.state,
|
||||||
|
@required this.company,
|
||||||
|
@required this.bankAccount,
|
||||||
|
@required this.origBankAccount,
|
||||||
|
@required this.onChanged,
|
||||||
|
@required this.onSavePressed,
|
||||||
|
@required this.onCancelPressed,
|
||||||
|
@required this.onEntityAction,
|
||||||
|
@required this.isSaving,
|
||||||
|
@required this.isDirty,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory BankAccountEditVM.fromStore(Store<AppState> store) {
|
||||||
|
final state = store.state;
|
||||||
|
final bankAccount = state.bankAccountUIState.editing;
|
||||||
|
|
||||||
|
return BankAccountEditVM(
|
||||||
|
state: state,
|
||||||
|
company: state.company,
|
||||||
|
isSaving: state.isSaving,
|
||||||
|
isDirty: bankAccount.isNew,
|
||||||
|
bankAccount: bankAccount,
|
||||||
|
origBankAccount: state.bankAccountState.map[bankAccount.id],
|
||||||
|
onChanged: (BankAccountEntity bankAccount) {
|
||||||
|
store.dispatch(UpdateBankAccount(bankAccount));
|
||||||
|
},
|
||||||
|
onCancelPressed: (BuildContext context) {
|
||||||
|
createEntity(
|
||||||
|
context: context, entity: BankAccountEntity(), force: true);
|
||||||
|
store.dispatch(UpdateCurrentRoute(state.uiState.previousRoute));
|
||||||
|
},
|
||||||
|
onSavePressed: (BuildContext context) {
|
||||||
|
Debouncer.runOnComplete(() {
|
||||||
|
final bankAccount = store.state.bankAccountUIState.editing;
|
||||||
|
final localization = navigatorKey.localization;
|
||||||
|
final navigator = navigatorKey.currentState;
|
||||||
|
final Completer<BankAccountEntity> completer =
|
||||||
|
new Completer<BankAccountEntity>();
|
||||||
|
store.dispatch(SaveBankAccountRequest(
|
||||||
|
completer: completer, bankAccount: bankAccount));
|
||||||
|
return completer.future.then((savedBankAccount) {
|
||||||
|
showToast(bankAccount.isNew
|
||||||
|
? localization.createdBankAccount
|
||||||
|
: localization.updatedBankAccount);
|
||||||
|
|
||||||
|
if (state.prefState.isMobile) {
|
||||||
|
store.dispatch(UpdateCurrentRoute(BankAccountViewScreen.route));
|
||||||
|
if (bankAccount.isNew) {
|
||||||
|
navigator.pushReplacementNamed(BankAccountViewScreen.route);
|
||||||
|
} else {
|
||||||
|
navigator.pop(savedBankAccount);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewEntity(entity: savedBankAccount);
|
||||||
|
}
|
||||||
|
}).catchError((Object error) {
|
||||||
|
showDialog<ErrorDialog>(
|
||||||
|
context: navigatorKey.currentContext,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return ErrorDialog(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEntityAction: (BuildContext context, EntityAction action) {
|
||||||
|
// TODO Add view page for bankAccounts
|
||||||
|
// Prevent duplicate global key error
|
||||||
|
if (action == EntityAction.clone) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((duration) {
|
||||||
|
handleBankAccountAction(context, [bankAccount], action);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleBankAccountAction(context, [bankAccount], action);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final AppState state;
|
||||||
|
final CompanyEntity company;
|
||||||
|
final BankAccountEntity bankAccount;
|
||||||
|
final BankAccountEntity origBankAccount;
|
||||||
|
final Function(BankAccountEntity) onChanged;
|
||||||
|
final Function(BuildContext) onSavePressed;
|
||||||
|
final Function(BuildContext) onCancelPressed;
|
||||||
|
final Function(BuildContext, EntityAction) onEntityAction;
|
||||||
|
final bool isSaving;
|
||||||
|
final bool isDirty;
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,9 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
||||||
static final Map<String, Map<String, String>> _localizedValues = {
|
static final Map<String, Map<String, String>> _localizedValues = {
|
||||||
'en': {
|
'en': {
|
||||||
// STARTER: lang key - do not remove comment
|
// STARTER: lang key - do not remove comment
|
||||||
|
'created_bank_account': 'Successfully created bank account',
|
||||||
|
'updated_bank_account': 'Successfully updated bank account',
|
||||||
|
'edit_bank_account': 'Edit Bank Account',
|
||||||
'default_category': 'Default Category',
|
'default_category': 'Default Category',
|
||||||
'account_type': 'Account Type',
|
'account_type': 'Account Type',
|
||||||
'new_bank_account': 'New Bank Account',
|
'new_bank_account': 'New Bank Account',
|
||||||
|
|
@ -87384,6 +87387,10 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
||||||
_localizedValues[localeCode]['new_bank_account'] ??
|
_localizedValues[localeCode]['new_bank_account'] ??
|
||||||
_localizedValues['en']['new_bank_account'];
|
_localizedValues['en']['new_bank_account'];
|
||||||
|
|
||||||
|
String get editBankAccount =>
|
||||||
|
_localizedValues[localeCode]['edit_bank_account'] ??
|
||||||
|
_localizedValues['en']['edit_bank_account'];
|
||||||
|
|
||||||
String get accountType =>
|
String get accountType =>
|
||||||
_localizedValues[localeCode]['account_type'] ??
|
_localizedValues[localeCode]['account_type'] ??
|
||||||
_localizedValues['en']['account_type'];
|
_localizedValues['en']['account_type'];
|
||||||
|
|
@ -87392,6 +87399,14 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
||||||
_localizedValues[localeCode]['default_category'] ??
|
_localizedValues[localeCode]['default_category'] ??
|
||||||
_localizedValues['en']['default_category'];
|
_localizedValues['en']['default_category'];
|
||||||
|
|
||||||
|
String get createdBankAccount =>
|
||||||
|
_localizedValues[localeCode]['created_bank_account'] ??
|
||||||
|
_localizedValues['en']['created_bank_account'];
|
||||||
|
|
||||||
|
String get updatedBankAccount =>
|
||||||
|
_localizedValues[localeCode]['updated_bank_account'] ??
|
||||||
|
_localizedValues['en']['updated_bank_account'];
|
||||||
|
|
||||||
// STARTER: lang field - do not remove comment
|
// STARTER: lang field - do not remove comment
|
||||||
|
|
||||||
String lookup(String key) {
|
String lookup(String key) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue